# Review, Compliance, and Trust & Safety

This page documents the governance spine that sits behind every Oshun V1
surface: the **review package** lifecycle that gates content and model releases,
the **immutable audit and provenance** layer that records what happened, and the
**Trust & Safety** subsystem that moderates content, assistant behavior,
persona/voice/avatar usage, generated media, support contact, and educational
integrity. It serves operators, reviewers, crisis-trained responders, and the
compliance/legal functions; in the V1 stack the deterministic policy and
state-machine cores live in `@oshun/trust-safety`, `@oshun/review-persistence`,
and `@oshun/audit-platform`, while the runtime enforcement (real classifiers,
live persistence, cross-domain fan-out) is the platform's job and is wired in at
the application boundary.

The honest framing up front: most of what this page describes is **implemented
as deterministic, fully-tested pure-function and state-machine logic** — the
policy taxonomy, severity SLAs, decision/appeal lifecycle, crisis cascade,
retention constants, and review state machine are real code with real exported
symbols. The things that are _runtime_ responsibilities — the actual ML
classifiers that produce a `PolicyHit`, the durable append-only store that backs
the hash-chained audit log, the real Redis bus the crisis worker binds to — are
honest seams: the cores compute and validate, the runtime persists and enforces.
Where a capability is provider-gated or runtime-owned, this page says so.

> **Companion pages.** The privacy/consent/DSAR/residency half of governance has
> its own page —
> [Privacy, Consent, Data Portability, and User Controls](./privacy-consent-portability.md)
> — grounded in `@oshun/privacy`. The customer-ops/entitlement/billing half
> lives in
> [Support, Entitlements, Billing, and Customer Operations](./support-billing.md)
> and
> [Crypto Payments — Non-Custodial Entitlement Settlement](./crypto-payments.md),
> grounded in `@oshun/billing-support` and `libs/aje/`. This page focuses on the
> **review, audit/provenance, and trust-and-safety** layer and cross-links to
> those siblings where the boundaries touch. The architecture companion is
> [Trust, Safety, and Privacy](../architecture/trust-safety-and-privacy.md).

---

## Where this sits in V1

Three libraries carry the bulk of the governance logic, and they are
deliberately separated by responsibility:

| Library                          | Package                     | Owns                                                                                                                                                                                 |
| -------------------------------- | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `libs/shared/review-persistence` | `@oshun/review-persistence` | Review package persistence, stage graphs, decision lifecycle, delegation, escalation, release-validity, artifact templates, prioritization, audit linkage                            |
| `libs/shared/audit-platform`     | `@oshun/audit-platform`     | Immutable hash-chained audit log, retention, compliance attestations, exception approvals, provenance/rights, watermark verification, synthetic-media labeling, automation authority |
| `libs/oshun/trust-safety`        | `@oshun/trust-safety`       | Policy taxonomy, severity/SLA, decision/appeal lifecycle, crisis cascade, abuse-pattern detection, operator dashboards, eval/release gates                                           |

The split matters: a Trust & Safety `SafetyDecision` (a moderation verdict) is a
different object from a review-persistence `ReviewDecision` (a creator/editorial
approval). Both record rationale, both emit audit events, both have appeal/
override lineage — but they govern different flows. Review packages gate _what
gets published_; Trust & Safety governs _what gets enforced after publication or
during a session_.

---

## Part 1 — Review, Audit, Compliance, Rights, and Provenance

This is the editorial/operator-facing governance layer. It is the machinery that
lets a piece of content, a model, or a generated artifact move from "submitted"
to "released" with a recorded, defensible decision trail behind it.

### Review packages and the decision lifecycle

A **review package** is the unit of governed release.
`@oshun/review-persistence` persists packages, their **stage graphs** (each
package moves through one or more ordered stages), and a **decision lifecycle**
that the `DecisionLifecycleService` (`decision-lifecycle.ts`) orchestrates over
the repository primitives. The service is composed entirely from
`ReviewPackageRepository` methods — it never reaches into the underlying store —
so any backend implementing the repository interface (the in-memory test double
in `in-memory.ts`, or the Prisma-backed production store) behaves identically.

The lifecycle enforces the ADR-0029 rules in real code:

- **Outcomes are constrained.** A `ReviewDecision.outcome` is one of `approved`,
  `rejected`, or `changes_requested` — the `OUTCOME_TO_TERMINAL_STAGE_STATE` map
  translates each non-approval outcome into the matching terminal stage state
  (`approved` → `approved`, `rejected` → `rejected`, `changes_requested` →
  `changes_requested`).
- **Threshold approvals.** `decideNextStageState` increments `currentApprovals`
  on each `approved` decision and only moves the stage to `approved` when
  `currentApprovals >= requiredApprovals` — so a two-signoff stage genuinely
  requires two approvals before it finalizes.
- **Rationale and attribution are mandatory.** Every `ReviewDecision` records
  `rationale`, `decidedById`, and `decidedRole`.
- **Override preserves lineage.** `recordOverride` marks the prior decision
  `final: false` (superseded) and records the new decision with
  `priorDecisionId` set, so the chain is never lost.
- **Reopen preserves lineage.** `recordReopen` clears the prior decision's
  `final` flag, transitions the stage back to `in_review`, resets
  `currentApprovals` to 0, and appends a `reopen` decision carrying
  `reopenedFromDecisionId`. By convention a reopen records
  `outcome: 'changes_requested'` because the stage is back in active review.
- **Every decision emits a structured audit event.** Each transition appends a
  `ReviewPackageAuditEvent` with `action: 'review.decision_recorded'`,
  `outcome: 'success'`, and the actor/stage/decision references — the audit
  linkage is not optional bookkeeping, it is part of the same atomic write.

The repository surface (`types.ts`) gives operators the queue they actually
work: `ReviewPackageFilter` narrows by `primaryDomain`, `touchesDomain`,
`status`, `priority`, `artifactType`, `releaseChannel`, `escalated`,
`governanceTagged`, free-text `search`, and time windows; `ReviewPackageSort`
orders by `priority` (critical-first per the `PRIORITY_WEIGHTS` map —
`critical: 3, high: 2, medium: 1, low: 0`) or recency; and `ReviewPackageList`
returns cursor-paginated results with a stable `total` so the UI can render
queue depths. Cursor pagination is chosen deliberately — operator queues are
deep and ordered by recency, and cursors stay stable under concurrent writes
where page offsets would skip or repeat rows.

The package also carries **release validity** (`release-validity.ts`),
**delegation policy** (`delegation-policy.ts`), **stage-graph assembly**
(`stage-graph-assemble.ts`), **artifact templates** (`template-registry.ts`),
and **audit linkage** (`linkage.ts`) — the full set the features backlog calls
out for governed artifact release.

### Immutable audit, hash-chaining, and retention

`@oshun/audit-platform` is the system of record. Its defining property is
**tamper-evidence via a hash chain** (`hash-chain.ts`): every audit event
produces a chain entry of the form

```
eventHash = sha256(canonicalSerialize(event))
entryHash = sha256(previousChainHash || eventHash)
```

(using `@noble/hashes` for the SHA-256 primitive). Chain entries live in an
append-only sidecar log (`AuditChainLog`); verifying a range re-runs the chain
forward from an anchor and compares, surfacing an `AuditChainMismatch`
(`chain-hash-mismatch` and friends) and raising `AuditChainTamperDetectedError`
at the exact position any record was altered. The store-layer append-only
guarantee is the first line of defense; the hash chain is the second.

**Retention** (`retention.ts`) is a real policy service, not a constant table:
`RetentionPolicyService` evaluates each record against a `RetentionPolicy`,
classifying it into a `RetentionTier` (`hot` | `warm` | `cold` |
`past_retention`) with a `RetentionRedactionMode` (`none` | `pii` | `full`), and
produces a `RetentionSweepReport`. A record under legal hold "stays hot forever
until the hold is lifted" — the retention sweep cannot age out held data.

**Compliance attestations** (`compliance-attestation.ts`) model a real
attestation workflow: an `AttestationDefinition` with a `cadence` and a `scope`
(`global` | `per-domain` | `per-tenant`), `AttestationEvidenceRequirement`s, an
`AttestationRun` that moves through statuses, signed `AttestationSignature`s,
and an `AttestationDecision` of `approved` | `approved_with_findings` |
`rejected`. `computeClosureHash` seals a completed run. **Exception approvals**
(`exception-approval.ts`) provide the documented escape valve when a control
must be waived, with its own approval trail.

### Human-in-the-loop authority and mandatory approval

The features backlog calls for human authority rules, automation
classifications, and mandatory human approval — and they are real:

- **Automation authority** (`automation-authority.ts`) defines an
  `AutomationPosture` per `ArtifactClass`, modulated by a `SensitivityContext`
  (`Audience` ∈ `internal | partner | public`, `DistributionScale` ∈
  `dev | staging | production`) through `AutomationPolicyContextMultiplier`s.
  The `AutomationPolicyRegistry` resolves an `AutomationDecision` — i.e. how
  much of a release may be auto-finalized vs. must route to a human — based on
  what is being released and how widely.
- **Mandatory human approval** (`mandatory-human-approval.ts`) is a hard gate: a
  `MandatoryHumanApprovalGate` produces a `FinalizationDecision` of `allowed` or
  `blocked`, with a `FinalizationBlockReason` when a required
  `HumanApprovalRecord` (signed by a `HumanApprovalSigner`) is absent. The
  integration test `blocked-auto-finalization.integration.spec.ts` exists
  precisely to prove the block fires.
- **Manual override** (`manual-override.ts`) lets an authorized operator
  override an automated outcome _with rationale_, and `escalation-rules.ts`
  encodes when a package must escalate.

### Rights, provenance, and synthetic-media labeling

The provenance layer attaches verifiable claims to generated media and verifies
them on the way back out:

- **Provenance attachment** (`provenance-attachment.ts`) and **source-asset
  lineage** (`source-asset-lineage.ts`) bind a generated artifact to its inputs;
  **training-data source evidence** (`training-data-source-evidence.ts`) records
  what a model was trained on for the rights report.
- **Watermark verification** (`watermark-verification.ts`) runs pluggable
  `WatermarkDetector`s — a `MetadataTagDetector` and a `C2paDetector` — and
  resolves a `WatermarkVerdict` from `WatermarkSignalReport`s. This is an honest
  detector-driven seam: the verdict reflects what the detectors actually found,
  including "no watermark present," rather than asserting a watermark exists.
- **Synthetic-media labeling** (`synthetic-media-labeling.ts`) drives a
  `LabelRequirement` → `LabelRenderCheck` → `LabelVerificationReport` flow so
  the AI-content disclosure is provably rendered, and **provenance badges**
  (`provenance-badges.ts`) compose end-user-facing `Badge`s (with a
  `BadgeSeverity` of `info | caution | warning`) "where they provide real value"
  — the `ProvenanceBadgeComposer` decides which badges to surface rather than
  blanket-labeling everything.
- **Content/model licensing review** (`content-license-review.ts`,
  `model-license-review.ts`) and the export surfaces — **rights report**
  (`rights-report.ts`), **evidence export** (`evidence-export.ts`), **audit
  investigation export** (`audit-investigation-export.ts`) — round out the
  source-lineage → rights-report → audit/privacy/compliance export chain the
  backlog enumerates.

These review/audit/provenance surfaces are exercised by the operator and admin
consoles (see [Admin Products — Web and Mobile](./admin-products.md) and
[Tenant, Institution, and Operator Toolkit](./tenant-toolkit.md), whose
audit-log explorer is backlog §20.4); the editorial release flow they gate is
described in
[Collaboration, Review, and Templates](./collaboration-and-templates.md).

---

## Part 2 — Trust & Safety

Trust & Safety (backlog §21) owns moderation, abuse, crisis, and appeals across
content, assistant behavior, persona/voice/avatar usage, generated media,
support contact, and educational integrity. Policies are taxonomized,
severity-classed, escalation-routed, audited, and evaluated. Everything below is
real code in `@oshun/trust-safety`; the parts that are runtime-owned (the actual
classifiers that emit a `PolicyHit`) are called out explicitly.

### Policy taxonomy

`policy-taxonomy/categories.ts` defines the canonical category enums. A
`PolicyHit` carries a discriminated-union `PolicyCategory` (`family` +
`category`), an optional `subClass`, a `confidence` in `[0, 1]`, an
`evidenceExcerpt`, a `detectorVersion`, and a `jurisdiction`. The classifier
that _produces_ a hit is the runtime's job; this module's job is to **validate**
it (`validatePolicyHit`) and **map it to a deterministic enforcement action**
(`mapHitToAction`).

| Family (`PolicyCategory.family`) | Real categories (`*_CATEGORIES` arrays)                                                                                                                                                                                                                           |
| -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `content`                        | `hate`, `harassment`, `sexual-content`, `graphic-violence`, `self-harm`, `illegal.csam`, `illegal.terrorism`, `illegal.weapons`, `illegal.drugs`, `defamation`, `doxxing`, `electoral-disinformation`, `health-disinformation`, `scams`, `financial-exploitation` |
| `assistant-behavior`             | `persona-drift`, `jailbreak`, `prompt-injection`, `deceptive-roleplay`, `off-policy-advice`, `harmful-instruction-generation`, `political-influence`, `unauthorized-memory-access`                                                                                |
| `synthetic-media`                | `deceptive-realism`, `identity-impersonation`, `nonconsented-likeness`, `deepfake`, `voice-clone-abuse`, `watermark-removal`                                                                                                                                      |
| `educational`                    | `fabricated-citations`, `retracted-source-use`, `plagiarism`, `exam-integrity-violation`, `answer-key-leakage`, `standards-alignment-misrepresentation`                                                                                                           |
| `support-ops`                    | `operator-harassment-of-customer`, `unauthorized-data-access`, `refund-abuse`, `social-engineering`                                                                                                                                                               |

Sexual content carries a finer `SEXUAL_NSFW_CLASSES` subclass: `suggestive`,
`explicit-adult-consensual`, `explicit-adult-nonconsensual`, `minor-sexual`. The
validator enforces real cross-field invariants — a subclass may only attach to
`sexual-content`, and `illegal.csam` _requires_ the `minor-sexual` subclass
(`csam-requires-minor-subclass`).

`mapHitToAction` is the deterministic enforcement core. It returns an
`EnforcementAction` — one of `block`, `downgrade-visibility`, `warn-author`,
`route-for-human-review`, or `no-action` — driven by curated key sets and the
hit's confidence:

- **Always hard-block, regardless of confidence:** the `minor-sexual` and
  `explicit-adult-nonconsensual` subclasses, plus the `HARD_BLOCK_KEYS` set
  (`content:illegal.csam`, `illegal.terrorism`, `illegal.weapons`, the
  synthetic-media abuses `nonconsented-likeness` / `voice-clone-abuse` /
  `deepfake` / `watermark-removal`, and the support-ops
  `unauthorized-data-access` / `social-engineering`).
- **Jurisdiction-bound:** `content:illegal.drugs` only hard-blocks when a
  `JurisdictionPolicyOverlay` _defines_ it in that jurisdiction; otherwise it
  routes for human review. An overlay can also _elevate_ an otherwise
  soft-blocked category.
- **Confidence-gated:** below `minConfidenceForHumanReview` the action is
  `no-action`; jailbreak/prompt-injection block at or above
  `minConfidenceForAction` and route for review otherwise; soft-block and
  downgrade keys behave the same with their respective fallbacks.
- **Self-harm** is special-cased to `downgrade-visibility` (it flows through the
  crisis pathway rather than a blanket block — see below).

Why a separate map per key set rather than one severity score? Because
enforcement is not monotonic in confidence: a high-confidence `scams` hit
_downgrades visibility_ while a low-confidence `illegal.csam` hit still
_blocks_. Encoding that as explicit category→action policy makes the behavior
auditable and testable rather than emergent from a threshold.

### Severity classes and SLAs

`severity/severity.ts` defines `SEVERITY_CLASSES = ['P0', 'P1', 'P2', 'P3']` and
a machine-readable `SlaBudget` per class. The features prose describes the SLAs;
the actual enforcing shape — `triageBudgetSeconds`, `actionBudgetSeconds`,
`requiresParallelIncident`, `requiresPostIncidentReview`, `weeklyAggregateOnly`,
and `minReviewerTier` — is the real `SLA_BY_SEVERITY` constant:

| Severity                               | Triage budget        | Action budget        | Min reviewer tier | Parallel incident | Post-incident review | Weekly-aggregate only |
| -------------------------------------- | -------------------- | -------------------- | ----------------- | ----------------- | -------------------- | --------------------- |
| **P0** — imminent harm                 | 5 min (`5 * 60`)     | 15 min (`15 * 60`)   | `crisis-trained`  | yes               | yes                  | no                    |
| **P1** — high severity / ongoing abuse | 30 min (`30 * 60`)   | 2 h (`2 * 3600`)     | `senior`          | no                | yes                  | no                    |
| **P2** — moderate / low-confidence     | 8 h (`8 * 3600`)     | 48 h (`48 * 3600`)   | `standard`        | no                | no                   | no                    |
| **P3** — informational / pattern-only  | weekly (`7 * 86400`) | weekly (`7 * 86400`) | `standard`        | no                | no                   | yes                   |

`classifySeverity` is the deterministic classifier. The `minReviewerTier` is a
real gate — a P0 case may not be triaged by a `standard` reviewer — and
`requiresParallelIncident` / `requiresPostIncidentReview` are obligations the
runbook hangs off, not commentary.

Escalation is encoded, not narrated:

- The `minor-sexual` and `explicit-adult-nonconsensual` subclasses are **always
  P0** regardless of threat signals (the "non-consensual sexual imagery" /
  "child-protection signals" P0 classes).
- A **credible-threat triple** —
  `hasNamedTarget && hasCrediblePlan && hasGeographicSpecificity` —
  auto-escalates any hit to **P0** (the "credible violence-to-others" class).
- Base P0/P1 membership comes from `P0_CATEGORIES` (`content:illegal.csam`,
  `content:self-harm`, `synthetic-media:nonconsented-likeness`) and
  `P1_CATEGORIES` (hate, harassment, identity-impersonation, voice-clone-abuse,
  deepfake, exam-integrity-violation).
- **P2 → P1 on repeat:** `shouldEscalateP2OnRepeat` escalates user-targeting
  harms (harassment/hate/doxxing/defamation, plus jailbreak/prompt-injection)
  when `priorOccurrencesInLast30Days >= 1`.

`evaluateSla` computes triage/action breach against the live timer, and there is
a _separate_ `AppealSlaBudget` table (`APPEAL_SLA_BY_SEVERITY`) — appeals get
their own, slower budgets (P0 appeal: 30 min triage / 4 h second-review / 6 h
notify) because an appeal of a P0 decision is itself urgent but not the same
clock as the original imminent-harm response.

### Decision classes and appeals

`decisions/decisions.ts` taxonomizes decisions into appealable and
non-appealable sets:

- **`APPEALABLE_DECISION_KINDS` (8):** `content-removal`,
  `account-suspension-under-30-days`, `persona-suspension`, `comment-hiding`,
  `recommendation-suppression`, `voice-entitlement-suspension`,
  `avatar-entitlement-suspension`, `billing-action-reversal`.
- **`NON_APPEALABLE_DECISION_KINDS` (5):** `csam-removal`,
  `credible-threat-suspension`, `legal-hold-action`, `court-ordered-action`,
  `permanent-ban`.

Each non-appealable kind carries a real operator runbook entry in
`NON_APPEALABLE_RUNBOOK` — `mandatoryReportRequired`, the
`legalContactRoleRequired` (`trust-safety-legal` | `incident-response` |
`court-liaison`), a `retentionDays` (CSAM and legal-hold at `365 * 7`,
court-ordered at `365 * 10`), and a `summary`. For example, `csam-removal`
requires a mandatory NCMEC/equivalent report, preservation under legal hold, and
two-reviewer signoff.

A `SafetyDecision` records `kind`, `issuedByReviewerId`, `rationale`,
`severity`, and a
`twoReviewerSignoff: { secondReviewerId, atUnixSeconds } | null`.
`validateDecision` enforces that two-reviewer signoff is present whenever it is
required — for **P0**, for the `TWO_REVIEWER_DECISION_KINDS` set
(`csam-removal`, `credible-threat-suspension`, `court-ordered-action`,
`permanent-ban`), for large accounts, public figures, decisions involving a
cloned voice or avatar, or decisions overriding Lilith policy — and that the
second reviewer is not the issuer (`self-signoff`).

The **appeal lifecycle** is a real state machine over `APPEAL_STATES`
(`received → triaged → in-review → decided → notified → audited`) with verdicts
`uphold | overturn | partial`:

1. `fileAppeal` refuses non-appealable decisions outright
   (`decision-not-appealable`) and stamps a `cooledOffUntilUnixSeconds`.
2. `triageAppeal` enforces **reviewer rotation** — the triage reviewer may not
   be the original (`reviewer-rotation-required`) — and a **cooling-off** wait
   (`cooling-off-not-elapsed`).
3. `decideAppeal` enforces that the second reviewer is neither the original nor
   the triage reviewer, and requires a rationale.
4. `notifyAppeal` records the `notificationChannel` (`email` | `in-app` |
   `postal`), and `auditAppeal` binds an `auditTrailId`.

This is why the appeal flow is trustworthy: a single reviewer can never carry an
appeal end-to-end, and the state machine refuses to skip steps.

### Crisis handling and the cross-surface crisis frame

`crisis/crisis.ts` models detection, routing, region-aware resource delivery,
post-crisis check-in, and mandatory reporting. A `CrisisDetection` has a
`CrisisDetectionSource` (`model-classifier`, `user-self-report`,
`third-party-report`, `pattern-match-across-sessions`,
`support-copilot-escalation`) and a `CrisisKind` (`suicidal-ideation`,
`self-harm-imminent`, `violence-to-others-credible`, `child-protection-signal`,
`domestic-violence-signal`, `medical-emergency`).

`routeCrisis` assigns the dedicated `crisis-trained-24x7` queue (bypassing
normal severity), and **selects resources by region, then language, then
accessibility** — it filters the catalog to the caller's region (or `global`),
prefers locale-matched resources (BCP-47 language-family aware), and ranks the
top five by accessibility-tag overlap then language specificity.
`mandatoryReportRequired` fires only for the `child-protection-signal` /
`violence-to-others-credible` kinds in the `MANDATORY_REPORTING_REGIONS` set
(`US`, `CA`, `UK`, `AU`, `EU`). `createMandatoryReport` resolves a per-region
`LegalContact` from the registry (e.g. NCMEC-class authorities), `planCheckIn`
schedules an opt-in follow-up and a `noRecommendationCooldown`, and
`buildCrisisAuditRecord` retains the event (7 years for child-protection
signals, 5 otherwise).

The standout piece is the **cross-surface crisis-frame cascade**
(`crisis/crisis-frame-cascade.ts`), the concrete implementation of a named exit
criterion. Activating a crisis frame for one surface previously did not
propagate; this module fixes that with a port-based publish/consume design:

- **The event:**
  `LILITH_CRISIS_FRAME_ACTIVATED_EVENT = 'lilith.crisis_frame.activated'` — the
  exact topic string the architecture's sequence diagram names.
- **The surfaces:**
  `CRISIS_FRAME_SURFACES = ['psyche', 'lilith-video', 'tara', 'iris', 'assistant']`
  — the closed set every frame projects into.
- **Non-overridable directives:** the payload `LilithCrisisFrameActivatedEvent`
  always carries `haltSynthesis: true` and `suspendMemoryWrites: true`. A
  per-surface projector cannot soften them.
- **The flow:** `activateLilithCrisisFrame` publishes the event over an injected
  `CrisisFramePublishPort`; `consumeCrisisFrameActivation` fans it out to each
  registered `CrisisFrameProjector`, attempting each surface independently so
  one surface failing to enter its frame never blocks the others (every outcome
  — `projected` | `skipped` | `failed` — is recorded so a recovery sweep can
  re-project the failures).

Crucially, the crisis domain stays **free of `@oshun/event-bus`** — it depends
on ports only. The real Redis binding lives in `crisis/crisis-frame-worker.ts`:
`createEventBusCrisisFramePublishPort(bus)` adapts an `IEventBus` to the publish
port, and `subscribeCrisisFrameWorker(bus, deps)` subscribes a worker to
`lilith.crisis_frame.activated` and routes each event's payload through the
consumer fan-out, returning the `Subscription` so the caller can stop it. This
is the seam between the pure, testable cascade and the live message bus. The
per-surface behavior (Psyche halting synthesis, the assistant switching to a
crisis-aware persona) is documented in
[Lilith Persona Policy](./lilith-persona-policy.md) and
[Psyche Real-Time Runtime](./psyche-realtime-runtime.md); the scene-level
mid-stream crisis frame is in
[Scene Safety, Determinism, Provenance, and Cue Privacy](./scene-governance.md).

### Abuse-pattern detection

`abuse-patterns/abuse-patterns.ts` covers five real detectors:

- **Repeat-offender ladder:**
  `REPEAT_OFFENDER_STAGES = ['warn', 'restrict', 'suspend', 'ban']` with
  `STAGE_THRESHOLDS` (`warn: 1, restrict: 3, suspend: 5, ban: 8`).
  `applyOffense` discounts false-positive-rich categories (`FP_RICH_CATEGORIES`
  — health/electoral disinformation, off-policy advice) before escalating, so a
  noisy classifier does not over-punish.
- **Coordinated-abuse clustering:** `detectCoordinationClusters` buckets by
  `(promptFingerprint, targetEntityId)`, requires a minimum distinct-user count
  inside a time window, and scores `confidence` from user count, IP/device
  _non-diversity_ (shared infrastructure raises suspicion), and tightness of the
  time window.
- **Bot/scripted signals:** `classifyBotBehavior` flags
  `rapid-retry-after-refusal` (≥5 in 5 min), `prompt-permutation` (≥10 in 5
  min), and any `tool-call-exfiltration-attempt`.
- **Classifier drift:** `detectClassifierDrift` compares baseline vs. recent
  false-positive/false-negative rates per `(classifierId, domain)` and emits a
  `DriftAlert` when the absolute delta exceeds the configured threshold.
- **Tenant-aggregate outliers:** `classifyTenantAbuseRates` computes a z-score
  per tenant against the platform mean and classifies `normal | spike | anomaly`
  (z ≥ 2 / z ≥ 3).

### Operator surfaces

`operator-surfaces/operator-surfaces.ts` builds the moderation cockpit:

- **`buildAppealEvidencePack`** assembles the full context an appeal reviewer
  needs — original content, `policyHits`, `classifierOutputs`
  (id/score/version), prior incident and appeal counts, account context (age,
  tenant, roles), and persona context (persona id, tone-policy id) — plus the
  resolution rationale.
- **`buildRepeatOffenderTimeline`** merges policy-hits, decisions, appeals, and
  crisis events into one reverse-chronological timeline with the consequence
  ladder applied.
- **`buildSafetyDashboard`** produces a `SafetyDashboardSnapshot`: per-class
  volume, **per-severity SLA attainment** (triaged-in-budget /
  actioned-in-budget computed against the same `slaFor` budgets), appeal
  outcomes, classifier health (`healthy | degraded | failing`), drift-alert
  counts, drift-cohort breakdowns, and crisis-event count.
- **Trend dashboards** bucketize hits over time and slice them `trendByDomain` /
  `trendByTenant` / `trendByLocale` for weekly/monthly, per-domain, per-tenant,
  and cross-locale comparison.

### Evaluation suites and release blocking

`evaluation/evaluation.ts` makes "any safety eval regression blocks release" a
real gate. `EVAL_CLASSES` (`content`, `contemplative`, `voice`, `avatar`,
`exam`, `crisis`) each produce an `EvalSuiteResult` carrying
precision/recall/F1, per-locale and per-demographic-slice parity, and an
`expectedCalibrationError`. `computeExpectedCalibrationError` is a _real_
sample-weighted ECE over confidence bins — not a placeholder — so calibration
quality is measured, not asserted. Adversarial suites (`prompt-injection`,
`jailbreak-template`, `cloned-voice-abuse`, `deepfake-generation`, `exam-cheat`)
report a `leakRate`.

`evaluateReleaseGate` is the blocking function. It returns
`{ allow: false, reasons }` if a new build regresses F1, demographic-slice
parity, per-locale parity, calibration (ECE up), or adversarial leak rate beyond
the configured thresholds — or if `isClassifierUpdate` is true and the
`championChallengerVerdict` is not `ready`. Live evaluation closes the loop:
`aggregateLiveSamples` rolls operator-reviewed production samples into inferred
precision/recall, and `updateGoldSet` promotes operator-confirmed samples into
ground-truth gold-set rows for future scoring.

---

## Real vs. runtime-owned — the honest boundary

These cores are deterministic pure functions and state machines; the runtime
owns enforcement. To be candid about where each side stands:

- **Real and verified (deterministic cores):** the full policy category enums
  and enforcement mapping, the severity/SLA budgets with reviewer-tier gates,
  the decision/appeal lifecycle with two-reviewer signoff and reviewer rotation,
  the crisis-frame cascade and its Redis-binding worker, the abuse-pattern
  detectors, the operator dashboards, the eval/release gate with a real ECE, and
  the review-package lifecycle and hash-chained audit/retention/attestation
  layer.
- **Runtime-owned (honest seams, not stubs):** the ML **classifiers** that emit
  a `PolicyHit` or a `CrisisDetection` (the cores validate and route what they
  are given); the **durable append-only store** behind the audit chain (the
  chain math is real; persistence is the platform's job); the **real Redis bus**
  the crisis worker binds (the cascade is bus-agnostic by design); and the
  **live legal-contact registry / resource catalog** that `routeCrisis` and
  `createMandatoryReport` are fed at runtime.
- **Adjacent domains, deliberately split:** consent/memory and DSAR fan-out are
  owned by `@oshun/privacy` and Iris (`@oshun/memory-iris`) — see
  [Privacy, Consent, Data Portability, and User Controls](./privacy-consent-portability.md);
  immutable audit _storage_ per the architecture is `@oshun/audit-platform`.

> **Note on a stale grounding reference.** `libs/maat` (`@maat/compliance`, with
> its `ghana-data-protection-compliance-engine`,
> `regulatory-filing-automation-engine`, and `esg-reporting-engine`) is a
> **separate B2B intelligence product**, not part of Oshun V1 governance. The
> implementation grounding for Oshun's review / T&S / privacy / billing logic is
> `@oshun/review-persistence`, `@oshun/audit-platform`, `@oshun/trust-safety`,
> `@oshun/privacy`, and `@oshun/billing-support` — do not read `libs/maat` as
> the Oshun governance code.

---

## Related

- [Privacy, Consent, Data Portability, and User Controls](./privacy-consent-portability.md)
  — the consent taxonomy, DSAR state machine, residency routing, retention
  constants, and regulatory-regime map (`@oshun/privacy`); the privacy half of
  governance.
- [Support, Entitlements, Billing, and Customer Operations](./support-billing.md)
  — the support-case routing/SLA machine, entitlement classes, metered billing,
  dunning, and the billing↔Aje entitlement bridge (`@oshun/billing-support`).
- [Crypto Payments — Non-Custodial Entitlement Settlement](./crypto-payments.md)
  — the settlement surface that drives subscription/entitlement state.
- [Lilith Persona Policy](./lilith-persona-policy.md) — the persona policy
  substrate the crisis frame and assistant-behavior categories enforce against.
- [Psyche Real-Time Runtime](./psyche-realtime-runtime.md) — the runtime that
  halts synthesis when a crisis frame is projected.
- [Scene Safety, Determinism, Provenance, and Cue Privacy](./scene-governance.md)
  — the Living-Scenes governance spine the crisis frame composes onto.
- [Collaboration, Review, and Templates](./collaboration-and-templates.md) — the
  editorial release flow the review-package lifecycle gates.
- [Admin Products — Web and Mobile](./admin-products.md) and
  [Tenant, Institution, and Operator Toolkit](./tenant-toolkit.md) — the
  consoles that surface review queues, the audit-log explorer (§20.4), and
  safety dashboards.
- Companion architecture page:
  [Trust, Safety, and Privacy](../architecture/trust-safety-and-privacy.md).
- The features hub: [../features.md](../features.md).
