INV-ARC-019: Identity and Ownership Model
| Reference Number | Version | Effective Date | Supersedes | Authority Level |
|---|---|---|---|---|
| INV-ARC-019 | 1.6 | 2026-09-28 | 1.5 | Constitutional |
I. Purpose
This document defines the canonical identity and ownership boundaries for Classroom Token Hub (CTH) V2. It ensures a clear separation between global authentication principals, class-local operational actors, isolation boundaries, public reference keys, and presentation profiles.
II. Scope
This model applies system-wide to database schema design, session management, routing logic, and capability validation across all domains and feature executors.
III. Authority Level
Constitutional (Tier 1) within INV-ARC. Derived from INV-CORE-000 Section III.4, Principal and Actor Authority, and governed within the hierarchy described by INV-CORE-001. This protocol is subordinate to foundational invariants.
IV. Dependencies
docs/INVARIANT/CORE/INV-CORE-000_CORE_INVARIANTS.mddocs/INVARIANT/CORE/INV-CORE-001_CAPABILITY_BASED_ARCHITECTURE_AND_AUTHORITY_MODEL.mddocs/SPEC/SPEC-SEC-001_CREDENTIALS_AND_IDENTITY_LOOKUP_CODE_CONTRACT.md(incorporated; see Section VI)
V. Core Principle
Every runtime object has one authoritative owner.
Identity resolution must answer these questions separately:
- Who authenticated?
- Who acted?
- In which boundary?
- How is the actor referenced publicly?
- What capability is being granted?
No identifier answers more than its assigned question.
VI. Authentication Principal
users.id
users.id identifies the authenticated human principal.
users.id is a randomly generated UUID, stored as a PostgreSQL uuid with an explicit UNIQUE
constraint in addition to the primary key. It is never sequential and never reissued. A value that is
not a UUID names no principal, so an identifier issued by an earlier numbering, whether held in a
session or by an external system, cannot come to name a different person.
users owns:
- authentication credentials
- student account recovery
- session establishment
- last class context
- TOTP
- user roles
users does not own:
- attendance
- hall passes
- economy operations
- student interactions
- support tickets
- class-local claim verification artifacts
- display identity
Passkey metadata is stored in the unified passkey_credentials table for both teacher and sysadmin, and the owning
principal is always users.id.
Principal reference allowlist
Outside users, persisted references to users.id are restricted to:
seats.user_id: optional authenticated-principal binding to a class actor;classes.teacher_user_id: teacher principal ownership of a class;passkey_credentials.user_id: authentication credential ownership;recovery_requests.user_id: authentication recovery ownership.
No classroom record, author field, reviewer field, economic effect, or command
fingerprint may store an authentication principal reference. Classroom attribution
uses a seat under an explicit class_id. Operator review records its role and
review metadata without attaching an authentication principal to a class record.
Authentication and Identity may resolve a principal to its permitted seats or owned
classes; downstream domain records and selectors use that established class/seat.
Unclaim removes only the principal binding and recreates temporary claim material from teacher-entered names. The seat, profile, and class-scoped records survive. Delete destroys the seat. A bound seat restricts principal deletion until Identity explicitly detaches or destroys that seat; no User-to-Seat delete cascade is permitted. Deleting an orphaned principal must neither delete nor rewrite surviving seat-owned facts. No compatibility principal columns or aliases may remain in the shipping schema.
Incorporation of SPEC-SEC-001
This document assigns ownership of authentication credentials to users, but ownership alone does
not say how a credential is constructed, verified, or failed. That code-level contract is
SPEC-SEC-001, which by its own Section III is “binding only where incorporated by a governing
INV-*, DOM-*, or FEAT-* contract.” No DOM-* contract sits above credential material —
DOM-IDEN-002 and DOM-IDEN-003 name the fields (pin_hash, passphrase_hash) and deliberately
specify no algorithm. This section is therefore the incorporating authority.
The following parts of SPEC-SEC-001 are incorporated and binding on all code in this repository:
- Section V.1, Credential material. One canonical password-hashing primitive; the scrypt profile
scrypt:32768:8:1with a per-hash random salt in the encoded verifier format; no application secret as a KDF input; verifier-only storage; boolean-only verification results with algorithm and parameters centralized in the primitive rather than duplicated at call sites; and fail-closed, non-disclosing handling of unusable verifiers. - Section V.2, Lookup and PII representation, to the extent it governs identity lookup digests.
The storage forms those digests may take remain owned by
INV-ARC-018Section V, which incorporates the same specification for that purpose. - Section V.3.1, that authentication establishes
users.idand does not by itself establishseat_idorclass_id. This restates in code terms what Section V of this document requires: no identifier answers more than its assigned question. - Section V.4, Recovery and capability artifacts, consistent with Section XIV’s settled decision that recovery tokens are user-owned recovery capability.
Two consequences follow that are easy to get wrong in code and are stated here so they are not rediscovered as preferences:
PEPPER_KEYis not a credential input. It keys deterministic lookup digests only. Peppering passwords would couple every credential to a key whose rotation is a lookup-digest migration event, silently converting it into a credential-invalidating event.SPEC-SEC-001Section V.1.3 prohibits this, and Section V.1a requires the rotation semantics of each key to stay independent.- Because the KDF profile is fixed by specification rather than by a library default, code must not
rely on a dependency’s default to supply it. A default that happens to match is a coincidence
that a future upgrade may end, and
SPEC-SEC-001Section VII prohibits substituting parameters outside those it governs.
VII. Operational Actor
seats.id
seats.id identifies one actor operating inside one class.
seats owns actor attribution for:
- attendance
- hall passes
- payroll
- banking
- store
- rent
- insurance
- support tickets
- student detail pages
- classroom actions
seats also owns class-local claim state:
- claim lifecycle
- claim verification hashes
- claimed/unclaimed state
- authority to bind one user to one participant position in one class
Name-lookup hashes used during roster claim belong on the seat because they prove entitlement to a specific class-local participant position. They are not global user identity and they are not display identity.
Teacher seats and student seats follow the same actor model.
VIII. Isolation Boundary
classes.class_id
classes.class_id defines the boundary in which actors operate.
classes owns:
- membership boundary
- economy configuration
- policies
- feature enablement
- class labels
- classroom state
join_code is a human-facing class alias. It may locate a class for invitation,
claim, or routing convenience, but it MUST resolve to class_id before any
authority-sensitive operation.
IX. Public Actor Identity
seats.public_id
seats.public_id is the single canonical deidentified public actor identifier.
Rules:
- It is a UUID encoded as a 36-character string.
- It carries no human-readable or role-specific meaning.
- It is stable for the lifetime of the seat.
- It is safe to expose where a class-scoped actor must be referenced without exposing
the internal
seats.id. - It MUST resolve under the active
class_id. - It MUST NOT grant authority by itself.
Use seats.public_id for:
- actor URLs
- teacher-facing participant lookup
- support correlation
- ticket actor attribution
- class-scoped analytics drilldowns
The following are invalid v2 residue, not supported identity alternatives:
Admin.public_idAdmin.teacher_public_idStudent.opaque_reference- separate TLCP actor identity families
Student.internal_reference is separate internal-locator residue. The canonical internal reference MAY be a combination of users.id, class_id, or seats.id
Named cleanup debt:
TLCP-SCHEMA-001— complete as of 2026-06-02. Physical TLCP actor columns/API labels now useactor_public_idand storeSeat.public_id.
X. Display Identity
identity_profiles
identity_profiles owns presentation-only identity, including display names and
visible identity attributes.
Display identity does not participate in authentication, authority, ownership resolution, or actor lookup. Actor lookup here means resolving a participant in the classroom economy; it does not mean an external capability holder checking for a same-day hall-pass entry within an already established class scope.
Hall-pass verification SHALL first resolve a supplied join_code to class_id
and validate the teacher verification capability for that class. Only then may
Identity read identity_profiles rows scoped to that exact class_id and compare
normalized first and last names in memory. The verifier acts as an external
entity under the verification capability, not as an economic actor. The response
is limited to the supplied name’s presence in that class and same-day hall-pass
metadata; it exposes neither the roster nor multi-day history or internal
identifiers. Profile names select a verification subject; they never establish
class scope, authenticate a user, grant a capability,
or authorize mutation. Multiple matching student profiles are ambiguous even if
only one has a pass. No global name search or retained claim hash is permitted
for this verification. The approved encrypted name fields are sufficient; do not
create a second persistent name-lookup representation for hall-pass verification.
Student claim hashes, roster fingerprints, and deduplication codes are temporary
claim artifacts. Successful initial claim or authenticated class binding MUST
clear them atomically. Credential recovery does not recreate them. Teacher and
student name edits update IdentityProfile only; they do not alter seat identity
or regenerate claim material.
Do not store claim artifacts, credential artifacts, or class authority in
identity_profiles.
XI. Capability Tokens
Capability tokens grant permission to perform one defined action. They are not users, seats, classes, or public actor identities.
Examples:
hall_pass_verify_token- recovery tokens
- claim tokens
- passkey credential metadata
Every capability design must answer two questions explicitly:
- Which object owns the token?
- Which
class_id, if any, constrains its use?
Settled ownership:
- Student recovery capability is owned and executed by
users - passkey capability is owned by
usersand stored inpasskey_credentials(jointly used by teacher and sysadmin). Ownership is keyed byusers.id - roster claim verification is owned by
seats
Ownership of hall_pass_verify_token SHALL be owned by users where user_role = teacher. The hall_pass_verify_token is a form of capability token that authorize the holder to access hall pass verification feature.
XII. Roster Provisioning and Seat Claim
Roster upload provisions a future participant position. It does not create a student-authenticated principal.
When a teacher uploads a roster:
- A class exists or is created.
- A
seatsrow is provisioned and bound to the class, with no User binding. - An
identity_profilesrow is provisioned and bound one-to-one to the seat. - Claim artifacts are stored on the seat.
- No authentication principal or credentials are provisioned by roster import.
Claim/setup later proves entitlement to the seat and creates/binds the authentication principal under Identity authority. Authenticated class binding attaches an existing principal. An unclaimed seat does not depend on a User row.
XIII. Runtime Context
The authenticated principal and active classroom context remain separate:
users.idestablishes who authenticated.seats.idestablishes who acts.classes.class_idestablishes where the actor acts.seats.public_idexposes the actor externally without exposingseats.id.
The session MUST NOT infer an actor or boundary from display fields, role-specific public identifiers, legacy numeric participant IDs, or aliases once canonical context is available.
XIV. Settled Decisions
users.id= authentication principal, a random UUIDseats.id= operational actorclasses.class_id= isolation boundaryseats.public_id= canonical deidentified public actor identityidentity_profiles= display-only identityjoin_code= boundary alias that resolves toclass_id- roster lookup hashes = seat-owned claim verification artifacts
- recovery tokens = user-owned recovery capability
- passkey metadata = user-owned authentication capability
- credential construction and verification =
SPEC-SEC-001Section V.1, incorporated by Section VI of this document;PEPPER_KEYis a lookup-digest key and is never a credential input hall_pass_verify_token= single capability token registered to user_id whereuser_role = teacherto grant limited read access to same-day hall pass record
XV. Open Decisions
- invalid public identifier residue removal order
- bridge-table retirement order
XVI. Amendment
Revisions to this document must increment the version number, update the effective date, and remain consistent with foundational documentation standards and core invariants.
Last updated: 2026-10-14