Skip to content
INV-ARC-019: Identity and Ownership Model

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.md
  • docs/INVARIANT/CORE/INV-CORE-001_CAPABILITY_BASED_ARCHITECTURE_AND_AUTHORITY_MODEL.md
  • docs/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:

  1. Who authenticated?
  2. Who acted?
  3. In which boundary?
  4. How is the actor referenced publicly?
  5. 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:1 with 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-018 Section V, which incorporates the same specification for that purpose.
  • Section V.3.1, that authentication establishes users.id and does not by itself establish seat_id or class_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:

  1. PEPPER_KEY is 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-001 Section V.1.3 prohibits this, and Section V.1a requires the rotation semantics of each key to stay independent.
  2. 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-001 Section 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_id
  • Admin.teacher_public_id
  • Student.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 use actor_public_id and store Seat.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:

  1. Which object owns the token?
  2. Which class_id, if any, constrains its use?

Settled ownership:

  • Student recovery capability is owned and executed by users
  • passkey capability is owned by users and stored in passkey_credentials (jointly used by teacher and sysadmin). Ownership is keyed by users.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:

  1. A class exists or is created.
  2. A seats row is provisioned and bound to the class, with no User binding.
  3. An identity_profiles row is provisioned and bound one-to-one to the seat.
  4. Claim artifacts are stored on the seat.
  5. 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.id establishes who authenticated.
  • seats.id establishes who acts.
  • classes.class_id establishes where the actor acts.
  • seats.public_id exposes the actor externally without exposing seats.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 UUID
  • seats.id = operational actor
  • classes.class_id = isolation boundary
  • seats.public_id = canonical deidentified public actor identity
  • identity_profiles = display-only identity
  • join_code = boundary alias that resolves to class_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-001 Section V.1, incorporated by Section VI of this document; PEPPER_KEY is a lookup-digest key and is never a credential input
  • hall_pass_verify_token = single capability token registered to user_id where user_role = teacher to 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.

Edit canonical source on GitHub

Last updated: 2026-10-14