Commit 9294ce58 authored by Paris Stentoumis's avatar Paris Stentoumis
Browse files

docs: align TI activate and status metadata with seeded-spec pattern

Amend ADR-0035 so Traffic Influence uses a platform-seeded service specification (app via application_reference), and rename qos_status metadata keys to capability_status across ADR-0036 and SRM/OEG contracts.
parent 23c2f509
Loading
Loading
Loading
Loading
+29 −2
Changes for decisions/0035-qod-session-service-specification.md: 29 added lines, 2 removed lines.
Original line number Diff line number Diff line
@@ -147,8 +147,35 @@ Deferred or excluded:

- `/qos-profiles` and `/qos-profiles/{name}` remain unimplemented.
- `api_subscriptions` is not populated for QoD; per-tenant entitlement is not enforced.
- Traffic Influence is unaffected and out of scope here — it resolves `service_specification_id`
  from its required `appId` via `app_registration_id` (ADR-0011) and needs no seeded row.

## Amendment (2026-10-20) — Traffic Influence uses the same seeded-spec pattern

**Status:** accepted
**Date:** 2026-10-20

Traffic Influence also activates through `command.srm.network.capability.activate` and needs a
`service_specification_id` that references a real catalog row. Unlike QoD, CAMARA TI carries a
required `appId` — but that id identifies the **edge application** whose traffic is influenced,
not the TI capability offering itself. Using the app's `app_registration_id` as
`service_specification_id` would attach the session holder to a workload specification that has no
`traffic_influence` requirement, so planning would fail.

The platform therefore seeds **one** Traffic Influence service specification alongside the QoD
one, with the same ownership rules (platform provider, empty deployment units, one
`capability_kind=traffic_influence` network requirement). OEG holds the id in
`ti_settings.service_specification_id` / `TI_SETTINGS__SERVICE_SPECIFICATION_ID` (well-known
default `baba6a12-c7b6-507b-9805-5755d38131ee`, from `well_known_service_specification_id("traffic-influence")`)
and sends it on every TI activate.

The onboarded app is identified separately through
`network_capability.target.application_reference` (`service_specification_ref` /
`external_app_id` = CAMARA `appId`, optional `app_instance_id`). SRM resolves that reference to an
active workload instance and fills `target.application_server` before control-path resolution.
OEG still resolves `appId` → `app_registration_id` for its own `traffic_influences` bookkeeping
and for notification `selected_appInstanceId` scoping; that value is **not** the activate
command's `service_specification_id`.

This supersedes the earlier note that TI was out of scope for this ADR and "needs no seeded row".

Revisit trigger:

+9 −3
Changes for decisions/0036-srm-owns-network-capability-expiry.md: 9 added lines, 3 removed lines.
Original line number Diff line number Diff line
@@ -55,18 +55,18 @@ with the operational `state` and the reason in `metadata`:
  "operation_id": "<the activate operation_id>",
  "state": "completed",
  "capability": "qod_session",
  "metadata": { "qos_status": "UNAVAILABLE", "qos_status_info": "duration_expired" },
  "metadata": { "capability_status": "UNAVAILABLE", "capability_status_info": "duration_expired" },
  "correlation_id": "…",
  "emitted_at": "2026-06-19T13:05:00Z"
}
```

`qos_status_info` is a new, optional metadata key distinguishing **why** a capability became
`capability_status_info` is an optional metadata key distinguishing **why** a capability became
unavailable. Defined values are `duration_expired` and `network_terminated`. It is metadata, never
`state` — backend and API-family vocabulary does not enter SRM's operational enum. When absent,
consumers treat the change as `network_terminated`, which preserves today's behaviour.

**OEG's mapping.** OEG maps `metadata.qos_status_info` to CAMARA's `statusInfo` on the
**OEG's mapping.** OEG maps `metadata.capability_status_info` to CAMARA's `statusInfo` on the
`QOS_STATUS_CHANGED` notification: `duration_expired` → `DURATION_EXPIRED`,
`network_terminated` → `NETWORK_TERMINATED`. The third CAMARA value, `DELETE_REQUESTED`, is
OEG-generated on the delete path and never arrives from SRM (ADR-0034).
@@ -116,3 +116,9 @@ Revisit trigger:

- A CAMARA release that specifies expiry precision, or an API family whose expiry semantics differ
  from "terminate and report" — for example one requiring auto-renewal.

## Amendment (2026-10-21) — rename metadata keys

The status-event metadata keys are renamed from `qos_status` / `qos_status_info` to
`capability_status` / `capability_status_info`. Semantics and values are unchanged; the keys
apply to async network capabilities generally (QoD and Traffic Influence), not QoD alone.
+2 −2
Changes for decisions/index.md: 2 added lines, 2 removed lines.
Original line number Diff line number Diff line
@@ -37,8 +37,8 @@ the reasoning lives here. Never renumber; supersede instead (see the record-deci
* [ADR-0032](0032-databus-validation-failure-split-by-answerability.md) - DataBus validation failures split by answerability: dead-letter only unattributable messages; every readable-envelope failure returns failed_before_start
* [ADR-0033](0033-async-network-capability-caller-id.md) - async network-capability caller-facing ids (QoD sessionId, trafficInfluenceID) are OEG-minted and adopted as service_instance.id; external_ref reverts to the realization ref
* [ADR-0034](0034-network-capability-delete-is-async.md) - network-capability delete is async via command.srm.network.capability.deactivate, and its completion is consumed to a terminal record state
* [ADR-0035](0035-qod-session-service-specification.md) - a QoD session references one platform-seeded service specification held in OEG config; profile names stay unvalidated, and QoS profiles are a catalog-layer concern (resolves F2)
* [ADR-0036](0036-srm-owns-network-capability-expiry.md) - SRM owns expiry of async network capabilities and reports it via event.srm.operation.status metadata (qos_status_info); OEG runs no timer and maps it to CAMARA DURATION_EXPIRED
* [ADR-0035](0035-qod-session-service-specification.md) - a QoD session references one platform-seeded service specification held in OEG config; profile names stay unvalidated, and QoS profiles are a catalog-layer concern (resolves F2); amended 2026-10-20 so Traffic Influence uses the same seeded-spec pattern (app via `application_reference`)
* [ADR-0036](0036-srm-owns-network-capability-expiry.md) - SRM owns expiry of async network capabilities and reports it via event.srm.operation.status metadata (capability_status_info); OEG runs no timer and maps it to CAMARA DURATION_EXPIRED
* [ADR-0037](0037-canonical-target-ports.md) - target.device.ports / target.application_server.ports join srm.params/v1's canonical target descriptor, generic across every capability with a device/application-server target rather than allowlisted per capability_type
* [ADR-0038](0038-device-targeted-control-path-resolution.md) - device-targeted capabilities resolve their control path by probing candidate adapters with can_serve_target; the loop and the choice stay in SRM, the per-backend answer comes from TF-SDK
* [ADR-0039](0039-source-spec-stated-by-publisher.md) - source_spec is stated by the publishing component (OEG from CAMARA, FM from OPG.04) on network capability commands; SRM never derives it from the envelope source and never synthesizes a family, API, or version
+1 −1
Changes for oeg/interface-contract.md: 1 added line, 1 removed line.
Original line number Diff line number Diff line
@@ -323,7 +323,7 @@ This section describes the async messages OEG sends and receives.
| Subject | Action |
|---|---|
| `event.srm.operation.completed` | Read `instances[]`; update `operations` and the pre-created row(s) — `app_instances` (single for `/appinstances`, array for `/deployments`, ADR-0005) or `qod_sessions` / `traffic_influences` (single, ADR-0033). For a **deactivate** operation the target record is resolved from the operation's own `metadata`, and the record moves to a terminal state (ADR-0034). Trigger callback(s) |
| `event.srm.operation.status` | Update in-flight/backend-driven state; map backend capability state changes to CAMARA notifications. For async network capabilities, `metadata.qos_status` gives the new condition and `metadata.qos_status_info` the reason — `duration_expired` → CAMARA `DURATION_EXPIRED`, `network_terminated` (or absent) → `NETWORK_TERMINATED` (ADR-0036). Expiry is SRM-owned; OEG runs no timer. Events carrying no state this family recognizes are ignored without warning — the subject is shared platform-wide |
| `event.srm.operation.status` | Update in-flight/backend-driven state; map backend capability state changes to CAMARA notifications. For async network capabilities, `metadata.capability_status` gives the new condition and `metadata.capability_status_info` the reason — `duration_expired` → CAMARA `DURATION_EXPIRED`, `network_terminated` (or absent) → `NETWORK_TERMINATED` (ADR-0036). Expiry is SRM-owned; OEG runs no timer. Events carrying no state this family recognizes are ignored without warning — the subject is shared platform-wide |
| `event.srm.catalog.updated` | Invalidate `catalog_cache_entries` or Redis keys |
| `event.srm.inventory.drift_detected` / `event.srm.inventory.drift_resolved` | Log and emit metrics; optional Portal notification later |

+4 −4
Changes for oeg/persistence-model.md: 4 added lines, 4 removed lines.
Original line number Diff line number Diff line
@@ -72,10 +72,10 @@ A metering record for either synchronous or asynchronous API usage. Stored in `a
The retry and delivery history for outbound webhook notifications. Stored in `callback_deliveries`.

**QodSession**  
The OEG-side record of a CAMARA QoD session (`POST /sessions`). `session_id` is **OEG-minted at request time and is the value SRM adopts as the session-holder `service_instance.id`** (ADR-0033) — the same mint-and-adopt relationship `AppInstance` has with `appInstanceId` (ADR-0005), so the CAMARA `sessionId`, OEG's primary key, and SRM's runtime root id are one value. The row is written eagerly (`state=REQUESTED`) alongside the `command.srm.network.capability.activate` publish (`capability_type=qod_session`). `state` transitions `REQUESTED` → `AVAILABLE`|`UNAVAILABLE` when that operation's `event.srm.operation.completed` is consumed, and again on backend-driven `event.srm.operation.status` (§L.2) — including **expiry**, which SRM owns and reports with `metadata.qos_status_info=duration_expired` (ADR-0036). OEG runs no timer over this table: `duration_seconds` is stored for the request record and for serving `expiresAt`, never to schedule work. `DELETE /sessions/{id}` publishes a second, distinct `command.srm.network.capability.deactivate` operation keyed on the same id, moves the row to `DELETION_REQUESTED`, and drives it to `DELETED`/`ERROR` when that operation completes (ADR-0034). On an `AVAILABLE` session, OEG also emits a `qos-status-changed` notification (`qosStatus=UNAVAILABLE`, `statusInfo=DELETE_REQUESTED`). Not a duplicate of SRM inventory: the row carries identity, ownership and cached status; capability detail (canonical parameters and result) is read live from `GET /internal/network-capabilities/{service_instance_id}`, the same live-read split `AppInstance` uses. Stored in `qod_sessions`.
The OEG-side record of a CAMARA QoD session (`POST /sessions`). `session_id` is **OEG-minted at request time and is the value SRM adopts as the session-holder `service_instance.id`** (ADR-0033) — the same mint-and-adopt relationship `AppInstance` has with `appInstanceId` (ADR-0005), so the CAMARA `sessionId`, OEG's primary key, and SRM's runtime root id are one value. The row is written eagerly (`state=REQUESTED`) alongside the `command.srm.network.capability.activate` publish (`capability_type=qod_session`). `state` transitions `REQUESTED` → `AVAILABLE`|`UNAVAILABLE` when that operation's `event.srm.operation.completed` is consumed, and again on backend-driven `event.srm.operation.status` (§L.2) — including **expiry**, which SRM owns and reports with `metadata.capability_status_info=duration_expired` (ADR-0036). OEG runs no timer over this table: `duration_seconds` is stored for the request record and for serving `expiresAt`, never to schedule work. `DELETE /sessions/{id}` publishes a second, distinct `command.srm.network.capability.deactivate` operation keyed on the same id, moves the row to `DELETION_REQUESTED`, and drives it to `DELETED`/`ERROR` when that operation completes (ADR-0034). On an `AVAILABLE` session, OEG also emits a `qos-status-changed` notification (`qosStatus=UNAVAILABLE`, `statusInfo=DELETE_REQUESTED`). Not a duplicate of SRM inventory: the row carries identity, ownership and cached status; capability detail (canonical parameters and result) is read live from `GET /internal/network-capabilities/{service_instance_id}`, the same live-read split `AppInstance` uses. Stored in `qod_sessions`.

**TrafficInfluence**  
The OEG-side record of a CAMARA Traffic Influence policy (`POST /traffic-influences`). Structurally identical to `QodSession`: `traffic_influence_id` is OEG-minted, adopted by SRM as `service_instance.id` (ADR-0033), written eagerly (`state=ordered`) alongside the activate publish (`capability_type=traffic_influence`), advanced by the completion and status events, and torn down by a second deactivate operation (ADR-0034). It differs in one respect — its `service_specification_id` is **resolved, not minted**: a Traffic Influence request always targets an already-registered `appId`, which OEG resolves to the owning `app_registration_id` (= `service_specification_id`, ADR-0011 — note `app_id` itself is **not** the specification id). Stored in `traffic_influences`.
The OEG-side record of a CAMARA Traffic Influence policy (`POST /traffic-influences`). Structurally identical to `QodSession`: `traffic_influence_id` is OEG-minted, adopted by SRM as `service_instance.id` (ADR-0033), written eagerly (`state=ordered`) alongside the activate publish (`capability_type=traffic_influence`), advanced by the completion and status events, and torn down by a second deactivate operation (ADR-0034). The activate command's `service_specification_id` is the **platform-seeded** Traffic Influence specification held in OEG config (ADR-0035 amendment), the same pattern as QoD. The request's required `appId` is still resolved to `app_registration_id` for this row's bookkeeping and for notification scoping, and is forwarded to SRM as `application_reference` so SRM can pick the workload instance — it is **not** the command's `service_specification_id`. Stored in `traffic_influences`.

**CatalogCacheEntry**  
An optional cached fragment of catalog data with TTL or version information. Lives in Redis/in-process TTL cache **only** — never a PostgreSQL table (persistent storage risks stale reads; see "What is intentionally not persisted" below).
@@ -337,13 +337,13 @@ Indexes: `BTREE(operation_id)`, `BTREE(tenant_id)`.
> Who may call QoD is the `quality-on-demand` product grant (ADR-0048). Neither step changes the
> command shape, this table, or the CAMARA surface.

**`traffic_influences`** — the OEG-side record for `POST /traffic-influences`. Same shape and lifecycle as `qod_sessions`: `traffic_influence_id` is OEG-minted and adopted by SRM as `service_instance.id` (ADR-0033), the row is written at request time, advanced by the activate completion, and driven to a terminal state by the deactivate completion (ADR-0034). It needs no seeded specification (ADR-0035): its `service_specification_id` is resolved from the request's required `appId` via `app_registrations.app_registration_id` (ADR-0011).
**`traffic_influences`** — the OEG-side record for `POST /traffic-influences`. Same shape and lifecycle as `qod_sessions`: `traffic_influence_id` is OEG-minted and adopted by SRM as `service_instance.id` (ADR-0033), the row is written at request time, advanced by the activate completion, and driven to a terminal state by the deactivate completion (ADR-0034). The activate command uses the platform-seeded TI specification in OEG config (ADR-0035 amendment); `app_registration_id` on this row is resolved from `appId` for ownership and notification scoping only.

| Column | Type | Notes |
|---|---|---|
| `traffic_influence_id` | UUID PK | OEG-minted; the CAMARA `trafficInfluenceID` **and** SRM's `service_instance.id` |
| `operation_id` | UUID FK -> `operations` | The **activate** operation that created this row |
| `app_registration_id` | UUID FK -> `app_registrations` | Resolved from the request's CAMARA `appId`; supplies `service_specification_id` on the command (ADR-0011) |
| `app_registration_id` | UUID FK -> `app_registrations` | Resolved from the request's CAMARA `appId` (ADR-0011); OEG bookkeeping / `selected_appInstanceId` scoping — **not** the activate command's `service_specification_id` |
| `edge_cloud_zone_id` | UUID | NULLABLE; the zone OEG pins on the activate command (`zone_id`): the requested CAMARA `edgeCloudZoneId`, else the zone resolved from `edgeCloudRegion` or `appInstanceId` (an `edgeCloudRegion` with no zones, or an `appInstanceId` SRM cannot resolve, is rejected `404`, never sent unpinned). SRM realizes the policy only inside a pinned zone (ADR-0045), so this is also the policy's zone; it scopes the `ready` `app_instances` reported as `selected_appInstanceId` on the `active` notification (only the requested one when `appInstanceId` was given). NULL when the request names no location — SRM then chooses the control path and no instance is reported as selected |
| `edge_cloud_region` | VARCHAR(256) | NULLABLE; the requested CAMARA `edgeCloudRegion`, resolved to a zone before the command is built |
| `source_port` | INTEGER | NULLABLE; from `sourceTrafficFilters.sourcePort` |
Loading