Files
member-console/design/identity/interfaces.md
Christian Galo 195cd348a9 Update design to use schemas per module and revert to using industry
aligned terminology for discounts and coupons and promo codes.
2026-04-01 03:08:35 -05:00

6.8 KiB

Identity Module -- Interfaces

Module: identity Schema: identity

The identity module occupies a distinctive structural position in the data model: it is the most widely consumed module and the only domain module with zero outbound cross-module dependencies. Every other module references identity; identity references no other module.


Provided Interfaces (other modules depend on us)

persons.person_id

The single most widely referenced primary key in the schema. Over thirty FK columns across four modules reference persons.person_id.

Consumer Module Consumer Table FK Column(s) Semantics
organization organizations owner_person_id Personal organization ownership
organization organizations suspended_by, deleted_by Governance actor attribution
organization org_members person_id Organizational membership
organization role_assignments person_id Permission grant recipient
organization invitations invited_by_person_id, resolved_person_id Invitation provenance and resolution
organization workspaces created_by_person_id Workspace creation attribution
organization service_accounts created_by_person_id, suspended_by, deleted_by Service account lifecycle attribution
organization service_account_keys revoked_by_person_id Key revocation attribution
billing billing_accounts owner_person_id Billing account ownership
billing patrons person_id Cooperative patron identity
billing patronage_events patron_person_id Patronage event attribution
billing invoices Various actor columns Invoice lifecycle attribution
billing subscriptions Actor attribution columns Subscription governance
audit audit_logs actor_person_id Actor attribution for all audited actions

Cardinality: Each consumer FK references exactly one person. A person may be referenced by zero or many rows in each consumer table.

users.user_id

Referenced within the identity module only. The sole consumer of users.user_id as a FK is persons.user_id in this module.

Consumer Module Consumer Table FK Column(s) Semantics
identity persons user_id Links business identity to authentication identity

Note: The audit log references user_id conceptually for session-authenticated actor identification, but the structural FK in audit_logs is actor_person_id, not actor_user_id. The user-to-person resolution happens at the application layer before the audit entry is written.


Consumed Interfaces (we depend on other modules)

NONE.

The identity module has zero outbound cross-module foreign keys. All FK references within identity tables point to other identity tables:

  • persons.user_id -> users (intra-module)
  • persons.deactivated_by -> persons (self-referential)
  • personal_access_tokens.person_id -> persons (intra-module)
  • personal_access_tokens.revoked_by_person_id -> persons (intra-module)
  • retention_holds.person_id -> persons (intra-module)
  • retention_holds.hold_placed_by -> persons (intra-module)
  • retention_holds.hold_released_by -> persons (intra-module)
  • person_merges.source_person_id -> persons (intra-module)
  • person_merges.target_person_id -> persons (intra-module)
  • person_merges.merged_by_person_id -> persons (intra-module)

This zero-dependency property makes the identity module the foundation of the dependency graph. It can be deployed, tested, and reasoned about without reference to any other module.


Interface Contracts

person_id stability through anonymization

The identity module guarantees that persons.person_id remains stable through anonymization. When a person is anonymized, the row is mutated (PII fields overwritten or nullified), not deleted. The person_id continues to resolve to a valid row indefinitely. All downstream FK references remain intact. Aggregate statistics, audit trails, and patronage totals computed against person_id remain valid; only the identifying content is destroyed.

This guarantee is load-bearing: the audit module, billing module, and organization module all depend on the invariant that a person_id FK will resolve to a row regardless of the person's lifecycle state.

person_id stability through merge

The identity module guarantees that persons.person_id survives merge operations. When two person records are merged, all FK references are repointed from the source to the target within a single transaction, but the source row is retained with status = 'merged'. The source person_id remains a valid FK target. The person_merges table provides structured provenance tracking which source was merged into which target.

After merge, the source record is a historical artifact with no active downstream relationships -- all active references now point to the target. But the source person_id is never deleted and never becomes a dangling reference.

user_id recreation semantics

The identity module acknowledges that users.user_id may be recreated on Keycloak re-registration. If the identity provider is replaced or a user re-registers after anonymization, a new users record is created with a new user_id. The previous users record (status 'deleted', PII anonymized) remains as a historical artifact. The new user may be linked to a new or existing person record, but the old user-person linkage is not restored -- it was severed during anonymization.

This means that user_id is not a durable cross-session identity anchor in the way that person_id is. Consumer modules should reference person_id, not user_id, for any relationship that must survive authentication provider changes.

Anonymization protocol cross-module reach

While the identity module does not consume interfaces from other modules, the anonymization protocol (Section 4.6 of architecture.md) reaches into the billing module to anonymize billing account PII (billing_name, billing_email, billing_address fields). This is an operational dependency -- the anonymization transaction spans module boundaries -- but it is not a structural FK dependency. The identity module does not hold a FK to billing_accounts; rather, the anonymization procedure locates billing accounts via the organization module's ownership chain (person -> personal organization -> billing accounts) and anonymizes them as part of the same transaction.

This cross-module operational reach is documented here as an interface contract: the billing module must accept that its billing_accounts rows may be mutated by the identity module's anonymization protocol when the billing account belongs to a personal organization whose owner is being anonymized.