Commit f46aef91 authored by Sergio Gimenez's avatar Sergio Gimenez
Browse files

Merge branch 'develop' into fm-persistence-model

parents a35be215 c89c2e37
Loading
Loading
Loading
Loading
+7 −4
Changes for decisions/0034-network-capability-delete-is-async.md: 7 added lines, 4 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
**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
**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)

@@ -64,9 +64,12 @@ QoD. Because step 3 moves the record off that state synchronously, a repeat `DEL
idempotent — it neither re-publishes nor re-notifies. For QoD this satisfies the spec rule that no
event is sent when the status was already `UNAVAILABLE`.

**Read behaviour after deletion.** CAMARA has no representation for a deleted session or policy,
so reads of a terminal record return `404`. The row is retained for audit and for the
`operations` history; it is not removed.
**Read behaviour after deletion.** Reads of a terminal record return `404` where CAMARA has no
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
retained for audit and for the `operations` history; it is not removed.

## Consequences

+1 −1
Changes for decisions/0038-device-targeted-control-path-resolution.md: 1 added line, 1 removed line.
Original line number Diff line number Diff line
@@ -9,7 +9,7 @@ status: "accepted"

# ADR-0038: Control-path resolution for device-targeted capabilities is SRM's, via an adapter probe

**Status:** accepted — the boolean return type is superseded by [ADR-0040](/decisions/0040-target-serveability-is-a-tri-state.md); everything else stands
**Status:** accepted — the boolean return type is superseded by [ADR-0040](/decisions/0040-target-serveability-is-a-tri-state.md), and the scoping to requests "with no `zone_id`/`domain_id`" is amended by [ADR-0045](/decisions/0045-pin-narrows-candidates-not-probing.md) (a pin narrows the candidate set rather than bypassing the probe); everything else stands
**Date:** 2026-08-26
**Affects:** [srm/interface-contract.md](/srm/interface-contract.md), [platform/architecture.md](/platform/architecture.md), extends [ADR-0027](/decisions/0027-tfsdk-generic-capability-adapter-interface.md)

+105 −0
Changes for decisions/0045-pin-narrows-candidates-not-probing.md: 105 added lines, 0 removed lines.
Original line number Diff line number Diff line
---
type: Decision Record
title: "ADR-0045: A zone or domain pin narrows the candidate set for device-targeted capabilities; it does not replace target probing"
description: "A pin constrains where a target may be served from, never who the target is; probing is triggered by how many candidates survive the pin, not by whether a pin was supplied. Amends ADR-0038's scoping to unpinned requests."
tags: [adr]
timestamp: 2026-09-08T00:00:00Z
status: "accepted"
---

# ADR-0045: A zone or domain pin narrows the candidate set for device-targeted capabilities; it does not replace target probing

**Status:** accepted
**Date:** 2026-09-08
**Affects:** [srm/interface-contract.md](/srm/interface-contract.md) §B, amends [ADR-0038](/decisions/0038-device-targeted-control-path-resolution.md), builds on [ADR-0040](/decisions/0040-target-serveability-is-a-tri-state.md)

## Context

[ADR-0038](/decisions/0038-device-targeted-control-path-resolution.md) opens its decision with
*"For any device-targeted capability **with no `zone_id`/`domain_id`**"*, and
[srm/interface-contract.md](/srm/interface-contract.md) §B mirrors that: *"**Neither pin present**
resolves by target probing below."* Pinned requests were sent instead to deploy §B.2's placement
rules, whose zone-only clause reads *"pinned to zone; SRM selects a domain in it."*

That clause is correct for deploy and wrong here. Deploying an app into a zone, any compute domain
in that zone that satisfies the requirement will do — the domains are interchangeable for the
purpose. A device-targeted capability is not like that: the domains are two different mobile cores,
and exactly one of them holds the subscriber.

A zone pin therefore does **not** remove the ambiguity ADR-0038 exists to solve. `capability.domain_id`
is NOT NULL, so two NEFs on one site are two `domain` rows carrying one capability row each
([srm/persistence-cross-standards-view.md](/srm/persistence-cross-standards-view.md)). A request
pinned to that zone still has two candidates and, under the inherited rule, no stated basis for
choosing between them.

The SRM implementation resolved the tie with `sorted(domains, key=domain.ref)` — alphabetical order
on an internal slug that [ADR-0028](/decisions/0028-topology-ref-fields-internal-only.md) defines as
internal-only, for logs and bootstrap YAML. Renaming `nef-4g` changed which core served a
subscriber. Worse, the wrong choice is silent: no error is raised, and the request succeeds against
a core that has never heard of that subscriber.

Pins on these commands are legitimate input, not something to reject. §B.5 lists `domain_id` as an
*"explicit network-domain pin (trusted internal)"*. OEG never sets it — it holds a phone number, not
a place — but the operator portal and FM-relayed requests may, and an operator forcing a specific
NEF during an incident is a capability the contract deliberately kept.

Options considered:

- **(a) Keep the pin as a probe bypass**, and require operators to configure at most one network
  domain per zone per capability kind. Costs nothing today, but makes correctness a deployment
  property that nothing validates and no error reports — the failure mode stays silent.
- **(b) Reject pins outright on device-targeted capabilities** (`failed_before_start`). Simple, and
  consistent with "OEG never sends them". Rejected: it deletes the trusted-internal escape hatch
  §B.5 documents, so an operator debugging a live QoD problem could no longer force a NEF.
- **(c) Treat the pin as a filter** over ADR-0038's candidate set, then apply ADR-0038's existing
  candidate-count rule unchanged.

## Decision

Option (c). For every device-targeted capability (`qod_session`, `traffic_influence`,
`location_context`, `device_status`), whatever the pin:

1. **Candidates** are ADR-0038 step 1's candidates, additionally restricted to the pinned `zone_id`
   and/or `domain_id` when present.
2. **A `domain_id` without a `zone_id`** remains invalid ([ADR-0031](/decisions/0031-internal-field-names-mirror-srm-vocabulary.md)) — unchanged.
3. **Exactly one candidate** — use it, no probe.
4. **More than one** — probe per ADR-0038 step 3 and ADR-0040's tri-state.
5. **None** — ADR-0038 step 4's outcomes, unchanged.

The trigger for probing is **how many candidates survive**, not whether a pin was supplied. A pin
constrains *where* the target may be served from; it never asserts *who* the target is. ADR-0038's
scoping phrase "with no `zone_id`/`domain_id`" is amended accordingly; everything else in ADR-0038
stands.

## Consequences

What becomes easier:

- A zone-pinned request is correct when the zone holds two network domains, instead of
  correct-by-alphabet.
- `domain.ref` stops influencing routing, restoring ADR-0028's rule that `ref` never leaves the
  internal vocabulary.
- The operator escape hatch survives untouched: pinning zone **and** domain leaves exactly one
  candidate, so rule 3 selects it with no probe — the same outcome as before, reached by the
  general rule instead of a special case.
- One resolution path for every pin shape, so a pinned and an unpinned request cannot drift apart.

What becomes harder:

- Candidate listing must apply the pin. A candidate lister that ignores it would probe backends
  outside the pinned zone — a worse failure than the one being fixed, since it contacts partners
  the caller excluded.
- A zone-pinned request in a two-NEF zone now costs one probe it did not previously pay.

Deferred or excluded:

- **Deploy placement is unchanged.** §B.2 keeps its meaning for `kind=compute`; "SRM selects a
  domain in it" stays correct there, where domains satisfying the requirement are interchangeable.
- **Whether a lone candidate should be probed** — that is, failing when the single surviving
  adapter answers `unknown_subscriber` rather than acting blind — is not decided here. ADR-0038's
  "exactly one candidate, no probe" rule stands.

Revisit trigger:

- A deployment requires the single-candidate case to be verified against the backend before acting.
  At that point what reopens is ADR-0038's rule 3, not this record.
+115 −0
Changes for decisions/0047-terminal-record-read-follows-camara-schema.md: 115 added lines, 0 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"
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

**Status:** proposed
**Date:** 2026-09-09
**Affects:** amends [ADR-0034](/decisions/0034-network-capability-delete-is-async.md), [oeg/interface-contract.md](/oeg/interface-contract.md), [srm/interface-contract.md](/srm/interface-contract.md) §E.1

## Context

[ADR-0034](/decisions/0034-network-capability-delete-is-async.md) states:

> **Read behaviour after deletion.** CAMARA has no representation for a deleted session or
> policy, so reads of a terminal record return `404`.

The premise is a claim about the vendored CAMARA schemas, and it is true of only one of the two
families the sentence covers.

**CAMARA QoD** (`qod-api.yaml`, `info.version: 0.10.1`) — `QosStatus`:

```
['REQUESTED', 'AVAILABLE', 'UNAVAILABLE']
```

Nothing represents a deleted session. `404` is the only answer the schema permits, and ADR-0034
is correct for QoD.

**CAMARA Traffic Influence** (`traffic_influence.yaml`, `info.version: wip`) — `state`:

```
['ordered', 'created', 'active', 'error', 'deletion in progress', 'deleted']
```

with the enum's own description tying two of those values to the DELETE call:

> When the DELETE method is invoked the state is `'deletion in progress'`. After the resource is
> deleted (as a consequence of the previous invocation of the DELETE method) the state is
> `'deleted'`.

Under a blanket `404` those two values are unobservable: no caller could ever see a state the
specification defines and describes. The API also notifies `onTrafficInfluenceChanged` with the
full resource, so a subscriber is told `state: deleted` by callback while a read of the same
resource denies it exists.

ADR-0034 was written primarily about QoD and generalised the phrase "session or policy" without
checking Traffic Influence's enum.

A second, smaller instance of the same generalisation: ADR-0034 step 3 says `DELETE` "returns
`204`". CAMARA QoD documents `204`; CAMARA Traffic Influence documents `202` ("Deletion in
progress"). Both implementations already follow their own specification.

Options considered:

- **(a) Keep `404` uniformly.** Consistent across families and needs no change. Rejected: it
  contradicts the vendored Traffic Influence schema, which the platform treats as the governing
  contract for northbound shapes, and renders two documented enum values unreachable.
- **(b) Return `200` uniformly**, exposing a deleted representation for both. Rejected: QoD's
  schema has no value to put in `qosStatus`, so there is nothing to return.
- **(c) Let each family follow its own vendored schema.**

## Decision

Option (c). ADR-0034's read rule is scoped: **reads of a terminal record return `404` only for
families whose vendored CAMARA schema has no representation of a deleted resource.**

1. **QoD is unchanged.** `GET /sessions/{id}` on a terminal row returns `404`.
2. **Traffic Influence returns `200`** with the record's own `state` (`deletion in progress` or
   `deleted`) for `GET /traffic-influences/{trafficInfluenceID}`.
3. **The collection excludes deleted policies.** `GET /traffic-influences` omits `deleted` rows,
   matching SRM's `GET /network-capabilities` collection form, which excludes terminated and
   failed realizations. `error` and `deletion in progress` remain listed: both are
   still-present resources a caller may need to find in order to delete.
4. **SRM read failures are interpreted against the local state.** SRM holds a
   `capability_instance` only while the policy is realized, so a `404` from
   `GET /internal/network-capabilities/{id}` is expected before activation completes and after
   teardown starts, and is not an error. A `404` while OEG's record is `active` means SRM lost a
   live policy and surfaces. Transport failures always surface as `503`.
5. **`DELETE` status codes follow each family's specification** — `204` for QoD, `202` for
   Traffic Influence. ADR-0034 step 3's "`204`" is read as the QoD case, not a platform rule.

Everything else in ADR-0034 stands: delete is async, the deactivate completion is consumed, the
record reaches a terminal state, and repeat deletes neither republish nor re-notify.

## Consequences

What becomes easier:

- Traffic Influence's `deletion in progress` and `deleted` states become observable, as its
  specification describes, and the callback and the read agree.
- Each family's northbound behaviour is derivable from its vendored spec rather than from a
  cross-family rule that may not apply.

What becomes harder:

- The two families now differ in a way a reader may mistake for an inconsistency. Both call
  sites carry a comment naming this ADR and the enum difference that drives it.
- `GET` by id and the collection now disagree about deleted policies by design. This mirrors SRM's
  own collection semantics but is a rule someone must know rather than infer.

**Open question, not resolved here: retention.** A deleted Traffic Influence policy is readable by
id indefinitely, because nothing expires the row. CAMARA does not say how long a deleted resource
remains addressable. The row is retained for audit regardless (ADR-0034), so the question is only
how long it stays *readable*. Revisit when a data-retention policy exists for `oeg_db`, or if the
table's growth becomes material.

**Generalisation to future families.** Any new CAMARA family added to OEG must have this checked
against its own schema rather than inherited: if its resource representation carries a deleted
state, reads return it; if not, reads return `404`.
+2 −0
Changes for decisions/index.md: 2 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -47,3 +47,5 @@ the reasoning lives here. Never renumber; supersede instead (see the record-deci
* [ADR-0042](0042-ewbi-oauth2-client-credentials-mtls-deferred.md) - E/WBI authorization is OAuth2 Client-Credentials: local Keycloak for inbound, partner Authorization Server for outbound; mTLS deferred
* [ADR-0043](0043-oeg-proxied-federation-establishment.md) - operator-triggered federation establishment is proxied through OEG over the DataBus (command.fm.federation.*); FM also supports config-driven bootstrap federation
* [ADR-0044](0044-srm-owns-federated-zone-locality-and-hand-off.md) - SRM resolves Edge Cloud Zone locality for deployment targets and forwards non-local deploys to FM; interim layer pending a future runtime-coordinator component
* [ADR-0045](0045-pin-narrows-candidates-not-probing.md) - a zone/domain pin narrows the device-targeted candidate set rather than bypassing target probing; probing is triggered by how many candidates survive the pin (amends ADR-0038's unpinned scoping)
* [ADR-0047](0047-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)
Loading