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.