# Domain Model

This document defines the main nouns in `nythos-core` and the invariants they preserve.

## Typed IDs

`nythos-core` uses typed newtype wrappers over `Uuid` for all primary identity references:

- `UserId`
- `TenantId`
- `SessionId`
- `RoleId`

Purpose:

- prevent mixing unrelated IDs by accident
- make function signatures explicit
- keep serialization and persistence decisions outside the type identity itself

Current behavior:

- each ID wraps a `Uuid`
- each ID exposes `new`, `generate`, `as_uuid`, and `into_uuid`
- each ID supports parsing, display, equality, ordering, and serde

## Value Objects

## `Email`

Validated email value object.

Rules:

- created through validation, not raw public string assignment
- normalized consistently for lookup and comparison
- stored and compared in a way that supports reliable lookup

Current normalization behavior:

- trims surrounding whitespace
- lowercases the full address
- requires a single `@`
- rejects empty local or domain parts
- rejects whitespace inside the address
- requires the domain portion to look structurally valid

## `Password`

Represents validated raw password input.

Rules:

- only used at trust boundaries or during credential verification flows
- should not be confused with a stored hash
- should be handled carefully in APIs and logs

Current validation behavior:

- must be between 8 and 1024 characters
- cannot be empty
- cannot contain newlines
- remains distinct from `PasswordHash`

## Identity

## `User`

Represents an account in the auth domain.

Current fields:

- `UserId`
- `Email`
- `UserStatus`
- `created_at`

Current status values:

- `Active`
- `Locked`
- `Disabled`

Invariants:

- user identity is stable through `UserId`
- auth flows check status before issuing new sessions
- status is domain state, not an HTTP concern

## `Tenant`

Represents a tenant boundary.

Current fields:

- `TenantId`
- slug
- optional `TenantSettings`

Current notes:

- slug validation stays lowercase ASCII plus `-`
- tenant identity is stable through `TenantId`
- RBAC is tenant-scoped
- user lookup operations in the core are tenant-aware
- `TenantSettings` is optional tenant metadata stored as a string map

## Auth Concepts

## `PasswordHash`

Represents a stored password hash.

Core expectation:

- hashes use Argon2id semantics

This stays abstracted behind `PasswordHasher`, but the intended outer
implementation is Argon2id. The core is not designed around insecure algorithm
swapping.

## `AccessToken`

Represents a short-lived signed token with JWT-like semantics.

Invariants:

- signed, not opaque
- short-lived
- derived from `Claims`
- verification happens through `TokenSigner`

The core treats it as a token value, not as an HTTP bearer header.

## `Claims`

Represents the identity facts embedded into an access token.

Current fields:

- subject `UserId`
- `TenantId`
- `TokenPurpose`
- `issued_at`
- `expires_at`

Current notes:

- claims do not currently embed roles or permissions
- login and refresh load tenant-scoped roles before signing a new access token
- claims must not weaken the tenant boundary
- claims do not currently carry `SessionId`
- this is a known design gap relative to `RevocationChecker`, which requires a `SessionId`
- request-time revocation checks therefore cannot be performed from verified `Claims` alone; outer layers currently need another way to recover or track session identity

## `TokenPurpose`

Represents why a signed token exists.

Current variant:

- `Access`

## Session

## `Session`

Represents a refresh-capable authenticated session.

Current fields:

- `SessionId`
- `UserId`
- `TenantId`
- `issued_at`
- `expires_at`
- revoked state

Invariants:

- a session belongs to exactly one user
- a session belongs to exactly one tenant
- revocation is explicit state, not inferred only from token expiry
- expired sessions must not refresh

`Session` is the durable unit behind refresh flows and revocation checks.

## `RefreshToken`

Represents an opaque refresh credential.

Invariants:

- opaque in the core model
- not a JWT in the core model
- linked to a session through storage, not self-describing claims
- rotated on every successful refresh

The previous refresh token becomes invalid after rotation.

## RBAC

## `Permission`

Represents a concrete authorization capability.

Current representation is a lowercase ASCII string with a namespace separator `.`.

Examples:

- `users.read`
- `users.write`
- `sessions.revoke`

Permissions reject empty values, missing namespace separators, and invalid character shapes.

## `Role`

Tenant-scoped authorization role.

Current fields:

- `RoleId`
- `TenantId`
- role name
- explicit permission set

Invariant:

- a role exists inside exactly one tenant scope

## `RoleAssignment`

Represents a relation between a user and a role within a tenant.

Current fields:

- `TenantId`
- `UserId`
- `RoleId`

Invariant:

- assignment scope must match both the role tenant and the request tenant

## `RoleRegistry`

Represents the available roles for one tenant.

Current shape:

- one `TenantId`
- a list of `Role` values
- validation that every role belongs to the same tenant

Invariant:

- all role and permission lookups are tenant-scoped

There is no global admin concept in `nythos-core`.

## Relationships

- a `User` can have many `Session` records
- a `Session` references exactly one `User` and one `Tenant`
- a `RoleAssignment` links a `User` to a `Role` within one `Tenant`
- an `AccessToken` is issued from `Claims`
- a `RefreshToken` is an opaque handle for session continuation

## Core Invariants

- all role lookups are tenant-scoped
- a session belongs to exactly one user and one tenant
- refresh tokens are opaque and rotated on every successful refresh
- access tokens are short-lived and signed
- claims carry subject, tenant, purpose, and issue/expiry timestamps
- raw passwords and stored hashes are separate types
- the core never maps these concepts to HTTP or transport details
