Consent is scoped, revocable, participant-aware, and versioned. A room-level toggle cannot silently authorize a newly joined member, a different audience, voice cloning, or later reuse.
V3 ("Lilith") is the first Oshun product where a member has a body in a room full of strangers — and three of the platform's oldest promises have to be re-answered at body resolution and in real time. Children must be protected when a 13-year-old and an adult can share a yoga commons. Harassment must have an instant, self-serve answer and an accountable operator trail, because a shove or a slur in an embodied room is gone the moment it happens unless someone captures it. And no one is recorded without a real choice, even when the "room" is a 4,096-seat stadium concert whose master capture is the artist's catalog. V3's stance is the same one it takes everywhere: it does not fork the trust spine. Embodiment is a thin, server-authoritative layer of computed decisions over V1's audit ledger, persona policy, and DSAR pipeline — the library decides, the world server and tenant services persist and enforce. This page covers the operator-and-policy side of that promise: minor-protection mode, harassment and abuse response, and the recording-consent flow, end to end. The companion that owns the at-the-body evaluators (tone, crisis, costume, persona) is Identity, Embodied Safety & Provenance Foundations; the section hub is ../V3_features.md.
What ships, honestly#
This layer scores high on the implemented-vs-spec axis because its hard parts are pure decision logic, not art or engine plugins. Two real TypeScript modules carry it, each backed by an automation suite.
- The in-world governance engine is real, computed code. The
minor-protection defaults, the point-at-avatar reporting pipeline with its
60-second replay evidence buffer, the operator-review SLA dashboard, the
progressive-sanction state machine, and the minor-cohort background-check gate
are all implemented as pure functions in
@oshun/tenant-lilith-commons(libs/v3/lilith-commons/src/index.ts). Each capability is declared in the package descriptor with an operational metric (in-world-reporting,operator-review-sla,progressive-sanctions,minor-protection-defaults,minor-operator-alerts,minor-instructor-background-checks) and exercised bysrc/__tests__/index.spec.ts. - The recording-consent policy is real, computed code.
libs/v3/lilith-identity-bridge/src/recording-consent.tsresolves every class and concert capture decision — full/observer/decline, the free privacy seat, the front-row visibility exception, the publication gate, DSAR coverage, and post-publication withdrawal — as discriminated-union functions, tested bysrc/__tests__/recording-consent.spec.ts. - The evidence trail is the genuine V1 audit platform. Every decision below
publishes a canonical event (
v3.lilith.in_world_report.submitted,v3.lilith.progressive_sanction.transitioned,recording_consent.*, …) into V1's@oshun/audit-platform— the same hash-chained, tamper-evident ledger V1 ships, with investigation export and retention tooling. V3 produces evidence; Oshun retains it.
Three honest qualifications. First, these libraries are pure decision
kernels: no database, no socket. The report-submission function returns a typed
operator-queue entry and an audit event; the world server is what actually files
the report and the tenant service is what runs the 30-minute mute clock.
Second, two spec-level product behaviors in V3features.md are not in
this code and are labeled as such below: the one-tap "step back" (personal-space
expansion + per-avatar self-mute) and the automatic "report-and-remove → quiet
cell" routing are documented UX flows; what is _coded is the report → evidence
→ operator-queue pipeline and the operator-applied mute sanction level. (The
solo-quiet-cell venue itself is a real Commons capability —
privacyMode: 'solo-quiet-cell' — it just isn't auto-triggered from a report
here.) Third, the platform-level trust-safety domains @themis and
@kuanyin are not imported by V3's in-world governance: those operate at
the V1/V2 layer (e.g. @v2/themis-privacy-dsr-routing joins @themis/privacy
for data-subject requests, per
V2 Security, Compliance & Sister-Monorepo Integration).
V3 reuses V1's spine instead. The one real Themis touchpoint for V3 is rights
adjudication in saraswati-stage/themis-rights-adjudication.ts, which belongs
to Commerce, Rights & Takedown, not to in-world
moderation.
Minor-protection mode#
V3 admits minors aged 13+, with realm-level age gates (Tara and Commons admit 13+; Saraswati Stage is 16+ at the realm boundary). The protection that travels with a minor account is enforced as computed defaults that cannot be toggled off, plus two structural gates around them.
Defaults that fail closed — and prove they can't be bypassed#
evaluateV3MinorProtectionDefaults treats any account under
V3_MINOR_PROTECTION_ADULT_AGE_YEARS (18) as protected and resolves four locked
surfaces (V3_MINOR_PROTECTION_LOCKED_SURFACES): voice-chat → off,
presence-outside-cohort → hidden, signed-edition-purchase → blocked,
and remix-licensing → blocked. Presence is the nuanced one: a minor's
"online in Lilith" presence is visible only to themselves, an operator, or a
viewer who shares a cohort (hasCohortOverlap) — a non-cohort stranger sees
nothing. The function does not merely set these states; it derives a
bypassProofPassed flag that is true only when every requested bypass was
denied. runV3MinorProtectionThirteenYearOldBypassFixture drives exactly
the adversarial case — a 13-year-old
(V3_MINOR_PROTECTION_TEST_ACCOUNT_AGE_YEARS) who requests voice chat on,
presence exposed, purchases unlocked, and remix licensing enabled, observed by a
non-cohort viewer — and asserts all four locks hold. The capability's
operational metric is literally minor-protection:13yo-locked-no-bypass. This
is a fail-closed default a child cannot click their way out of, not a settings
page with a recommended toggle.
Any flagged minor interaction pages an operator within 30 seconds#
createV3MinorFlaggedInteractionOperatorAlert is deliberately one-sided: it
returns null for an adult-only flag, but for any interaction with at least one
participant under 18 it emits a P0-minor-safety alert to the
v3-lilith-minor-safety-alerts queue, console-visible, with the minor avatar/
account ids attached and an audit event
v3.lilith.minor_flagged_interaction.operator_alerted. The delivery SLA is a
hard 30 seconds (V3_MINOR_INTERACTION_OPERATOR_ALERT_SLA_MS = 30_000), and
deliveredWithinSla is computed against it.
runV3MinorFlaggedInteractionOperatorAlertFixture asserts that adult-only flags
are ignored, every minor flag produces a console-visible alert, and every alert
lands inside the 30-second window — so a flagged minor interaction can never
sink quietly into the normal queue.
The background-check gate runs at scheduling time, not at the door#
The strongest minor protection is the one a child never sees, because it fires
before the room exists. evaluateV3MinorCohortClassScheduling blocks an adult
instructor from being scheduled against a minor cohort unless their background
check is cleared and valid through at least
V3_MINOR_COHORT_BACKGROUND_CHECK_MIN_VALIDITY_MS (24 h) past the scheduled
start. Every other status — missing, pending, failed, expired — produces
a blocked decision with a precise reason
(minor-cohort-background-check-missing, …-not-cleared, …-expired) and an
audit event. runV3MinorCohortBackgroundCheckEnforcementFixture proves all four
failure modes block, a cleared instructor is allowed, and an adult-only cohort
does not require the check (background-check-not-required) — so a non-cleared
adult never reaches a room with minors in the first place. Minors in recordings
are handled by the recording-consent flow below: observer-by-default for
classes, an auto-assigned, un-disable-able privacy seat for concerts.
Harassment and abuse response#
Harassment in an embodied room is ephemeral — the durable artifact is whatever the platform chooses to keep. V3's answer pairs an immediate report with a 60-second replay buffer so the operator sees what happened, routes it through a measured SLA, and escalates repeat offenders through an audited, appealable sanction ladder.
Point-at-avatar reporting, built as a short interaction#
The report is a few seconds of pointing, not a form you leave the world to fill
out. createV3InWorldReportDraft → pointV3InWorldReportAtAvatar →
selectV3InWorldReportCategory advance a draft through
target → category → comment → submitted, and renderV3InWorldReportUiModel
drives the in-world panel (category options unlock only once an avatar is
targeted). The six categories (V3_IN_WORLD_REPORT_CATEGORIES) are
harassment, impersonation, unsafe-conduct, voice-abuse, minor-safety,
and other — and minor-safety is special: submitV3InWorldReport stamps it
P0-immediate on the operator queue while everything else is P1-same-day.
The 60-second evidence buffer, and why voice transcripts stay ephemeral#
A report is only as good as its evidence. validateV3InWorldReportReplayBuffer
requires a full 60-second window (V3_IN_WORLD_REPORT_REPLAY_WINDOW_MS) of
both voice and target-avatar motion coverage, and submitV3InWorldReport
throws if the buffer is incomplete — the report cannot be filed on a partial
record. The submission's evidenceKinds are
['voice-replay-buffer', 'avatar-motion-replay-buffer'], and every object is
written under a tenant-resident partition
(tenant:<tenantId>:<REGION>:safety-reports:<reportId>) so evidence never
leaves the user's residency zone.
The privacy design here is the interesting part. Voice transcript is normally
ephemeral: createV3VoiceTranscriptRollingBuffer keeps only the trailing 60
seconds of the current session and sets durablePersistence: 'none'.
Transcript becomes durable only when attachV3VoiceTranscriptBufferToReport
binds it to a specific report (persistenceScope: 'attached-report-only').
auditV3Voice TranscriptRetention then proves the invariant, flagging
unattached-voice-content-persisted, voice-content-beyond-60s-buffer, or
window drift, and passed is true only with zero violations. In plain terms:
the platform keeps a rolling buffer to make reports possible, but it cannot
retain your voice beyond 60 seconds unless you filed a report about it — the
evidence trail and the privacy ceiling are the same mechanism.
The operator review SLA is a release gate, not an aspiration#
Reports land on v3-lilith-safety-report-review, and
evaluateV3OperatorReviewSlaDashboard scores every review against a
30-minute SLA during programmed hours and a 2-hour SLA off-hours
(V3_OPERATOR_REVIEW_PROGRAMMED_HOURS_SLA_MS / ..._OFF_HOURS_SLA_MS). It
buckets events into daily windows and exposes releaseGateGreen — true only
when the most recent 7 consecutive UTC days each clear 95% compliance
(V3_OPERATOR_REVIEW_SLA_MIN_COMPLIANCE = 0.95, ..._CONSECUTIVE_DAYS = 7).
The gate is strict on both axes: it checks the days are genuinely consecutive
(areConsecutiveUtcDays) and the fixture refuses fewer than 20 reviews/day, so
a green light means a real, sustained queue, not one quiet afternoon. This is
the follow-the-sun staffing promise made measurable.
Progressive sanctions, every step audited and appealable#
Repeat offenders escalate through a four-step ladder
(V3_PROGRESSIVE_SANCTION_LEVELS): warning → mute → realm-ban → account-ban.
advanceV3ProgressiveSanctionState is the transition function — a mute stamps
muteUntilMs 30 minutes out (V3_PROGRESSIVE_SANCTION_MUTE_DURATION_MS), a
realm-ban/account-ban flips the corresponding flag, and every transition
carries both an appeal route to v3-lilith-appeals-review (7-day window) and an
audit event v3.lilith.progressive_sanction.transitioned, appended to the
state's transitions and auditLog. runV3ProgressiveSanctionsIntegrationTest
drives the full sequence and passes only when the final level is account-ban,
the exercised order equals the canonical ladder, all transitions are
audit-logged, and all appeals are routed — so "recorded and appealable" is a
coded invariant, not a policy paragraph. The operator-applied mute here is
distinct from the self-serve, in-the-moment controls below.
Self-serve "step back" and the quiet cell — honestly labeled#
V3features.md describes two immediate, self-serve controls: a one-tap "step
back" that expands the target's personal-space radius and mutes the chosen
avatar with no operator in the loop, and a one-tap "report and remove" that
files the report _and routes the reporter out into a quiet cell. These are
real product commitments, but in the libraries on this page they are
spec-level: lilith-commons implements the report draft → evidence → submit
→ operator-queue pipeline and the operator-side sanction ladder, while the
personal-space expansion, the per-avatar self-mute, and the automatic
reporter-to-quiet-cell hop are world-server behaviors not present in this
module. The destination is real — the solo-quiet-cell venue is a coded Commons
capability (privacyMode: 'solo-quiet-cell', reflection-scope-bound) — but the
report-triggered routing into it is documented, not yet computed here. Crisis
routing (a non-dismissible in-world soft prompt, a V1 crisis-pipeline handoff,
and an on-call page, gated at recall ≥ 0.95 / FPR ≤ 0.02) lives in
@oshun/lilith-body-policy and is detailed on
Identity, Embodied Safety & Provenance Foundations.
The recording-consent flow#
Class and concert capture have different scales and customs, so they use two
different consent models — but both resolve to a concrete avatar/voice/chat
treatment, both audit into @oshun/audit-platform, and both route DSAR and
withdrawal through the same V1 pipeline. The whole flow is real code in
recording-consent.ts.
Class recording — per-participant consent at the gate#
resolveV3ClassRecordingConsent maps a participant's choice to a treatment.
Consent in full → visible / master-mix / included. Observer mode →
generic-silhouette / excluded-from-master-mix / redacted, stamped
redactionStatus: 'observer-redaction'. Decline and leave → routed to the
lobby (route: 'lobby') with the ticket retained and nothing recorded. The
load-bearing branch is the minor one: a minor choosing full triggers
guardianConfirmationRequired, and absent a guardianConfirmedAtIso the
decision downgrades to observer automatically rather than recording the
child in the master mix — fail-closed, by computation, not by reviewer
diligence. A mid-class change of heart is honored by
resolveV3ClassMidSessionOptOut, which downgrades to observer and sets
retroactiveFromMs to the opt-out instant, so the recording is redacted from
that point forward.
Concert recording — blanket terms with a free, un-removable privacy seat#
Per-attendee dialogs at 4,096 seats are impractical, so concerts use blanket
ticket-terms consent plus a non-recorded option.
resolveV3ConcertTicketRecording throws if ticketTermsAffirmed is false —
you cannot hold a concert ticket without acknowledging the recording. From there
it branches by tier: a minor is auto-assigned a privacy-seat with
privacySeatLocked: true (the toggle cannot be turned off); a crowd-band
LL-HLS spectator is spectator-aggregate-only (not in the master recording,
chat counted only in aggregate); a front-256 named seat throws unless the
buyer both affirms full visibility and was offered an alternate tier, and
throws again if they try to request a privacy seat (front-row is inherently
visible); any other attendee may opt into a privacy seat. Crucially,
privacySeatCostDeltaCents is the literal type 0 — privacy is free by
construction. Two server-enforced guards back this: validateV3AudienceCamCue
refuses to publish any cue whose targetSection is privacy (the audience
camera can never sweep the privacy section), and
resolveV3ConcertMidShowPrivacySwitch lets an attendee switch mid-show,
excluding them from audience-cam sweeps thereafter and forcing
retroactiveRedaction with an operator review gate.
Publication gate, DSAR, and post-publication withdrawal#
Consent is enforced again at the moment of release.
evaluateV3RecordingPublicationGate sets publishAllowed only when the consent
ledger is valid, the privacy-seat ledger is valid, and redaction has
actually been applied — otherwise it returns precise blockedReasons
(consent-ledger-invalid, privacy-seat-ledger-invalid,
redaction-not-applied). DSAR coverage is a gate, not best-effort:
buildV3RecordingDsarCoverage throws if the export is missing the consent
ledger, the privacy-seat decisions, or the list of recordings the user appears
in — the three exportSections are all required. And consent is revocable after
the fact: resolveV3PostPublicationWithdrawal returns rerenderRequired,
redactionReapplied, and v1TakedownCascade, all true, with a 48-hour
deadline — a withdrawal forces a re-render, re-redaction, and a hand-off into
the V1 takedown pipeline. "Published" is never the end of the consent story.
How it connects#
This page is the operator-and-policy face of a trust foundation owned elsewhere. The at-the-body evaluators it leans on — tone scoring, the measured crisis gate, costume rules, avatar-swap impersonation blocking, and AI-persona enforcement — are Identity, Embodied Safety & Provenance Foundations, and the rights side of a recording — the Isis provenance bundle, the C2PA signature, and the Themis 24-hour adjudication a takedown rides on — is Commerce, Rights & Takedown. The internal mechanics this page summarizes (the rolling-window math, the crisis thresholds, the consent-ledger shape, and the audit hash-chain) are dissected in Persona Policy, Provenance & Rights (architecture). The honest line throughout: the minor-protection defaults, the reporting and evidence pipeline, the SLA and sanction gates, and the recording-consent policy are real, computed, and tested; the personal-space "step back," the report-triggered quiet-cell routing, and the operator-console UI are the runtime and surfaces these decisions drive.
Related#
- Identity, Embodied Safety & Provenance Foundations — the identity bridge and the at-the-body safety evaluators (tone, crisis, costume, persona) this page escalates from
- Commerce, Rights & Takedown — the provenance bundle, C2PA signature, and Themis rights adjudication a recording withdrawal cascades into
- Persona Policy, Provenance & Rights (architecture) — the internals: rolling window, crisis gate, consent ledgers, and the audit hash-chain
- V2 Security, Compliance & Sister-Monorepo Integration
— where the platform's
@themis/consent/DSR trust-safety domains actually sit - The section hub: ../V3_features.md