Commit 4f645506 authored by Dimitrios Gogos's avatar Dimitrios Gogos
Browse files

docs: update ADR-0048 and ADR-0066 to clarify app_provider_id claim usage;...

docs: update ADR-0048 and ADR-0066 to clarify app_provider_id claim usage; amend related documentation in architecture and persistence models
parent a1b9984c
Loading
Loading
Loading
Loading
+1 −1
Changes for decisions/0048-platform-roles.md: 1 added line, 1 removed line.
Original line number Diff line number Diff line
@@ -9,7 +9,7 @@ status: "proposed"

# ADR-0048: Three platform roles, API product grants, and tenancy

**Status:** proposed
**Status:** proposed — decision 6's "`app_provider_id` is the token's `sub`" is amended by [ADR-0066](/decisions/0066-api-clients-through-keycloak-client-registration.md): the `app_provider_id` claim when present, else `sub`
**Date:** 2026-10-04
**Affects:** [platform/architecture.md](/platform/architecture.md) §Security Architecture → Roles, [oeg/architecture.md](/oeg/architecture.md) (role and scope checks, F4), [oeg/persistence-model.md](/oeg/persistence-model.md) (`tenant_id`, `app_provider_id`, `api_subscriptions`), [platform/glossary.md](/platform/glossary.md), [ADR-0006](/decisions/0006-oeg-db-schema-consolidation.md), [ADR-0035](/decisions/0035-qod-session-service-specification.md), portal backend `Role` enum, Helm `charts/keycloak` (realm `oop`)

+4 −2
Changes for decisions/0066-api-clients-through-keycloak-client-registration.md: 4 added lines, 2 removed lines.
Original line number Diff line number Diff line
@@ -11,7 +11,7 @@ status: "proposed"

**Status:** proposed
**Date:** 2026-10-08
**Affects:** [platform/architecture.md](/platform/architecture.md) (UC-OOP-08, §Security Architecture), [portal/requirements-and-design.md](/portal/requirements-and-design.md) §L, Helm `charts/keycloak` (realm `oop`) and `charts/portal`, builds on [ADR-0048](/decisions/0048-platform-roles.md)
**Affects:** [platform/architecture.md](/platform/architecture.md) (UC-OOP-08, §Security Architecture), [portal/requirements-and-design.md](/portal/requirements-and-design.md) §L, Helm `charts/keycloak` (realm `oop`) and `charts/portal`, builds on [ADR-0048](/decisions/0048-platform-roles.md) and amends its decision 6 (`app_provider_id`), [oeg/architecture.md](/oeg/architecture.md) F4, [oeg/persistence-model.md](/oeg/persistence-model.md)

## Context

@@ -32,7 +32,9 @@ rights over every other client: Keycloak's `view-clients` exposes every client's
2. **An API client** is a confidential Keycloak client that can only use client credentials. It
   carries the scopes of its products and the `oeg` audience, its service account holds the
   product roles, and its tokens carry `organization` (the creator's organization, or their
   `sub`) and `app_provider_id` (the creator's `sub`; OEG does not read it yet).
   `sub`) and `app_provider_id` (the creator's `sub`). OEG takes the caller's `app_provider_id`
   from this claim when present, else from `sub`, so what an API client creates belongs to its
   creator.
3. **Products are fixed at creation** and limited to those the creator holds in Keycloak. Each
   user has at most 5 clients, with unique names.
4. **The secret is generated by Keycloak, shown once** and never stored by the portal.
+7 −7
Changes for oeg/app-registration-flow.md: 7 added lines, 7 removed lines.
Original line number Diff line number Diff line
@@ -168,12 +168,11 @@ HTTP 201 + { "appId": "<uuid>" }

| Field | Source |
|---|---|
| `tenant_id` | JWT `client_id` claim |
| `app_provider_id` | JWT `client_id` claim |
| `tenant_id` | token `organization` claim, else `sub` (ADR-0048) |
| `app_provider_id` | token `app_provider_id` claim, else `sub` (ADR-0048, ADR-0066) |

Both fields hold the same value today — they represent conceptually different identities (tenant
scoping vs. app-provider business identity) that may diverge under future delegation/federation
scenarios.
The tenant is the organization; the app provider is the developer, which for an API client is
the developer who created it.

---

@@ -190,8 +189,8 @@ AppRegistrationTranslation
  name                        str
  version                     str
  appProvider                 str         NOT forwarded to SRM under normal operation. The authoritative
                                          service_specification.app_provider_id always derives from the JWT
                                          client_id claim; this body value is used only as a fallback source
                                          service_specification.app_provider_id always derives from the token
                                          (app_provider_id claim, else sub); this body value is used only as a fallback source
                                          for app_provider_id if no JWT/client_id claim is present.
  packageType                 str         → service_deployment_unit.runtime_kind (container|helm; others → 501, ADR-0009)
  appRepo                     obj
@@ -415,3 +414,4 @@ Compact end-to-end mapping across UC1–UC3 (rows marked UC2/UC3 apply only to t
| Version | Date | Summary |
|---------|------|---------|
| 2.0 | 2026-07-07 | OOP Release 2.0. |
| 2.0 | 2026-10-09 | JWT fields: `tenant_id` is the `organization` claim, else `sub` (ADR-0048); `app_provider_id` the `app_provider_id` claim, else `sub` (ADR-0066). Was the JWT `client_id`. |
 No newline at end of file
+2 −1
Changes for oeg/architecture.md: 2 added lines, 1 removed line.
Original line number Diff line number Diff line
@@ -323,7 +323,7 @@ These questions are not resolved by the current architecture text. Keep them vis
- **F1. Catalog cache:** In-process TTL vs another transient cache for `event.srm.catalog.updated` invalidation? *Recommendation:* start with in-process + short TTL; move to a shared transient cache only when load requires.
- **F2. Consent / User Identity Token:** Where does the User Identity Token enter — header vs body? *Recommendation:* follow the CAMARA security profile plus OPG.02 privacy guidance; store opaque reference in `operations.metadata` JSONB, delegate validation to SRM.
- **F3. Location Retrieval's identifier-echo fallback is an unresolved guess:** when SRM's `srm_resolved_identifier` is absent, OEG echoes the first identifier the request supplied in a fixed order (`ipv4`, `phone_number`, `ipv6`, `network_access_id`) — the mapper's own docstring calls this a guess that may not match the identifier the network actually used ([oeg/interface-contract.md](/oeg/interface-contract.md) §N.4). No decision exists for why this order, or whether guessing is acceptable at all for a CAMARA field the consumer is expected to trust.
- **F4. Token identity extraction (resolved for tenancy):** `tenant_id` is the token's `organization` claim, else its `sub`, and `app_provider_id` is the `sub` (ADR-0048). Still open: no claim maps to a device, so Location Retrieval's `device` cannot be omitted the way the vendored CAMARA spec allows when the subject is derivable from the token ([oeg/interface-contract.md](/oeg/interface-contract.md) §N.4).
- **F4. Token identity extraction (resolved for tenancy):** `tenant_id` is the token's `organization` claim, else its `sub`, and `app_provider_id` is the token's `app_provider_id` claim, else its `sub` (ADR-0048, ADR-0066). Still open: no claim maps to a device, so Location Retrieval's `device` cannot be omitted the way the vendored CAMARA spec allows when the subject is derivable from the token ([oeg/interface-contract.md](/oeg/interface-contract.md) §N.4).
- **F5. Tenant scoping of reads and deletes is not enforced (deferred to the next release):** OEG stores `tenant_id` and `app_provider_id` on every record, but reads and deletes don't filter by them yet. `GET /apps` lists every tenant's apps, and reads or deletes by ID (apps, app instances, app deployments, QoD sessions) return any tenant's record. The persistence model's "`tenant_id` scopes reads and deletes" is the target; until then, isolation rests on resource IDs being random UUIDs.

## G. Functional Requirements
@@ -506,3 +506,4 @@ Structured logging and correlation IDs are the minimum observability baseline. P
| 2.0 | 2026-10-04 | Auth responsibility and REQ-OEG-02 add per-operation CAMARA scope checks; F4 resolved for tenancy (`organization` claim, else `sub`), device mapping still open; F5 records that tenant scoping of reads and deletes is deferred; REQ-OEG-09 rate limits deferred and keyed by tenant and API product (ADR-0048). |
| 2.0 | 2026-10-04 | Auth and logging match the OEG implementation: no `oop-commons`; the CAMARA scope is required for every token, including QoD sessions (stricter than QoD v0.10.1). REQ-OEG-10 and §Structured Logging list the fields OEG logs (caller `tenant_id`, `app_provider_id`, `client_id`; one `http_request` line per request) and what is not logged yet (`operation_id`). |
| 2.0 | 2026-10-06 | REQ-OEG-08: the sink token is stored encrypted, and expired/revoked tokens skip the delivery (ADR-0063). |
| 2.0 | 2026-10-09 | `app_provider_id` is the token's `app_provider_id` claim, else its `sub`, so an API client's resources belong to the developer who created it (ADR-0066). |
+2 −1
Changes for oeg/persistence-model.md: 2 added lines, 1 removed line.
Original line number Diff line number Diff line
@@ -189,7 +189,7 @@ in `app_instances.state`).
| `correlation_id` | VARCHAR(128) | From `X-Correlator` or generated; INDEX |
| `idempotency_key` | VARCHAR(128) | NULLABLE; composite UNIQUE(`tenant_id`, `idempotency_key`) — scoped per tenant to avoid cross-tenant collision |
| `tenant_id` | VARCHAR(255) | token `organization` claim, else `sub` (ADR-0048) |
| `app_provider_id` | VARCHAR(255) | token `sub` (ADR-0048) |
| `app_provider_id` | VARCHAR(255) | token `app_provider_id` claim, else `sub` (ADR-0048, ADR-0066) |
| `operation_type` | VARCHAR(32) | `deploy`, `scale`, `terminate`, `network_capability_activate`, `network_capability_deactivate`. The activate/deactivate split exists because teardown is its own operation with its own `operation_id` (ADR-0034); the capability family is distinguished by the record the operation points at, not by the type. |
| `status` | VARCHAR(32) | CHECK IN (`PENDING`,`IN_PROGRESS`,`COMPLETED`,`PARTIALLY_COMPLETED`,`FAILED`). Exposed on `GET /platform/v1/operations/{operationId}` as JSON field `status` with the same name and values (no mapping layer). Phase 1 uses `PENDING`→`COMPLETED`\|`PARTIALLY_COMPLETED`\|`FAILED`; `PARTIALLY_COMPLETED` when a multi-zone operation's `instances[]` has a mix of per-instance outcomes (ADR-0005); `IN_PROGRESS` activates with `event.srm.operation.status` consumption in Phase 2. Note: this column's values are UPPERCASE and exposed as-is (no mapping), unlike `app_instances.state`/`app_deployments.state` below, which are lowercase and likewise exposed as-is — each column follows its own casing convention with no transformation on read. |
| `app_registration_id` | UUID FK -> `app_registrations` | NULLABLE (not all operations concern an app); resolved from the request's `appId` (ADR-0010) |
@@ -554,3 +554,4 @@ Ship JWT validation, `ErrorInfo`, one async deploy path, `GET /platform/v1/opera
| 2.0 | 2026-09-11 | `traffic_influences.edge_cloud_zone_id` stores the zone pinned on the activate command rather than only the requested `edgeCloudZoneId`, and scopes `selected_appInstanceId` on the Traffic Influence notification. |
| 2.0 | 2026-10-04 | `tenant_id` is the token `organization` claim, else `sub`, and `app_provider_id` the `sub` (was "JWT `client_id`"); `api_subscriptions` reserved for per-product quota and validity (ADR-0048). |
| 2.0 | 2026-10-06 | Callback tokens (ADR-0063): new `CallbackCredential` entity and `callback_credentials` table (AES-GCM, tombstoned on revoke); `callback_registrations.sink_credential_ref` removed and the table now also covers the QoD `webhook`; `callback_deliveries.state` values aligned to `delivered`/`failed`/`skipped`; Security Design and deployment env updated for the app-wide key. |
| 2.0 | 2026-10-09 | `app_provider_id` is the token's `app_provider_id` claim, else its `sub`, so an API client's resources belong to the developer who created it (ADR-0066). |
 No newline at end of file