Skip to content

Key organizations by GUID consistently across Admin, Lobby and Core #152

Description

@einari

Organization identity is inconsistent across the services, and it is why only one organization shows a trial in the Admin listing.

What is wrong today

Admin holds 7 OrganizationTrialGranted events but organizationTrials has a single row. Six of them are keyed by organization GUID; one is keyed by tenant name. OrganizationTrial is keyed by tenant name, so only the last one materializes. TrialReportingReactor was changed to key by tenant name at some point, which silently orphaned everything granted before it.

Six organizations (Cratis, Elvion, Hageskal, HiveConsulting, Identigo, Vybraze) have no OrganizationTrialStarted at all - they predate the trial flow, and are exactly the ones carrying manual SeatsGranted events as a workaround.

The old events cannot be salvaged by replaying: a projection keyed by tenant name cannot recover the tenant from a GUID, and projections cannot join to organizations to look it up.

The model we want

The organization's GUID is its event source id everywhere, and never appears as a payload property. Keying on the GUID rather than the name is what would let an organization be renamed later without orphaning its history - which is precisely the failure above, in a different guise.

Changes

Core has to learn the organization's identity. It currently has no notion of the GUID at all: Admin mints it and publishes OrganizationOnboardingAccepted(OrganizationId, TenantName), but only Lobby consumes it. Core should consume it from the Admin inbox and record the organization in its tenant namespace, so it can key on the GUID afterwards.

Contract events drop the tenant from their payload, carrying it as the event source id instead:

  • Core.Contracts TrialGrantedForOrganization(Tenant, Sku, Expiration) -> (Sku, Expiration)
  • Admin.Contracts SeatsGrantedToTenant(Tenant, Sku, Quantity, Expiration) -> (Sku, Quantity, Expiration)

Organization-level facts key on the organization, not the user. OrganizationTrialStarted is appended against the user id today; it is an organization fact and belongs on the organization's stream.

Admin's reactors and read models key on OrganizationId. TrialReportingReactor then needs no lookup at all - the event source is already the organization.

Existing events get migrated. Few enough to rewrite in place, and we are in beta.

Integration specs

End to end, one flow: invite from Admin -> acceptance in Lobby -> user registered and seat assigned in Core for the target tenant -> organization registered in Admin with the correct seat assignment. The existing onboarding spec covers part of this path already and is the natural place to extend.

Note

Cratis stays at 3 seats - a SingleUser trial grants one, and its manual grant is what covers the other two.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions