You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Introduce tenant-local human identities for the app-per-tenant model. A person who signs up in App A must not automatically exist in App B: the same email may create separate entities in each tenant, with independent credentials, sessions, verification, password recovery, roles, and lifecycle. All work should land through small PRs targeting the integration branch feature/tenant-local-identities, followed by one stabilization PR from that branch to main.
Context and product decision
Atomic is adopting this application boundary:
One Atomic app maps to exactly one Atom tenant.
Atomic/app users remain Atom human entities; a tenant is never a user.
Platform administrators and service identities remain separate from app users.
Atom currently supports globally reusable identities. In that model, a user who signs up once can later receive access to another tenant without creating a separate app identity. That is not the desired behavior for app-per-tenant isolation.
Problem statement
For tenant-local apps, signing up in one app must not establish an identity or reusable credential in any other app.
Given the same email address in two app tenants, Atom must represent two independent identities:
Concern
App A / Tenant A
App B / Tenant B
Entity
Independent entity ID
Independent entity ID
Password and credentials
Independent records
Independent records
Verification and recovery
Tenant A only
Tenant B only
Sessions and JWTs
Tenant A only
Tenant B only
Roles and policies
Tenant A only
Tenant B only
Lifecycle
Tenant A only
Tenant B only
The user must separately sign up or accept an invitation in App B. Passwords may coincidentally have the same value if chosen by the user, but they are stored and managed independently; resetting App A's password must not affect App B.
Goals
Isolate app-user identities by tenant.
Require trusted tenant/app context for signup, login, verification, recovery, invitation acceptance, and OAuth/OIDC linking.
Allow the same normalized email in different tenants while preventing duplicates inside one tenant.
Bind credentials, sessions, JWTs, roles, and lifecycle operations to the identity's home tenant.
Reject cross-tenant memberships, role assignments, policies, and credential use for tenant-local identities.
Preserve platform/global identities for Atom administrators and system services.
Provide a safe, observable, retryable migration path for existing deployments.
Non-goals
Cross-app single sign-on.
Automatically sharing one user account or session across tenants.
Copying API keys, access tokens, certificates, or active sessions during migration.
Treating Atomic collaborators as finished-app users.
Adding tenant_user as an EntityKind; human remains the principal kind, while identity scope describes where the identity exists.
Changing the online authorization principle: tokens identify identity/session; live permissions remain database-backed.
Proposed domain model
Identity scope
Introduce an explicit identity scope with two exhaustive values:
platform: no home tenant; reserved for platform administration and approved system/service identities.
tenant: a home tenant is mandatory; used for isolated app users.
EntityKind remains responsible for what the principal is (human, service, and so on). Identity scope is responsible for where that identity exists.
Tenant identity mode
Support an explicit tenant identity mode for controlled rollout:
global: legacy behavior for existing tenants.
tenant_local: isolated identities for app tenants.
All newly provisioned Atomic app tenants should use tenant_local. Existing tenants should not silently change behavior during migration.
Identity uniqueness
Tenant-local humans are unique by (tenant_id, normalized_email) among live identities.
Platform humans retain global normalized-email uniqueness.
A tenant-local identity must always have a home tenant.
A tenant-local identity cannot be moved to or reused by another tenant.
Functional requirements
Signup and open enrollment
Signup must resolve a trusted tenant ID or alias before identity lookup.
The target tenant must be active, not deleted, and configured for open enrollment.
Email lookup and conflict handling must occur only inside the target tenant.
Entity creation, password credential creation, verification state, tenant membership, and default Member role assignment must be atomic.
The default role must come from server-side tenant enrollment configuration; clients must not supply an arbitrary role ID.
Repeated requests must be idempotent and must not create duplicate live identities.
Public responses must not disclose identities from other tenants.
Login
Tenant-local login must require tenant ID or alias.
Identity lookup must be restricted to that tenant and identity scope.
App A credentials must fail when presented to App B, even when the email and password text are identical.
Login must fail when the tenant or entity is inactive, frozen, deleted, suspended, or otherwise ineligible.
Successful sessions must record the home tenant.
JWTs must carry the authenticated tenant context and be rejected when used against another tenant.
Email verification and password recovery
Verification and recovery tokens must be bound to both entity and tenant.
Tenant context must be resolved from trusted token state or validated app configuration, not accepted blindly from a browser field.
Resetting or verifying one tenant identity must not affect an identity with the same email in another tenant.
Responses must resist account and cross-tenant enumeration.
Redirects and email content must return the user to the correct application.
Invitations
Invitations belong to exactly one tenant.
Acceptance must create or activate a tenant-local identity only in the invitation tenant.
An identity with the same email in another tenant must not be linked or reused.
Invitation role assignment must be limited to roles belonging to the invitation tenant.
Revocation, expiration, repeated acceptance, suspension, and removal must remain tenant-local and auditable.
OAuth/OIDC
External identity links must be tenant-scoped.
Provider identity uniqueness must include tenant, issuer/provider, and provider subject.
The same external account may create separate local identities in separate app tenants.
OAuth callbacks must cryptographically bind and validate the intended tenant/application.
Credentials, sessions, and tokens
Every credential belongs to one tenant-local entity and inherits its home-tenant boundary.
Sessions record and enforce the home tenant.
Access-token and shared-key verification must reject tenant mismatch.
API keys, access tokens, certificates, and sessions must never be implicitly reusable in another tenant.
Revocation, rotation, replacement, listing, and reveal operations must enforce the same boundary.
Authorization and administration
Tenant-local identities can receive tenant memberships, role assignments, direct policies, and group memberships only within their home tenant.
Assignment-time guardrails must reject cross-tenant subject/object/role combinations.
Client-provided tenant IDs must always be checked against authenticated identity context.
Platform administrators may manage tenant-local identities through platform-authorized control-plane operations, but a platform identity must not silently become an app user.
Tenant admins must not enumerate, read, mutate, or recover identities from sibling tenants.
Tenant lifecycle
Tenant suspension/freeze must block tenant-local authentication and authorization according to existing tenant lifecycle semantics.
Tenant deletion must revoke affected sessions and credentials as required by Atom's deletion rules.
Restore must follow existing credential/session/certificate restoration rules without reviving later manual revocations.
Purge must physically remove tenant-local identities and clean all authorization references through the canonical purge paths.
APIs, bootstrap, and events
GraphQL, REST/auth, and applicable gRPC surfaces must use the same tenant-qualified semantics.
Bootstrap must represent identity scope and tenant identity mode without introducing reusable production secrets.
Audit records and domain events must include sufficient tenant/entity context for investigation without leaking private credential material.
Mutation success events must remain transactional with their mutations according to the existing outbox rules.
Error responses should use stable, safe error categories and correlation metadata where supported.
Security invariants
The implementation is not complete unless all of these hold:
A tenant-local entity always has exactly one home tenant.
The same entity ID is never used as an app user in multiple tenants.
Tenant A credentials, sessions, JWTs, verification tokens, reset tokens, OAuth state, and invitations fail in Tenant B.
Cross-tenant role assignments, policies, memberships, and credential administration fail closed.
Tenant aliases, UUIDs, role IDs, entity IDs, redirect values, and request attributes cannot be tampered with to escape the home tenant.
Authentication responses do not reveal whether the same email exists in another tenant.
Platform identities and tenant-local identities cannot be confused by lookup fallback.
Online permission checks continue to hit the database; identity scoping does not move permissions into JWTs.
Backward compatibility and migration
Use an additive, staged rollout:
Add identity scope and tenant identity mode while preserving existing behavior.
Classify/backfill existing identities deterministically and report ambiguous cases.
Keep existing tenants in legacy/global mode until explicitly migrated.
Provision all new Atomic app tenants in tenant-local mode.
For an existing app migration, create a distinct tenant-local entity per existing app member.
Revoke old sessions at cutover.
Prefer tenant-specific password setup/reset instead of silently sharing credentials.
Do not copy API keys, access tokens, certificates, or active sessions.
Provide dry-run reporting, conflict detection, resumability, and an auditable migration result.
Define rollback boundaries before enabling tenant-local authentication for a migrated tenant.
Any decision to copy password hashes into independent credential records for migration must receive a separate security review. Even if copied initially, the records must be independent afterward.
Delivery and branch strategy
Create the integration branch:
feature/tenant-local-identities
Delivery rules:
Create each ticket branch from the latest integration branch.
Send every feature PR to feature/tenant-local-identities, not directly to main.
Keep schema changes additive until all runtime readers support the new model.
Require tests, migration notes, and API compatibility notes in every PR.
Do not merge incomplete authorization paths behind a default-on flag.
After all tickets and end-to-end tests pass, open one stabilization PR from feature/tenant-local-identities to main.
Atomic/Phoenix integration belongs in the separate Atomic repository and should use its own integration branch, for example feature/app-per-tenant. Branches and PRs cannot span repositories.
Proposed ticket and PR breakdown
ATOM-TLI-001 — Architecture decision and contracts
Record app-per-tenant and tenant-local identity decisions.
Finalize terminology, invariants, compatibility policy, and API behavior.
Decide legacy/global tenant behavior and migration boundaries.
At a glance
Introduce tenant-local human identities for the app-per-tenant model. A person who signs up in App A must not automatically exist in App B: the same email may create separate entities in each tenant, with independent credentials, sessions, verification, password recovery, roles, and lifecycle. All work should land through small PRs targeting the integration branch
feature/tenant-local-identities, followed by one stabilization PR from that branch tomain.Context and product decision
Atomic is adopting this application boundary:
Atom currently supports globally reusable identities. In that model, a user who signs up once can later receive access to another tenant without creating a separate app identity. That is not the desired behavior for app-per-tenant isolation.
Problem statement
For tenant-local apps, signing up in one app must not establish an identity or reusable credential in any other app.
Given the same email address in two app tenants, Atom must represent two independent identities:
The user must separately sign up or accept an invitation in App B. Passwords may coincidentally have the same value if chosen by the user, but they are stored and managed independently; resetting App A's password must not affect App B.
Goals
Non-goals
tenant_useras anEntityKind;humanremains the principal kind, while identity scope describes where the identity exists.Proposed domain model
Identity scope
Introduce an explicit identity scope with two exhaustive values:
platform: no home tenant; reserved for platform administration and approved system/service identities.tenant: a home tenant is mandatory; used for isolated app users.EntityKindremains responsible for what the principal is (human,service, and so on). Identity scope is responsible for where that identity exists.Tenant identity mode
Support an explicit tenant identity mode for controlled rollout:
global: legacy behavior for existing tenants.tenant_local: isolated identities for app tenants.All newly provisioned Atomic app tenants should use
tenant_local. Existing tenants should not silently change behavior during migration.Identity uniqueness
(tenant_id, normalized_email)among live identities.Functional requirements
Signup and open enrollment
Login
Email verification and password recovery
Invitations
OAuth/OIDC
Credentials, sessions, and tokens
Authorization and administration
Tenant lifecycle
APIs, bootstrap, and events
Security invariants
The implementation is not complete unless all of these hold:
Backward compatibility and migration
Use an additive, staged rollout:
Any decision to copy password hashes into independent credential records for migration must receive a separate security review. Even if copied initially, the records must be independent afterward.
Delivery and branch strategy
Create the integration branch:
feature/tenant-local-identitiesDelivery rules:
feature/tenant-local-identities, not directly tomain.feature/tenant-local-identitiestomain.Atomic/Phoenix integration belongs in the separate Atomic repository and should use its own integration branch, for example
feature/app-per-tenant. Branches and PRs cannot span repositories.Proposed ticket and PR breakdown
ATOM-TLI-001 — Architecture decision and contracts
ATOM-TLI-002 — Schema and typed domain model
ATOM-TLI-003 — Tenant-qualified repository and service primitives
ATOM-TLI-004 — Signup, verification, and default enrollment
ATOM-TLI-005 — Login, sessions, and JWT tenant binding
ATOM-TLI-006 — Recovery and credential lifecycle
ATOM-TLI-007 — Invitations and tenant-local membership lifecycle
ATOM-TLI-008 — OAuth/OIDC tenant-local linking
ATOM-TLI-009 — Authorization and assignment guardrails
ATOM-TLI-010 — Tenant lifecycle, restore, and purge
ATOM-TLI-011 — Existing-user migration tooling
ATOM-TLI-012 — Public contracts, bootstrap, and documentation
ATOM-TLI-013 — Adversarial and end-to-end validation
Acceptance criteria
cargo test,cargo clippy -- -D warnings, andcargo fmt --checkpass before the integration branch is proposed formain.Open decisions to resolve in ATOM-TLI-001