Commit 6a0be1d6 authored by Dimitrios Gogos's avatar Dimitrios Gogos
Browse files

docs: amend ADR-0038 and add ADR-0045 to clarify pin behavior for device-targeted capabilities

parent 4ee40871
Loading
Loading
Loading
Loading
+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.
+1 −0
Changes for decisions/index.md: 1 added line, 0 removed lines.
Original line number Diff line number Diff line
@@ -47,3 +47,4 @@ 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)
+1 −1
Changes for platform/glossary.md: 1 added line, 1 removed line.
Original line number Diff line number Diff line
@@ -107,7 +107,7 @@ This is the single glossary for all Open Operator Platform (OOP) architecture do
| Term | Meaning |
|------|---------|
| **Compute Domain** | A concrete compute execution target inside a zone — for example, a specific Kubernetes cluster. Modeled as an SRM `domain` with `kind=compute`. SRM selects which compute domain to deploy to; TF-SDK's adapter talks to that domain's API. "Compute domain" is prose only — on contracts the field is `domain_id`, with the command subject fixing the kind (ADR-0031). |
| **Network Domain** | A concrete network execution target inside a zone — for example, a specific NEF. Modeled as an SRM `domain` with `kind=network`. SRM selects which network domain to use; TF-SDK's adapter calls that domain's API. "Network domain" is prose only — on contracts the field is `domain_id`, with the command subject fixing the kind (ADR-0031). For device-targeted capabilities with no `domain_id` pin, SRM selects among candidate network domains by probing their adapters with `can_serve_target` rather than by a stored pin (ADR-0038, ADR-0040). |
| **Network Domain** | A concrete network execution target inside a zone — for example, a specific NEF. Modeled as an SRM `domain` with `kind=network`. SRM selects which network domain to use; TF-SDK's adapter calls that domain's API. "Network domain" is prose only — on contracts the field is `domain_id`, with the command subject fixing the kind (ADR-0031). For device-targeted capabilities, SRM selects among candidate network domains by probing their adapters with `can_serve_target` rather than by a stored pin (ADR-0038, ADR-0040); a `zone_id`/`domain_id` pin narrows the candidates it probes rather than replacing the probe (ADR-0045). |
| **Zone (edge zone)** | A top-level scoping container inside a platform (SRM `zone`). A resource zone is the site-level container for compute and network domains. It stores no adapter config and no capacity; executors are reached via `control_path_ref`. On cross-component contracts, zone identity is always the zone UUID (`zone.id` = CAMARA `EdgeCloudZoneId`); the human-readable `zone.ref` is internal-only and never crosses a component boundary (ADR-0013; generalized to the rest of the topology chain by ADR-0028). "Resource zone" describes a `zone` with `kind=resource`; the contract field is always `zone_id` (ADR-0031). |
| **Compute Resource Specification** | The CPU, memory, GPU, and storage requirements for a service, held as the canonical `srm.compute/v1` object in `service_deployment_unit.resource_requirements`. SRM passes these to TF-SDK so the adapter can configure platform-specific resource limits (e.g., Kubernetes resource requests/limits). |
| **Service Specification** | The catalog blueprint for a deployable service, including artifact metadata (Helm chart reference, container image, deployment type). SRM resolves artifact identity from this; TF-SDK uses it to know what to deploy. |
+18 −9
Changes for srm/interface-contract.md: 18 added lines, 9 removed lines.
Original line number Diff line number Diff line
@@ -220,21 +220,29 @@ SRM writes the canonical Capability Request (`srm.params/v1`) with that value un
`parameters.profile_ref`, merged with `parameters` from the command. Adapters and JSONB columns
always see the canonical nesting; they never read the command-top-level field directly.

**Pin rules.** Same as deploy §B.2, except that `domain_id` here resolves to a domain with
`kind=network` rather than `kind=compute` — the subject fixes the kind, not the field name
**Pin rules.** Pin *shapes* are as deploy §B.2, except that `domain_id` here resolves to a domain
with `kind=network` rather than `kind=compute` — the subject fixes the kind, not the field name
(ADR-0031): zone-only, both (domain must belong to zone), domain-only → invalid
(`failed_before_start`, §A); null and omitted are equivalent. **Neither pin present** resolves by
target probing below; deploy §B.2's `placement_constraints` clause does not apply, as
`NetworkCapabilityPayloadV1` carries no such field.
(`failed_before_start`, §A); null and omitted are equivalent. Deploy §B.2's `placement_constraints`
clause does not apply, as `NetworkCapabilityPayloadV1` carries no such field.

What a pin *means* differs from deploy. Deploy §B.2's "pinned to zone; SRM selects a domain in it"
does not carry over: compute domains satisfying a requirement are interchangeable, two mobile cores
are not. Here **a pin narrows the candidate set and nothing more** — every request, pinned or not,
is resolved by target probing below ([ADR-0045](/decisions/0045-pin-narrows-candidates-not-probing.md)).
A pin constrains where the target may be served from; it never asserts who the target is.

**Resolving the control path from the target.** A CAMARA-originated request carries no pin — the
caller supplies a subscriber identifier, not a place. This rule covers every device-targeted
capability (`qod_session`, `traffic_influence`, `location_context`, `device_status`):
capability (`qod_session`, `traffic_influence`, `location_context`, `device_status`), pinned or not:

1. **Candidates** are the active `control_path_binding`s under every active `capability` whose
   `kind` matches the request and whose `supported_actions` covers the action. Two NEFs are two
   `domain` rows, so they yield two capabilities of the same kind — one candidate each.
2. **Exactly one candidate** — use it. No probe; single-NEF deployments pay nothing.
   `kind` matches the request and whose `supported_actions` covers the action, restricted to the
   pinned `zone_id` and/or `domain_id` when present (ADR-0045). Two NEFs are two `domain` rows, so
   they yield two capabilities of the same kind — one candidate each, and a zone pin covering both
   leaves both.
2. **Exactly one candidate** — use it. No probe; single-NEF deployments pay nothing, and a request
   pinned to zone *and* domain names exactly one candidate and so is never probed.
3. **More than one** — SRM calls `can_serve_target(request)` on each candidate's adapter in
   `is_default` then ascending `priority` order, and uses the first that answers `can_serve`.
   The answer is one of `can_serve`, `unknown_subscriber`, or `unsupported_identifier`
@@ -867,3 +875,4 @@ component (platform/future-architecture.md §6.1, ADR-0044).
|---------|------|---------|
| 2.0 | 2026-07-07 | OOP Release 2.0. |
| 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. |
 No newline at end of file