Oshun Platform · Features

Review, Compliance, and Trust & Safety

A focused page within the Oshun Platform Features documentation. The full map and every sibling page live in the Features hub.

5sections19 minread3tables

On this page

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 — grounded in @oshun/privacy. The customer-ops/entitlement/billing half lives in Support, Entitlements, Billing, and Customer Operations and Crypto Payments — Non-Custodial Entitlement Settlement, 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.


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 (approvedapproved, rejectedrejected, changes_requestedchanges_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

text
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), AttestationEvidenceRequirements, an AttestationRun that moves through statuses, signed AttestationSignatures, 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 (Audienceinternal | partner | public, DistributionScaledev | staging | production) through AutomationPolicyContextMultipliers. 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 WatermarkDetectors — a MetadataTagDetector and a C2paDetector — and resolves a WatermarkVerdict from WatermarkSignalReports. 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 LabelRequirementLabelRenderCheckLabelVerificationReport flow so the AI-content disclosure is provably rendered, and provenance badges (provenance-badges.ts) compose end-user-facing Badges (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 and Tenant, Institution, and Operator Toolkit, whose audit-log explorer is backlog §20.4); the editorial release flow they gate is described in Collaboration, Review, and Templates.


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 triplehasNamedTarget && 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_RUNBOOKmandatoryReportRequired, 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 and Psyche Real-Time Runtime; the scene-level mid-stream crisis frame is in Scene Safety, Determinism, Provenance, and Cue Privacy.

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; 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.