Commit e0881225 authored by Paris Stentoumis's avatar Paris Stentoumis
Browse files

docs(adr-0053): use order-local federated_handoff instead of partner inventory

parent e58dc628
Loading
Loading
Loading
Loading
+31 −17
Changes for decisions/0053-srm-fm-federated-lifecycle-handoff-over-the-databus.md: 31 added lines, 17 removed lines.
Original line number Diff line number Diff line
@@ -9,9 +9,9 @@ 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; 2026-09-29: `partner_not_found`)
**Status:** proposed (amended 2026-09-25: SRM keeps instance rows for partner targets; 2026-09-29: `partner_not_found`; 2026-10-08: order-local `federated_handoff` instead of partner inventory)
**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)
**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), [srm/persistence-model.md](/srm/persistence-model.md) (`service_order.federated_handoff`), [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.*`

> **Numbering.** `0052` is skipped on purpose. `oeg-fm` has renumbered its ADRs once already
@@ -119,7 +119,7 @@ isn't built for request/reply.
| Subject | Stream | Payload | SRM publishes it when |
|---|---|---|---|
| `command.fm.service.deploy` | `OOP_TASKS` | the `SrmServiceDeployV1` SRM received, unshaped | the target zone came back federated |
| `command.fm.service.terminate` | `OOP_TASKS` | the `SrmServiceTerminateV1` SRM received, unshaped | the instance's row says `hosted_by: partner` |
| `command.fm.service.terminate` | `OOP_TASKS` | the `SrmServiceTerminateV1` SRM received, unshaped | the instance is absent from SRM's local inventory (partner-hosted; SRM never wrote a partner `service_instance`) |

The envelope keeps the original `operation_id` and `correlation_id`. SRM does no GSMA shaping; FM
alone maps to `InstallApp` and `RemoveApp` (ADR-0044). Terminate has no lookup step: FM resolves the
@@ -187,25 +187,39 @@ in-memory timer, so a restart doesn't lose track of it.
### Record ownership

This amends step 4 of the SRM hand-off flow ("No local `service_order`/`service_instance` rows are
written for the federated portion"), which splits in two:
written for the federated portion"), and **supersedes the 2026-09-25 amendment** that had SRM
persist partner `service_instance` rows and `federation_business` zone rows:

- **SRM writes a `service_order` row** for federated deploys and terminates, as for any command it
  accepts. Its `UNIQUE(operation_id)` constraint is the idempotency anchor, and the row lets SRM
  match a late `event.fm.operation.completed` after a restart. The Component Boundary Matrix already
  gives service orders to SRM.
- **SRM writes a `service_instance` row for each partner target too**, in state `creating` with
  `metadata.hosted_by: partner`. An order can target zones at any number of partners, and SRM needs
  somewhere to remember which of them have answered; OEG also only updates an instance it finds in
  the final event's `instances[]`. Because `service_instance.zone_id` is a non-null foreign key into
  SRM's `zone` table, SRM records the partner zone there first, as `kind: federation_business`,
  from FM's `GET /internal/federated-zones/{zone_id}`. `list_resource_zones` only returns
  `kind: resource`, so these rows don't reach placement candidates or `GET /internal/zones` twice,
  and a later deploy to a recorded partner zone still goes to FM. Drift reconciliation must skip
  `hosted_by: partner` instances, since SRM can't observe them; it doesn't exist yet.
- **SRM completes the order from FM's answer.** The order stays `in_progress` while any target is
  `creating`. Each `event.fm.operation.completed` moves its instance to `active` or `failed`; once
  none is left `creating`, SRM completes the order (`completed`, `partially_completed` or `failed`)
  and publishes `event.srm.operation.completed` listing every instance.
- **`payload_snapshot` stays a frozen copy of the inbound command** (audit only). It is not mutated
  to track partner progress.
- **Pending partner targets live only in `service_order.federated_handoff`** (JSONB, default `{}`).
  Shape when the order has federated placements:

  ```json
  {
    "targets": [
      {
        "app_instance_id": "<uuid>",
        "zone_id": "<uuid>",
        "status": "pending|completed|failed"
      }
    ]
  }
  ```

  Empty `{}` when the order has no federated targets. SRM does **not** persist partner zones in
  `zone` or partner `service_instance` rows; partner inventory stays with FM
  (`federation_transactions` / `shared_resource_catalogues`).
- **SRM completes the order from FM's answer.** The order stays `in_progress` while any
  `federated_handoff.targets[]` entry is `pending`. Each `event.fm.operation.completed` updates the
  matching target's `status`; once none is left `pending`, SRM completes the order (`completed`,
  `partially_completed` or `failed`) and publishes `event.srm.operation.completed` whose
  `instances[]` lists local inventory rows plus the partner entries from `federated_handoff`
  (so OEG can update every `appInstanceId`).

FM keeps its `federation_transactions` row unchanged, as the audit and dispute record that ties the
partner's `appInstIdentifier` to our `operation_id`.
+1 −1
Changes for decisions/index.md: 1 added line, 1 removed line.
Original line number Diff line number Diff line
@@ -52,6 +52,6 @@ the reasoning lives here. Never renumber; supersede instead (see the record-deci
* [ADR-0049](0049-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-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; 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-0053](0053-srm-fm-federated-lifecycle-handoff-over-the-databus.md) - SRM hands federated deploys and terminates to FM as `command.fm.service.deploy` / `.terminate` on `OOP_TASKS`, and FM reports the outcome as `event.fm.operation.completed` on `OOP_EVENTS`; zone lookups stay synchronous HTTP reads. `completion_basis` separates a confirmed deploy from a terminate the partner only accepted. SRM writes a `service_order` row and a `service_instance` row per partner target (`hosted_by: partner`, partner zone recorded as `federation_business`) and completes the order from FM's answer; FM waits 30 minutes by default for a deploy callback. Amends ADR-0001/0022 (FM consumes `command.fm.>`) and ADR-0046's DataBus scope; resolves G4
* [ADR-0053](0053-srm-fm-federated-lifecycle-handoff-over-the-databus.md) - SRM hands federated deploys and terminates to FM as `command.fm.service.deploy` / `.terminate` on `OOP_TASKS`, and FM reports the outcome as `event.fm.operation.completed` on `OOP_EVENTS`; zone lookups stay synchronous HTTP reads. `completion_basis` separates a confirmed deploy from a terminate the partner only accepted. SRM writes a `service_order` and tracks pending partner targets in order-local `federated_handoff` (no partner zone/instance inventory); `payload_snapshot` stays frozen command audit; completes the order from FM's answer. FM waits 30 minutes by default for a deploy callback. Amends ADR-0001/0022 (FM consumes `command.fm.>`) and ADR-0046's DataBus scope; resolves G4
* [ADR-0054](0054-fm-receives-gsma-callbacks-through-one-router.md) - FM receives every OPG.04 callback on `/operatorplatform/federation/v1/callbacks/{kind}`: one router that checks the `fed-mgmt-notif` scope and that `federationContextId` is an outbound context of the calling partner, then a per-kind idempotent handler. `onPartnerStatusEvent` first; `FM_PARTNER_STATUS_LINK` moves to `/callbacks/partner-status`. The `federation` realm gets `fed-mgmt-notif` as an optional scope on partner clients
* [ADR-0061](0061-fm-onboards-apps-at-partners-over-ewbi-before-installapp.md) - FM onboards an application at a partner over the EWBI (`UploadFile`, `UploadArtefact`, `OnboardApplication`) on the first `InstallApp` of that specification version, with derived GSMA ids, public repositories and container units only for now, and `flavourId` from the zone subscription; the partner creates the specification in its SRM under the originating partner as tenant and resolves `InstallApp` from that onboarding, replacing ADR-0018's manual app mapping. Adds `onApplicationOnboardStatusEvent` to ADR-0054's router
+3 −2
Changes for fm/overview-and-architecture.md: 3 added lines, 2 removed lines.
Original line number Diff line number Diff line
@@ -430,7 +430,7 @@ A locally connected App Provider deploys to an Edge Cloud Zone that turns out to
  1. OEG publishes `command.srm.service.deploy` unconditionally, as it does for every deploy (no locality check in OEG).
  2. SRM checks its own zone inventory; not found locally, so it queries FM for federated zone/capability data.
  3. FM returns the matching federated zone entry (from `shared_resource_catalogues`).
  4. SRM writes its `service_order` row and publishes `command.fm.service.deploy` on `OOP_TASKS`, carrying the deploy command it received unchanged (not raw GSMA/OPG.04); the outcome comes back later on `event.fm.operation.completed` (ADR-0053, §G4).
  4. SRM writes its `service_order` row (order-local `federated_handoff` for pending partner targets; no partner zone/instance inventory) and publishes `command.fm.service.deploy` on `OOP_TASKS`, carrying the deploy command it received unchanged (not raw GSMA/OPG.04); the outcome comes back later on `event.fm.operation.completed` (ADR-0053, §G4).
  5. FM maps the request to OPG.04 `InstallApp` and executes the outbound EWBI call to the partner's FM, through the same code path used for inbound federation-originated deploys (UC-FM-01's fulfillment pipeline).
  6. FM publishes `event.fm.operation.completed` on `OOP_EVENTS`; SRM consumes it and publishes `event.srm.operation.completed` for the original command, as for a local deploy.
- **Why this exists:** Keeps placement (local-vs-federated) a single SRM-owned decision instead of splitting it between OEG and FM; see ADR-0044.
@@ -558,7 +558,7 @@ goes through OEG at all — see G4.

**The question was:** When SRM finds a deploy's target zone is federated (UC-FM-07), how does it hand the work to FM: synchronous internal HTTP, or an async DataBus command?

**Status: resolved (ADR-0053).** Over the DataBus in both directions, for terminate as well as deploy. SRM publishes `command.fm.service.deploy` or `command.fm.service.terminate` on `OOP_TASKS`, and FM answers with `event.fm.operation.completed` on `OOP_EVENTS`, which SRM turns into its usual `event.srm.operation.completed`. Zone lookups (`GET /internal/federated-zones`) stay synchronous HTTP reads. The earlier recommendation here, HTTP for the request, was dropped in review: OEG has already answered the App Provider before SRM sees the command, so an HTTP `202` from FM would tell SRM nothing a JetStream acknowledgement doesn't, and one hand-off shouldn't need two transports. ADR-0053 also has SRM write a `service_order` row for federated work (no `service_instance` row) and sets FM's deploy-callback wait at 30 minutes by default. See [srm/interface-contract.md §F.4a](/srm/interface-contract.md) and [fm/requirements-and-design.md §L.3a](/fm/requirements-and-design.md).
**Status: resolved (ADR-0053).** Over the DataBus in both directions, for terminate as well as deploy. SRM publishes `command.fm.service.deploy` or `command.fm.service.terminate` on `OOP_TASKS`, and FM answers with `event.fm.operation.completed` on `OOP_EVENTS`, which SRM turns into its usual `event.srm.operation.completed`. Zone lookups (`GET /internal/federated-zones`) stay synchronous HTTP reads. The earlier recommendation here, HTTP for the request, was dropped in review: OEG has already answered the App Provider before SRM sees the command, so an HTTP `202` from FM would tell SRM nothing a JetStream acknowledgement doesn't, and one hand-off shouldn't need two transports. ADR-0053 has SRM write a `service_order` for federated work, track pending partner targets in order-local `federated_handoff` (no partner zone/instance inventory), and sets FM's deploy-callback wait at 30 minutes by default. See [srm/interface-contract.md §F.4a](/srm/interface-contract.md) and [fm/requirements-and-design.md §L.3a](/fm/requirements-and-design.md).

---

@@ -573,3 +573,4 @@ goes through OEG at all — see G4.
| 2.0 | 2026-09-08 | A.1 EWBI-termination row notes the Nginx EWBI ingress is not deployed today, so there is no partner-facing inbound EWBI ingress and inbound federation is a future release; the operator-triggered OEG→FM internal path (§L.3b) works today regardless. Clarification, no behaviour change. |
| 2.0 | 2026-09-08 | ADR-0051 corrects ADR-0046's scope/payload/namespace (transport kept): OEG serves **seven** canonical OPG.04 federation-lifecycle operations (create/read/update/delete federation, `GetFederationContextId`, zone subscribe/unsubscribe) for the operator-triggered *outbound* direction, on `/operatorplatform/federation/v1/*`, forwarding the raw GSMA payload **byte-for-byte** — FM no longer "maps an operator-facing payload to OPG.04". Inbound partner traffic on the same paths still terminates at FM; the two directions split at the ingress. Intro bullets, A.1 EWBI-termination row, UC-FM-06, dependency and boundary-matrix rows 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 surface moves back to its own `/platform/v1/federation/partners/{partnerOpId}/federations/...` namespace, not the canonical prefix — the two directions no longer share a URL or split at the ingress at all. Eight operations now (`GetZoneData` added). The "authenticated by OEG's platform service credential" language is corrected everywhere it appeared: this hop has no dedicated credential, only cluster-internal network-policy reachability. Intro bullets, A.1 EWBI-termination row, UC-FM-06, dependency and boundary-matrix rows updated again. ADR-0047/0048 renumbered to ADR-0050/0051. |
| 2.0 | 2026-10-08 | UC-FM-07 / §G4: ADR-0053 amendment — SRM tracks pending partner targets in `service_order.federated_handoff`; no partner zone/instance inventory in SRM. |
+2 −1
Changes for fm/requirements-and-design.md: 2 added lines, 1 removed line.
Original line number Diff line number Diff line
@@ -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 instance's row says `metadata.hosted_by: partner` |
| `command.fm.service.terminate` | `OOP_TASKS` | `SrmServiceTerminateV1`, as SRM received it | the instance is absent from SRM's local inventory (partner-hosted; no partner `service_instance` in SRM) |

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`.
@@ -902,4 +902,5 @@ A locally connected App Provider queries device location for a subscriber on a p
| 2.0 | 2026-09-28 | Federation Context entity: operator-triggered establishment (OEG's §L.3b proxy, ADR-0051) and bootstrap moved from the inbound sentence to the outbound one — on outbound the partner issues `federationContextId` and FM stores it; the stale ADR-0046 citation replaced with ADR-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-10-08 | §L.3a: terminate publish trigger aligned with ADR-0053's 2026-10-08 amendment (instance absent from SRM local inventory; no partner instance rows). |
| 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. |
+22 −18
Changes for srm/interface-contract.md: 22 added lines, 18 removed lines.
Original line number Diff line number Diff line
@@ -870,14 +870,16 @@ on the DataBus in both directions, with only the zone lookup as a synchronous re
   network-capability placement.
2. Not found locally: SRM calls FM's `GET /internal/federated-zones/{zone_id}`. A `404` (nobody has
   the zone) is a normal placement failure, the same error path as an unresolvable local zone.
3. Found as federated: SRM writes its `service_order` row and publishes
   `command.fm.service.deploy` on `OOP_TASKS` with the `SrmServiceDeployV1` payload it received,
   unshaped. FM owns everything from here, including the mapping to OPG.04 `InstallApp`.
3. Found as federated: SRM writes its `service_order` row — `payload_snapshot` frozen as the inbound
   command, `federated_handoff.targets[]` seeded `pending` for each partner placement — and
   publishes `command.fm.service.deploy` on `OOP_TASKS` with the `SrmServiceDeployV1` payload it
   received, unshaped. No partner `zone` or `service_instance` row is written. FM owns everything
   from here, including the mapping to OPG.04 `InstallApp`.

**Terminate:**

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`.
1. SRM looks up `service_instance_id` in its own inventory; absent means the instance is
   partner-hosted (SRM never persisted a partner instance row).
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.
@@ -885,10 +887,11 @@ on the DataBus in both directions, with only the zone lookup as a synchronous re
**Completion, both operations:**

SRM consumes `event.fm.operation.completed` from `OOP_EVENTS`, correlates on `operation_id`,
completes the `service_order`, and publishes `event.srm.operation.completed` as usual. That event
must list the instance in `instances[]` (status `completed` for a finished terminate): OEG only
updates the instances it finds there, so an empty list would leave the App Provider's instance
stuck in `INSTANTIATING` or `TERMINATING`.
updates `federated_handoff` for deploy partner targets, completes the `service_order` when no
target remains `pending`, and publishes `event.srm.operation.completed` as usual. That event must
list the instance in `instances[]` (status `completed` for a finished terminate): OEG only updates
the instances it finds there, so an empty list would leave the App Provider's instance 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),
@@ -899,15 +902,15 @@ flattened: `partner_confirmed` (the partner's deploy callback reported the insta
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`.

**Record ownership (amended by ADR-0053).** SRM **writes a `service_order` row** for federated
deploys and terminates, as for every command it accepts. It's the `UNIQUE(operation_id)` idempotency
anchor (§D.4) and what lets SRM match a late `event.fm.operation.completed` after a restart. SRM
also **writes a `service_instance` row per partner target**, state `creating`, `metadata.hosted_by:
partner`, after recording the partner zone in `zone` as `kind: federation_business` (the row's
`zone_id` is a foreign key). The order stays `in_progress` until every partner target has an
`event.fm.operation.completed`; SRM then completes it and publishes `event.srm.operation.completed`
listing every instance, which is what OEG updates its `app_instances` from. Drift reconciliation
skips `hosted_by: partner` instances.
**Record ownership (amended by ADR-0053, 2026-10-08).** SRM **writes a `service_order` row** for
federated deploys and terminates, as for every command it accepts. It's the `UNIQUE(operation_id)`
idempotency anchor (§D.4) and what lets SRM match a late `event.fm.operation.completed` after a
restart. Pending partner targets live only in **`service_order.federated_handoff`**;
`payload_snapshot` stays frozen command audit. SRM does **not** persist partner zones or partner
`service_instance` rows. The order stays `in_progress` until every `federated_handoff` target is
non-`pending`; SRM then completes it and publishes `event.srm.operation.completed` listing local
instances plus partner entries from `federated_handoff`, which is what OEG updates its
`app_instances` from.

**Retry ownership.** SRM owns getting the command onto `OOP_TASKS`: it publishes through JetStream
and waits for the acknowledgement, inside its own `command.srm.*` handling. FM owns everything after
@@ -929,3 +932,4 @@ interim layer until the runtime coordinator in platform/future-architecture.md
| 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. |
| 2.0 | 2026-10-08 | §F.4a: ADR-0053 amendment — partner progress on `service_order.federated_handoff` only; no partner zone/instance inventory; terminate again keys off "not found locally"; complete when no `federated_handoff` target is `pending`. |
Loading