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

docs(oeg): F5 is resolved, reads are scoped by app provider

F5 still said OEG returns any tenant's apps, instances, deployments and
QoD sessions. OEG has checked ownership since 1cc2ac0 (2026-10-12): a read
or delete of a record another app provider created answers 404, and lists
return only the caller's records. Ownership is the app_provider_id of the
operation that created the record, so the scope is the developer, not the
organization in tenant_id.

The persistence model said tenant_id "scopes reads and deletes" on
qod_sessions and traffic_influences; it doesn't, the creating operation's
app_provider_id does.

Co-Authored-By: default avatarClaude Opus 5.5 <noreply@anthropic.com>
parent 079643be
Loading
Loading
Loading
Loading
+2 −1
Changes for oeg/architecture.md: 2 added lines, 1 removed line.
Original line number Diff line number Diff line
@@ -349,7 +349,7 @@ These questions are not resolved by the current architecture text. Keep them vis
- **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 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.
- **F5. Reads and deletes are scoped to the caller's app provider (resolved):** OEG answers `404` when a caller reads or deletes an app, app instance, app deployment, QoD session or traffic influence that another app provider created, and lists return only the caller's own records. Ownership is the `app_provider_id` of the operation that created the record. The scope is the app provider, not `tenant_id`: two developers of the same organization don't see each other's resources. Sharing within an organization waits on ownership moving to the organization, which ADR-0066 (API clients registered through Keycloak, owned by their creator) lists as later work.

## G. Functional Requirements

@@ -538,3 +538,4 @@ Structured logging and correlation IDs are the minimum observability baseline. P
| 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.0 | 2026-10-13 | Federation-lifecycle proxy row, UC-OEG-06 and REQ-OEG-17: OEG also relays `GET /platform/v1/federation/identity` (ADR-0051, amended 2026-10-13); the "not built yet (federation-manager#20)" note removed from UC-OEG-06. |
| 2.0 | 2026-10-20 | F5 resolved: reads and deletes are scoped to the caller's `app_provider_id`, through the operation that created each record. |
+3 −2
Changes for oeg/persistence-model.md: 3 added lines, 2 removed lines.
Original line number Diff line number Diff line
@@ -309,7 +309,7 @@ Indexes: `BTREE(operation_id)`, `BTREE(app_registration_id)`.
| `duration_seconds` | INTEGER | CAMARA `duration` → command `parameters.duration_seconds` |
| `state` | VARCHAR(32) | `REQUESTED`/`AVAILABLE`/`UNAVAILABLE` are activate-derived and surface as CAMARA `qosStatus`; `DELETION_REQUESTED`/`DELETED`/`ERROR` are teardown-derived and surface as `404` |
| `external_ref` | VARCHAR(255) | NULLABLE; the backend realization ref from the completion's `instances[0].external_ref`. Informational — correlation is on `session_id` |
| `tenant_id` | VARCHAR(255) | token `organization` claim, else `sub` (ADR-0048); scopes reads and deletes |
| `tenant_id` | VARCHAR(255) | token `organization` claim, else `sub` (ADR-0048). Reads and deletes are scoped by the `app_provider_id` of the creating operation, not by this column |
| `created_at` | TIMESTAMPTZ | |
| `updated_at` | TIMESTAMPTZ | |

@@ -351,7 +351,7 @@ Indexes: `BTREE(operation_id)`, `BTREE(tenant_id)`.
| `destination_protocol` | VARCHAR(32) | NULLABLE; from `destinationTrafficFilters.destinationProtocol` |
| `state` | VARCHAR(32) | `ordered`/`active`/`error` are activate-derived; `deletion in progress`/`deleted` are teardown-derived |
| `external_ref` | VARCHAR(255) | NULLABLE; backend realization ref. Informational — correlation is on `traffic_influence_id` |
| `tenant_id` | VARCHAR(255) | token `organization` claim, else `sub` (ADR-0048); scopes reads and deletes |
| `tenant_id` | VARCHAR(255) | token `organization` claim, else `sub` (ADR-0048). Reads and deletes are scoped by the `app_provider_id` of the creating operation, not by this column |
| `created_at` | TIMESTAMPTZ | |
| `updated_at` | TIMESTAMPTZ | |

@@ -555,3 +555,4 @@ Ship JWT validation, `ErrorInfo`, one async deploy path, `GET /platform/v1/opera
| 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). |
| 2.0 | 2026-10-20 | `qod_sessions` and `traffic_influences`: reads and deletes are scoped by the creating operation's `app_provider_id`, not `tenant_id`. |
 No newline at end of file