# Party identity and account boundaries Status: Proposed. Clarifies the [phased plan](plan.md) and [contract catalogue](contracts.md), revised 21 October 2026. The [context/port diagram](metallo-integration.md#context-and-port-diagram) shows how Metallo consumes canonical identity and Project authority. ## User and Party terminology Use **Party** consistently for the person's stable platform identity in domain models and contracts. The user interface can use user-facing language such as **user**; **member** describes a person's relationship to a Project. In this first release, a `PartyId` identifies a person participating in the platform. Project memberships, invitations, policy acceptance and audit records refer to that stable identity. A Keycloak subject, GitLab user and Waldur user are external identities associated with the person. Recovery can replace the authentication binding while preserving the Party and its memberships. `PartyAuthentication` names one aggregate within Party Governance. Its scope is authentication eligibility and recovery. `PartyId` also appears in other models; sharing that reference does not put their state inside this aggregate. Organisation governance remains a separate model introduced in a later phase. Party registration initially establishes its canonical ID through this root. There is no second `User` aggregate or parallel identity. Introduce a separate Party aggregate only if additional Party lifecycle rules need their own immediate consistency boundary; its identity would remain the existing PartyId. ## Records and consistency boundaries | Model | Owner | Responsibility | | --- | --- | --- | | `PartyAuthentication` aggregate, keyed by PartyId | Party Governance in Beto | Current authentication binding, unique stable AAI person identifier, retired history, platform access state, authVersion and recovery. | | `PartyIdentityAttributes` projection, keyed by PartyId | Party Governance in Beto | Latest identity attributes received from the trusted AAI, with source, observation time and its own revision. | | `PartyProviderBinding` identity record, with its own canonical bindingId and version | Party Governance in Beto | Verified association of one external account with a Party, proof reference and retirement. It is an independent write root. | | `Project` and `ProjectFulfilment` aggregates | Project in Beto | Membership authority and delivery of Project representation/access, including required provider-user prerequisites. | | Compatibility onboarding progress | Retained integration | Durable signup-time account/personal setup and per-session credentials, with independent provider retries and qualified identity hand-off. | | Site account fulfilment and native access | The responsible Site provisioning integration and Metallo's Site Runtime | Site-specific account lifecycle, native enforcement, local delivery and recovery through explicit ports. | This iteration introduces no Account Provisioning context. Project owns the port for its provider representation/access; retained signup operations remain explicit compatibility work. The [authentication transition](authentication-transition.md) defines API session ownership and the legacy adaptor. A separate account context needs independent business rules and consumers before its model is selected. Waldur identity proof belongs behind Party Governance's narrow owner port; Project/member access belongs behind Project's ports. Metallo obtains Party and Project facts through their published queries or consumer-owned event projections. Its Waldur resource adaptors cannot independently resolve platform identities or membership. Shared provider client code supplies technical mechanisms only. Record provider effects and retry progress independently. A GitLab outage leaves Waldur progress intact. Project user prerequisites delegate to the retained account writer during coexistence, preventing signup and Project fulfilment from competing to create the same account. Provider API details stay in owner adaptors. An account being verified establishes who it belongs to. An account being ready establishes that requested provider effects have completed. Project permissions are checked separately. The binding record stores no tokens, role assignments or provisioning retries. `RecordPartyProviderBinding` writes this record and leaves the PartyAuthentication version unchanged. For Site accounts, publish only the identity association needed by Beto. Keep native account operations and access rules with their existing owners. The [Site Runtime boundary](../../../metallo/docs/epics/instantiate-and-run/site-runtime-context.md) already consumes member bindings and permissions through explicit ports; this proposal does not move those responsibilities into PartyAuthentication. ## Platform access and external identity state Use separate fields for two decisions: | Field | Values | Meaning | | --- | --- | --- | | `accessState` on PartyAuthentication | `enabled`, `disabled` | Whether platform policy permits this Party to authenticate. | | `identityState` on a current or retired authentication binding | `enabled`, `disabled`, `purged` | Latest accepted lifecycle state of that exact external issuer/subject. | | `authenticationEligible` in query results | Boolean | Derived eligibility from platform access and the current binding's availability. Session/grant generation and assurance are checked as well. | The initial verified OIDC registration establishes an enabled external identity, subject to any previously recorded lifecycle signal or tombstone. A subsequent login cannot overwrite a disabling or purging signal. Once signal delivery is qualified, its source identity, occurrence time and ordering rules govern updates. An AAI enablement does not lift a platform-imposed disablement. Conversely, enabling platform access cannot bypass a disabled or purged current binding. `authVersion` advances on access changes, current-binding eligibility changes and binding replacement, fencing previously issued credentials. Attribute-only updates and provider account retries leave this generation unchanged. `purged` is terminal for the specific external identity. Retain a tombstone so that the old issuer/subject cannot be reused. An independently approved recovery can bind the same Party to a different available identity. Recovery preserves a platform-imposed disablement; lifting it requires its authorised command. Signals for a retired binding never disable or re-enable the replacement binding. The pre-login signal reservation and binding uniqueness implementation remains a Phase 4 qualification gate. Persist unavailable identities before acknowledging their signals and prove that concurrent first login cannot bypass those records. Do not enable the receiver until this is enforced without a cross-root write. Project lifecycle is separate: the initial scope has `active` and `closed`. Architecture-reset also has `suspended`. Introduce suspension with explicit rules for member reads, new workloads, running workloads, provider access and resumption. Its omission from the first contract is a staged capability decision. ## Identity attributes received from the AAI The projection retains the [complete identity attribute inventory](../../../../contracts/authentication/README.md#identity-attribute-inventory-and-recovery): display and given/family names, verified email, preferred username, home organisation, external affiliations and entitlements. It also records the exact stable `voperson_id` and optional `site_username` observations, source issuer/subject, observation time, authentication generation and projection revision. Absent arrays and explicit empty arrays remain distinguishable. Affiliations and entitlements confer no permissions. V3 requires one globally unique opaque `voperson_id`. An existing exact issuer/subject can adopt it; a later subject change requires approved recovery and preserves PartyId. Recovery reserves the retired binding and advances `auth_version`. The owner recovery command accepts independently approved proof; operator-facing request/approval workflows remain a later phase. `site_username` comes from the enrolled issuer's UserInfo response. An explicit issuer-to-Site enrolment qualifies a durable hand-off to the separate provider binding root, using provider `site` and the enrolled instance such as `icarus`. Missing Site claims leave Site association pending while platform login proceeds. A changed or conflicting username requires attention through the binding lifecycle; it cannot silently replace an existing association. The Site binding preserves the exact AAI username for Metallo's HPC POSIX match. The [claim inventory](../../../../contracts/authentication/README.md#identity-attribute-inventory-and-recovery) records GRNET's current naming format for fixtures; it is not a canonical rule. `PartyIdentityAttributesChanged` publishes only contact fields and projection revision for consumers such as Support. Its outgoing intent commits with the projection. Authentication subjects, stable IDs, affiliations, entitlements and Site usernames stay outside that event. Site associations use the existing provider-binding events. For example, a person signs in with subject `abc` and email `old@example.test`. A later validated login supplies `new@example.test` for the same binding. Update the attributes while retaining PartyId, Project memberships and authVersion. Two different subjects sharing an email do not establish one Party or an account binding. Accepted authentication records durable private intent for the projection update. The projection consumer deduplicates observations and rejects stale ordering or observations from a replaced binding. `GetCurrentParty` may briefly report `identityAttributesState: pending` after registration or recovery. It returns `ready` attributes only for the current binding and authentication generation. Its top-level `version` remains the PartyAuthentication revision; the attribute projection has a separate `version`. Invitation claims use freshly validated recipient evidence from the trusted authentication boundary. A cached display email cannot authorise account linking or invitation acceptance. Provider identity association requires qualified issuer/subject mapping or equivalent explicit ownership proof. User-editable profile fields, such as a chosen display name or biography, need their own ownership and update rules when introduced. Keep their values separate from the AAI-derived copy so that login does not silently overwrite user choices. ## Provider account workflow 1. Resolve the Party through its validated authentication binding. 2. If the account already exists, verify its external identity and record a `PartyProviderBinding` through the published Party Governance command. 3. Signup resumes retained account/personal setup and per-session credential work. Project fulfilment records its own desired Project revision and uses its provider port to ensure prerequisites and reconcile groups/access. Both paths use one qualified writer per provider operation. 4. Record confirmed effects or `pending` / `attention-required` progress with stable effect identities. Confirmed ownership proof produces a durable request to establish the Party binding. Consumers needing the association also require that binding outcome. Administrator credentials stay outside API/Beto runtime; the existing compatibility exception retains its documented deployment scope. 5. Project applies current desired access; stale effects cannot confirm the latest revision. Party disablement immediately fences platform authentication, while independently recorded provider access removal can remain pending. Every command writes one root. A successful provider call cannot atomically commit another context's binding or Project membership. Record the outcome and durable hand-off, then expose pending progress until the receiving owner commits. Retries preserve effect identity and compare the current desired revision. ## Event names and versions Use `PartyRegistered` as the event name and `contract_version: 1` as its schema version, matching the fields of the shared event envelope. The earlier notation `PartyRegistered.v1` referred to this same integration contract. The catalogue now separates the name and version into columns to make that explicit. Publish one registration event, with one eventId, when the Party first registers. An implementation may raise a private domain fact and map it to that integration contract through the existing delivery foundation. The private fact has its own explicit registration and visibility. It is not a second public registration event, and consumers subscribe to the published contract once. Emit the registration fact once when canonical Party registration first commits. Repeated login, provider account creation and authentication recovery retain the PartyId and do not register the Party again. Redelivery preserves the original eventId; consumer receipts handle duplicates. Provider provisioning has its own facts and can remain pending after Party registration.