Commit 33841b7f authored by Sergio Gimenez's avatar Sergio Gimenez
Browse files

docs(fm,srm): write the concrete SRM->FM hand-off interface

parent 8cff3bee
Loading
Loading
Loading
Loading
+4 −4
Changes for fm/overview-and-architecture.md: 4 added lines, 4 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 forwards the deploy to FM using FM's internal schema (not raw GSMA/OPG.04) — exact request/response contract, retry, and timeout semantics are an open item (§G4).
  4. SRM forwards the deploy to FM using FM's internal schema (not raw GSMA/OPG.04) via a synchronous `POST /internal/federation/deploy` answered `202`; the outcome returns later on `event.fm.operation.completed` (ADR-0052, §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 reports the result back to SRM, which resumes its normal `event.srm.operation.completed` lifecycle for the original command.
- **Why this exists:** Keeps placement (local-vs-federated) a single SRM-owned decision instead of splitting it between OEG and FM; see ADR-0044.
@@ -554,11 +554,11 @@ goes through OEG at all — see G4.

**Recommendation:** Internal HTTP from OEG to FM for all outbound federation hand-offs (synchronous queries). OEG calls FM with the routing identifiers and original request context; FM resolves the partner, forwards over EWBI, and returns the result to OEG. This keeps EWBI protocol knowledge inside FM and avoids a second async hop on the developer-facing latency path. Initially, OEG rejects non-local requests with a 400/404 (federation not yet available). The OEG spec already anticipates this pattern in UC-OEG-05.

### G4. SRM → FM Hand-off for Federated Deployment (ADR-0044)
### G4. SRM → FM Hand-off for Federated Deployment (resolved by ADR-0052)

**The question:** When SRM determines a deploy target zone is federated (UC-FM-07), what is the exact hand-off contract to FM — synchronous internal HTTP (matching every other FM↔SRM internal call in this document) or a new async DataBus command?
**The question was:** When SRM determines a deploy target zone is federated (UC-FM-07), what is the exact hand-off contract to FM — synchronous internal HTTP or a new async DataBus command?

**Status:** unresolved. The 2026-09-01 TSC+MDL meeting decided SRM (not OEG) owns this hand-off, but did not conclude the transport. **Recommendation:** default to synchronous internal HTTP for consistency with L.4's existing FM↔SRM pattern, with SRM treating the call as part of its own already-async `command.srm.service.deploy` handling (SRM owns retry/timeout toward FM, not OEG). Revisit once FM/SRM implementation work starts on UC-FM-07.
**Status: resolved (ADR-0052).** Both, split by hop, and extended to cover termination as well as deployment. The request hop is a synchronous internal HTTP `POST` to FM (`/internal/federation/deploy`, `/internal/federation/terminate`) answered `202`; the result hop is `event.fm.operation.completed` on `OOP_EVENTS`, which SRM consumes and republishes as `event.srm.operation.completed`. A second channel is unavoidable because OPG.04 `InstallApp` returns only `202` plus a later callback, and `RemoveApp` returns `200` with no callback at all; and FM cannot call back into SRM over HTTP because SRM exposes no HTTP lifecycle mutation endpoints. ADR-0052 also amends the SRM hand-off flow so SRM writes a `service_order` row (the idempotency anchor) while still writing no `service_instance` rows, and sets FM's deploy-callback deadline at a 30-minute 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).

---

+106 −12
Changes for fm/requirements-and-design.md: 106 added lines, 12 removed lines.
Original line number Diff line number Diff line
@@ -503,31 +503,86 @@ from L.5, plus:
|---|---|---|
| `no-route` | 404 | No routing rule matches `identifier_value`; no partner could be resolved |

### L.3a SRM → FM Internal HTTP (Federated Deploy Hand-off, ADR-0044)
### L.3a SRM → FM Internal HTTP (Federated Lifecycle Hand-off, ADR-0044, ADR-0052)

For zone-based deployment targeting, SRM — not OEG — determines locality and, when the target
zone is federated, hands off to FM. This is new surface introduced by ADR-0044; unlike L.3, exact
request/response contract, retry, and timeout semantics are an **open item** ([fm/overview-and-architecture.md §G4](/fm/overview-and-architecture.md)) — the shapes below are indicative, not final.
For zone-based deployment targeting, SRM — not OEG — determines locality and hands off to FM when
the work belongs to a partner. ADR-0044 established the ownership; ADR-0052 fixes the transport:
a synchronous internal HTTP request here, and an asynchronous `event.fm.operation.completed` on
`OOP_EVENTS` for the result (§M.1). The two hops exist because OPG.04 returns no outcome on the
request: `InstallApp` answers `202` and reports later via callback, and `RemoveApp` answers `200`
with no callback at all.

| Method | Path | Description |
|---|---|---|
| `GET` | `/internal/federated-zones/{zone_id}` | Look up a zone in `shared_resource_catalogues`; `404` if the zone is not federated with any partner |
| `POST` | `/internal/federation/deploy` | Hand off a deploy whose target zone resolved as federated; FM maps the request to OPG.04 `InstallApp` and executes the outbound EWBI call |
| `GET` | `/internal/federated-zones/{zone_id}` | Resolve a zone against `shared_resource_catalogues`; `404` if the zone is federated with no partner |
| `POST` | `/internal/federation/deploy` | Hand off a deploy whose target zone resolved as federated; FM maps it to OPG.04 `InstallApp` |
| `POST` | `/internal/federation/terminate` | Hand off a terminate for an instance absent from SRM inventory; FM maps it to OPG.04 `RemoveApp` |

`GET /internal/federated-zones/{zone_id}` response (200):
Both `POST` bodies are the unmodified `SrmServiceDeployV1` / `SrmServiceTerminateV1` SRM already
received from OEG. SRM performs no GSMA/EWBI shaping — FM alone owns that mapping. FM processes both
through the same fulfillment pipeline as UC-FM-01 (inbound federation-originated requests), so
behaviour does not depend on origin.

#### `GET /internal/federated-zones/{zone_id}`

Response `200`:

```json
{
  "zone_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
  "partner_op_id": "uuid",
  "partner_op_id": "3f6c1d7e-9a52-4b08-b6d2-2e4f8a913c55",
  "supported_apis": ["edge-application-management"]
}
```

`POST /internal/federation/deploy` reuses the same `SrmServiceDeployV1` shape SRM already receives
from OEG, so SRM does not need to re-shape the payload for this call — FM alone owns the mapping
to OPG.04 `InstallApp`. FM processes this deploy through the same fulfillment pipeline as UC-FM-01
(inbound federation-originated deploys), so behavior is identical regardless of origin.
`404` carries the `zone-not-federated` problem type. SRM treats it as an ordinary placement failure —
the same path as an unresolvable local zone — not as an FM error.

#### `POST /internal/federation/deploy` and `POST /internal/federation/terminate`

Response `202` — the hand-off is accepted and FM now owns the partner exchange:

```json
{
  "operation_id": "8c1f0e2a-77b4-4f5e-9a10-5c3d2b9e4f61",
  "federation_transaction_id": "b2d4f6a8-1c3e-4a5b-8d7f-0e9c2a4b6d81",
  "partner_op_id": "3f6c1d7e-9a52-4b08-b6d2-2e4f8a913c55",
  "accepted_at": "2026-09-22T09:14:03Z"
}
```

`operation_id` is echoed from the envelope and is the key SRM correlates the later
`event.fm.operation.completed` on. `federation_transaction_id` is FM's audit handle, for operator
troubleshooting only — SRM stores it opaquely and never interprets it.

**Idempotency.** `operation_id` is the key. A repeat carrying an `operation_id` FM has already
accepted returns `202` with the original body and starts no second EWBI exchange. SRM may therefore
retry freely; retry ownership for this hop is SRM's, inside its own async `command.srm.*` handling
(ADR-0052).

**Instance resolution on terminate.** SRM does not look the instance up first. It finds the
`service_instance_id` absent from its local inventory, posts the terminate, and FM resolves the
instance from its own `federation_transactions` records — matching on the stored
`external_resource_id`, which holds the `appInstIdentifier` in its wire spelling (hex, since
OPG.04's `InstanceIdentifier` forbids hyphens). A `404` means the instance is unknown to both
components.

**Errors.** RFC 7807, reusing `partner-not-active`, `agreement-violation` and `agreement-expired`
from §L.5, plus:

| Type Suffix | HTTP Status | Meaning |
|---|---|---|
| `zone-not-federated` | 404 | No partner offers this zone (`GET /internal/federated-zones/{zone_id}`) |
| `instance-not-federated` | 404 | No federation transaction matches this `service_instance_id` (terminate) |
| `zone-not-permitted` | 403 | The zone is catalogued but the governing agreement does not permit deployment to it |
| `partner-unreachable` | 502 | The partner's EWBI endpoint could not be reached; SRM may retry |

A `502` is the one error SRM should retry on. The `4xx` types are caller-correctable or terminal and
SRM completes the operation as failed without re-posting.

**What FM does not return here.** No instance state, no partner topology, no `federationContextId`.
The outcome arrives only on `event.fm.operation.completed`; the topology-hiding rules that govern
every other FM response (REQ-FED-13) apply to that event too.

### L.3b OEG → FM Internal HTTP (Federation-Lifecycle Proxy, ADR-0051)

@@ -672,6 +727,45 @@ contract comparison silently.
| `command.srm.service.terminate` | OOP_TASKS | `SrmServiceTerminateV1` | OPG.04 `RemoveApp` authenticated and agreement-checked |
| `command.srm.network.capability.activate` | OOP_TASKS | `SrmNetworkCapabilityActivateV1` | Inbound EWBI network capability request |
| `command.srm.network.capability.deactivate` | OOP_TASKS | `SrmNetworkCapabilityDeactivateV1` | Inbound EWBI network capability release |
| `event.fm.operation.completed` | OOP_EVENTS | `FmOperationCompletedV1` | Terminal outcome of an **outbound** federated deploy or terminate handed off by SRM (§L.3a, ADR-0052) |

`event.fm.operation.completed` is the result hop of the SRM→FM hand-off. It exists because OPG.04
returns no outcome on the request itself, and because SRM exposes no HTTP lifecycle mutation
endpoint for FM to call back into (SRM interface contract §A). SRM consumes it and republishes
`event.srm.operation.completed`, so OEG and the App Provider see one uniform terminal event whether
fulfillment was local or federated.

**`FmOperationCompletedV1`**

| Field | Type | Required | Description |
|---|---|---|---|
| `schema_version` | string | Yes | `"1.0"` until a breaking change |
| `operation_id` | UUID | Yes | Echoed from the originating `command.srm.*` envelope; SRM's correlation key |
| `correlation_id` | string | Yes | Tracing key, echoed from the envelope |
| `operation` | enum | Yes | `deploy` \| `terminate` |
| `status` | enum | Yes | `completed` \| `failed` |
| `partner_op_id` | UUID | Yes | Which partner fulfilled it |
| `federation_transaction_id` | UUID | Yes | FM's audit handle, opaque to SRM |
| `service_instance_id` | UUID | Cond. | The caller-facing `app_instance_id`; required when `operation=deploy` and `status=completed` |
| `external_ref` | string or null | No | The partner's `appInstIdentifier` in its wire spelling; informational, for reconciliation and logs |
| `metadata` | object | Yes | Carries `completion_basis` — see below |
| `error` | RFC 7807 object or null | Cond. | Required when `status=failed` |
| `completed_at` | date-time | Yes | ISO-8601 UTC |

**`metadata.completion_basis`** states what "completed" is grounded in, because OPG.04 gives the two
operations different guarantees:

| Value | Emitted for | Means |
|---|---|---|
| `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 |
| `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
accepted but never performed belongs to reconciliation (ADR-0052).

**Topology hiding applies to this event** (REQ-FED-13). `external_ref` is a partner-issued handle
and is safe; partner zone identifiers, endpoints and internal references are not carried.

The charging integration required by REQ-FED-18 needs its own contract/ADR; no DataBus subject or
"one record after every transaction" policy is defined yet.
+50 −13
Changes for srm/interface-contract.md: 50 added lines, 13 removed lines.
Original line number Diff line number Diff line
@@ -513,6 +513,7 @@ Stream `OOP_TASKS`; Filter mutating `command.srm.*`; Deliver All; Ack **Explicit
| `event.srm.catalog.updated` | v1 | SRM | OEG, FM | Durable, at-least-once | Catalog cache invalidation; FM partner catalog re-sync |
| `event.srm.inventory.drift_detected` | v1 | SRM | OEG, portal | Durable, at-least-once | Drift alert |
| `event.srm.inventory.drift_resolved` | v1 | SRM | OEG, portal | Durable, at-least-once | Drift cleared |
| `event.fm.operation.completed` | `FmOperationCompletedV1` | FM | SRM | Durable, at-least-once | Terminal outcome of an outbound federated deploy/terminate (§F.4a, ADR-0052) |

FM does not publish `command.srm.service.scale` or `command.srm.network.capability.update`: no
OPG.04 v1.4.0 EWBI operation maps to the former, and the latter needs an explicit OPG.04
@@ -847,10 +848,14 @@ Same as F.1; FM publishes with `source=federation` and `federation_partner_ref`,
events, strips operator-internal topology before returning upstream. This is the **inbound** case
— a partner OP's request that already targets our own infrastructure.

### F.4a Federated deploy hand-off (SRM → FM, ADR-0044)
### F.4a Federated lifecycle hand-off (SRM → FM, ADR-0044, ADR-0052)

The **outbound** case: OEG publishes `command.srm.service.deploy` as in F.1, with no locality
information — SRM decides.
The **outbound** case: OEG publishes `command.srm.service.deploy` or `.terminate` as in F.1, with no
locality information — SRM decides. ADR-0044 gave SRM that decision; ADR-0052 fixes the hand-off as
a synchronous internal HTTP request plus an asynchronous result event, because OPG.04 returns no
outcome on the request itself.

**Deploy:**

1. SRM looks up the target `zone_id` in its own zone inventory (F.1 step 5, "selects zone/domain
   when not pinned") — a direct lookup, not the candidate-probing mechanism §B describes for
@@ -859,16 +864,48 @@ information — SRM decides.
   `404` (zone unknown anywhere) is treated as a normal placement failure — same error path as an
   unresolvable local zone.
3. Zone found as federated: SRM calls FM's `POST /internal/federation/deploy` with the same
   `SrmServiceDeployV1` payload it received — no GSMA/EWBI shaping in SRM. FM executes the
   outbound EWBI handshake with the partner's FM and reports back.
4. SRM completes the original command's lifecycle from FM's result — publishes
   `event.srm.operation.completed` as usual. No local `service_order`/`service_instance` rows are
   written for the federated portion; SRM's own inventory records only local fulfillment.

**Open item:** exact request/response contract, retry, and timeout semantics for step 3 are not
yet finalized — see [fm/overview-and-architecture.md §G4](/fm/overview-and-architecture.md).
Scope: zone-based deployment targeting only. Interim layer pending a future runtime-coordinator
component (platform/future-architecture.md §6.1, ADR-0044).
   `SrmServiceDeployV1` payload it received — no GSMA/EWBI shaping in SRM. FM answers `202` and
   takes ownership of the partner exchange.

**Terminate:**

1. SRM looks up `service_instance_id` in its own inventory. Not found locally.
2. No separate lookup step: SRM calls FM's `POST /internal/federation/terminate` with the
   `SrmServiceTerminateV1` payload it received. FM resolves the instance from its own federation
   transaction records and answers `202`, or `404` if the instance is unknown to both components —
   an ordinary not-found failure.

**Completion, both operations:**

SRM consumes `event.fm.operation.completed` from `OOP_EVENTS`, correlating on `operation_id`, and
completes the original command's lifecycle by publishing `event.srm.operation.completed` as usual.
Callers see one uniform terminal event regardless of where fulfillment happened.

The event's `metadata.completion_basis` states what the outcome is grounded in and must be carried
through rather than flattened: `partner_confirmed` (the partner's deploy callback reported the
instance running), `partner_accepted` (the partner accepted a termination — OPG.04 defines no
callback for `RemoveApp` and no `TERMINATED` state, so removal is *not* confirmed), or
`deadline_expired` (no deploy callback arrived within FM's configured wait).

**Record ownership (amended by ADR-0052).** SRM **writes a `service_order` row** for federated
deploys and terminates, as it does for every command it accepts: it is the `UNIQUE(operation_id)`
idempotency anchor (§D.4) and the record that lets SRM correlate a late
`event.fm.operation.completed` after a restart. SRM **writes no `service_instance` rows** — a
federated instance runs on the partner's infrastructure, SRM's inventory exists to serve placement,
best-effort capacity queries and drift reconciliation, and an instance SRM cannot observe would
produce permanent false drift. (`service_instance.zone_id` is also a non-null foreign key into
SRM's `zone` table, and a federated zone is by definition absent from it.) The caller-facing record
is unaffected: OEG maintains its own `app_instances` rows and answers App Provider reads from
`oeg_db`.

**Retry ownership.** Hop 1 (the `POST`) is SRM's, retried inside its own async `command.srm.*`
handling; `operation_id` makes the call idempotent at FM, and only a `502 partner-unreachable`
warrants re-posting. Everything past the `202` — the partner handshake, the callback wait and its
deadline, publishing the terminal event exactly once — is FM's.

Scope: zone-based deployment targeting and the termination of instances placed that way. Interim
layer pending a future runtime-coordinator component (platform/future-architecture.md §6.1,
ADR-0044).

---