Human FK Rule — contact as sole human identity
Every foreign key pointing at a human points at contacts.id; references(() => users.id) is forbidden everywhere. Spec: docs/superpowers/specs/2026-04-24-contact-as-sole-human-identity-design.md in renewa-gmbh/bookish-broccoli.
The model
contacts= durable human identity (Contacts). Identity attributes, membership, attribution (createdBy,approvedBy,decidedBy, …), assignment/ownership (assignedTo,reviewerId,ownerId, …), and audit references all FK here.users= one authentication method among many, keyed oncontact_id(Users). A person can exist (and be referenced) without a login.- Active-ness is a predicate, not an FK target:
SELECT 1 FROM users WHERE contact_id = $1 AND is_active = true. - Writes use
actor.contact.id(ServiceActor); auth-type gating readsactor.authFactors.
Enforcement — ratcheted, Strangler Fig
- CI grep gate with an allowlist of pre-existing
users.idFKs (the number only goes down) +/auditchecks on schema-touching PRs. - Same migration mechanism later reused by Persistence Topology (I#2013).
- Notable migration:
workflow_transition_log.actorId→contacts.id(PR#1931, I#1759). - Grandfathered (tracked, not new-code-eligible): auth-subsystem
userIdcolumns (sessions,*_tokens),audit_logs.userId,document_obtaining_request_batches.reviewerId/assigneeId/ownerId.
See Also
- Contacts — the identity entity
- Users — auth-method reframe
- RBAC Authorization — roles key on contacts.id
- Persistence Topology — the other Strangler-Fig ratchet