diff --git a/WORK-RECORDS.md b/WORK-RECORDS.md index b9d5085..df2aa04 100644 --- a/WORK-RECORDS.md +++ b/WORK-RECORDS.md @@ -30,6 +30,7 @@ | workplan | USER-WP-0020 | finished | — | workplans/USER-WP-0020-self-service-and-user-administration-portal.md | | workplan | USER-WP-0021 | active | — | workplans/USER-WP-0021-portal-product-expansion.md | | workplan | USER-WP-0022 | blocked | — | workplans/USER-WP-0022-public-registration-and-jit-application-profiles.md | +| workplan | USER-WP-0023 | active | — | workplans/USER-WP-0023-flex-auth-caller-identity.md | | task | USER-WP-0001-T1 | done | — | workplans/USER-WP-0001-preparation-and-interface-adoption.md | | task | USER-WP-0001-T2 | done | — | workplans/USER-WP-0001-preparation-and-interface-adoption.md | | task | USER-WP-0001-T3 | done | — | workplans/USER-WP-0001-preparation-and-interface-adoption.md | @@ -165,3 +166,6 @@ | task | USER-WP-0022-T03 | cancel | — | workplans/USER-WP-0022-public-registration-and-jit-application-profiles.md | | task | USER-WP-0022-T04 | cancel | — | workplans/USER-WP-0022-public-registration-and-jit-application-profiles.md | | task | USER-WP-0022-T05 | progress | — | workplans/USER-WP-0022-public-registration-and-jit-application-profiles.md | +| task | USER-WP-0023-T01 | done | — | workplans/USER-WP-0023-flex-auth-caller-identity.md | +| task | USER-WP-0023-T02 | done | — | workplans/USER-WP-0023-flex-auth-caller-identity.md | +| task | USER-WP-0023-T03 | wait | — | workplans/USER-WP-0023-flex-auth-caller-identity.md | diff --git a/openapi/portal-v1.yaml b/openapi/portal-v1.yaml index 47bbb71..42e9c2d 100644 --- a/openapi/portal-v1.yaml +++ b/openapi/portal-v1.yaml @@ -379,8 +379,10 @@ components: metadata: type: object description: >- - Mutable tenant metadata. tenant_id, identifier, and grouping are - immutable at the authority and are rejected here. + Mutable tenant metadata. tenant_id and identifier are immutable at + the authority and are rejected here. grouping is mutable but only + through the authority's own reclassification route, never as + metadata, because it resolves a tenant's spend ceiling. minProperties: 1 properties: display_name: {type: string, minLength: 1, maxLength: 200} @@ -396,6 +398,13 @@ components: external_ref: {type: string} lifecycle: {type: string, enum: [active, retired, unknown]} version: {type: integer, minimum: 0} + grouping: + type: string + nullable: true + description: >- + Authoritative classification, reported by the tenant authority. + Read it from here; never parse it from the identifier, whose own + grouping segment is historical once a tenant is reclassified. display_name: {type: string, nullable: true} contact_email: {type: string, nullable: true} retired_at: {type: string, nullable: true} diff --git a/src/user_engine/adapters/tenant_management.py b/src/user_engine/adapters/tenant_management.py index 7986d97..ca1ac11 100644 --- a/src/user_engine/adapters/tenant_management.py +++ b/src/user_engine/adapters/tenant_management.py @@ -176,6 +176,7 @@ class HTTPTenantManagementAdapter: external_ref=str(payload.get("tenant_id") or identifier), lifecycle=str(payload.get("lifecycle") or "unknown"), version=int(payload.get("version") or 0), + grouping=payload.get("grouping"), display_name=payload.get("display_name"), contact_email=payload.get("contact_email"), retired_at=payload.get("retired_at"), diff --git a/src/user_engine/ports.py b/src/user_engine/ports.py index 38e40a1..faebf37 100644 --- a/src/user_engine/ports.py +++ b/src/user_engine/ports.py @@ -128,6 +128,9 @@ class TenantRecord: external_ref: str lifecycle: str version: int + # Authoritative classification. The identifier's own grouping segment is + # historical after a reclassification, so it must never be parsed for this. + grouping: str | None = None display_name: str | None = None contact_email: str | None = None retired_at: str | None = None diff --git a/src/user_engine/web.py b/src/user_engine/web.py index 5c78931..464eb9d 100644 --- a/src/user_engine/web.py +++ b/src/user_engine/web.py @@ -1694,6 +1694,7 @@ class PortalApplication: f"Tenant {record.tenant}", f"""
Lifecycle {escape(record.lifecycle)} at version {record.version}.
+Grouping {escape(record.grouping or 'not reported')}, as reported by the tenant authority. The identifier's own segment is historical after a reclassification and is not the grouping.
{replayed} {metadata_form}