# Data ownership and authorization matrix

Authentication establishes *who is calling*. Authorization decides *what they
may read*. They are separate steps and both run on every protected request.

## Roles

| Role | May do | May **not** do |
|---|---|---|
| `data_owner` | Everything with their own threads: create, ingest, read raw text and every analysis, export, grant and revoke research access, inspect access history, request deletion | Reach another owner's thread |
| `application_client` | Submit and read on behalf of an explicitly delegated owner | Become the owner; manage grants; act without a delegation claim |
| `researcher` | Read exactly the fields named by an active, unexpired, unrevoked grant | Expand their own scopes; see raw text, units or embeddings without the specific scope; see the instructional reference model |
| `system_administrator` | Operational management; the administrator console when `ADMIN_CONTENT_ACCESS=true` | Reach conversation content through the ordinary API by virtue of the role |

Across the API, administrative status confers no content access: a request from
an administrator for thread content is denied by `decideThreadAccess` and
audited like any other. That rule is unchanged.

The administrator console (`/api/v1/admin/*`) is a deliberate, deployment-level
exception to it. Only when explicitly configured with `ADMIN_CONTENT_ACCESS=true` may an
administrator open any account's uploaded conversation, including raw text.
Two things hold regardless:

- Every cross-owner read writes an audit event against **the subject's** trail,
  not the administrator's, so the person whose conversation was opened sees it
  in their own access history.
- The default, `ADMIN_CONTENT_ACCESS=false`, denies content access:
  the console still lists accounts and thread metadata, and answers `403
  admin_content_access_disabled` for content, recording
  `admin.content_access_denied` against the subject.

Deployments that must not permit operator access to conversation text should keep
`ADMIN_CONTENT_ACCESS=false`. Existing deployments that explicitly set it to true
must change their configuration; the new default does not override an explicit opt-in.

## Scope matrix

| Field or resource | Owner | Delegate | Researcher requires |
|---|---|---|---|
| Thread metadata | ✓ | ✓ | any active grant |
| Turn identifiers, sequence, speaker type, content hash | ✓ | ✓ | any active grant |
| Participant identifiers | real | real | pseudonymous, always |
| Timestamps | ✓ | ✓ | `timestamps` |
| Provider token usage, model, provider | ✓ | ✓ | `token_usage` |
| Raw contribution text | ✓ | ✓ | `raw_text` |
| Pseudonymised text | ✓ | ✓ | `pseudonymized_text` |
| Thread aggregates, production, participation, trajectory | ✓ | ✓ | `aggregate_metrics` |
| Per-participant summaries, regression | ✓ | ✓ | `turn_metrics` |
| Latency distributions | ✓ | ✓ | `timestamps` |
| Speech acts | ✓ | ✓ | `speech_acts` |
| LCC curves | ✓ | ✓ | `lcc_curves` |
| LCC expectation matches | ✓ | ✓ | `lcc_expectation_matches` |
| LCC misconception matches | ✓ | ✓ | `lcc_misconception_matches` |
| LCC reference model content | ✓ | ✓ | **never** — a curve scope never exposes the instructional model |
| ICCC curves | ✓ | ✓ | `iccc_curves` |
| ICCC contribution-level evidence | ✓ | ✓ | `iccc_contribution_scores` |
| Semantic units | ✓ | ✓ | `semantic_units` |
| Embeddings | ✓ | ✓ | `semantic_representations` |
| Export | ✓ | ✓ | `export` |
| Grants: create, list, revoke | ✓ | ✗ | ✗ |
| Access history | ✓ | ✗ | ✗ |
| Deletion request | ✓ | ✗ | ✗ |

An ungranted field is **absent** from the response, not `null`. Its presence
cannot be inferred.

## Rules

- **Deny by default.** No grant, no access.
- **Embeddings are sensitive derived data.** Access to numeric curves never
  implies access to the representations behind them; `semantic_representations`
  is a separate scope.
- **Grants are purpose-specific and revocable.** Revocation takes effect on the
  next request; there is no grace period and no cache.
- **Owners can always see who has access** and every read that was performed.
- **Every research read and export is audited**, including denials.
- **Version 1 assumes one primary human data owner per owner-scoped thread.**
  In a multi-human discussion every human remains a data subject with rights
  defined by policy; one participant's ownership is not consent to disclose
  another participant's private data. Participant-level consent metadata is
  stored so a multi-owner policy can be added later without changing analytical
  identifiers.

## Pseudonymisation

Research views replace participant identifiers with `p_<hmac>` derived from the
thread identifier, the participant identifier and `OWNER_KEY_SALT`. Pseudonyms
are stable within a thread and differ across threads, so a participant cannot be
tracked between threads by identifier alone.

`pseudonymized_text` additionally masks email addresses, phone numbers and URLs
in free text. This is a mitigation, not a guarantee of de-identification:
conversational text can be re-identifying through content alone.
