# Keep, Share, Shareability, Takedown, and Lineage

This page documents what happens to a Living Scene _after_ it is rendered: how a
user **keeps** a session as a personal artifact, how an explicit second consent
turns it into a **share**, the per-component **shareability matrix** that
decides how far an artifact can reach, the public-viewer and embed surface, the
seven-disposition **takedown cascade**, tenant share policy, and reshare /
lineage / remix attribution. It serves the contemplative product (users keeping
and sharing offerings), the public web (a per-artifact viewer), and tenant and
operator administration. In the V1 stack it sits between the renderer and the
customer/public surfaces; the domain logic lives in
`libs/yemaya/living-scenes-runtime/src/personal-artifacts/personal-artifacts.ts`
and `…/shareability/matrix.ts` and `…/takedown/dispositions.ts`, and the HTTP
surface is the 1871-line BFF route at
`apps/oshun/bff/src/routes/living-scenes.ts`. The safety/provenance companion to
this page is
[Scene Safety, Determinism, Provenance, and Cue Privacy](./scene-governance.md).

## Keep is one action; Share is a second, consented action

**Keep** is the default personal-artifact action: at any point during or after a
session, the user saves the current Score + Render Envelope + Cue Plan as a kept
Living Offering. `createKeptLivingOffering` validates the score and envelope,
**binds the artifact to the score author account** (it throws
`ownership-mismatch` if the score's `authorUserId` differs from the keeper),
computes a `canonicalEnvelopeHash`, attaches a full accessibility-artifacts
bundle (reduced-motion carousel, per-locale VTT captions, audio-description
tracks, transcripts, accessible viewer chrome), and stamps a retention envelope.
Score storage is small (kilobytes per artifact), so the persistence policy is
**lifetime, no quota-based eviction** — the BFF keep route replies with
`bffPersistencePolicy: 'lifetime-no-quota-eviction'`. Retention policy is one of
`LIVING_SCENE_RETENTION_POLICIES = '30-day' | '1-year' | 'indefinite'` (default
`indefinite`); `quotaEvictionAllowed` is hardcoded `false` and
`tombstoneRequiredOnDelete` `true`. The keep endpoint is
`POST /v1/living-scenes/artifacts/keep`; the owner's list is
`GET /v1/living-scenes/artifacts`.

**Share** is an explicit, separately-consented action. `createLivingSceneShare`
**requires `explicitConsent` plus a non-empty `consentReceiptId` distinct from
Keep** — it throws `consent-required` otherwise — and then materializes a share
record (`LivingSceneShare`) carrying a short-code, privacy level, rendition, a
visible mark, a signed C2PA manifest, a Lilith pre-share check, and an
attestation URL. The share endpoint is
`POST /v1/living-scenes/artifacts/:artifactId/share`. The BFF makes both keep
and share **content-addressed and idempotent**: shares are stored by `shareId`
with a `shareIdByShortCode` index, so a re-submitted identical request resolves
to the same record rather than duplicating.

## The four share privacy levels (and the rendition choice)

A share carries one of four privacy levels —
`LIVING_SCENE_SHARE_PRIVACY_LEVELS`:

| Level                | Meaning                                                                                                                                 |
| -------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `private-link`       | Default. Unguessable URL, no listing; expires only when revoked.                                                                        |
| `link-with-password` | Shared URL guarded by a user-set passphrase (`createLivingSceneShare` throws `password-required` if the level is set with no password). |
| `tenant-internal`    | Visible to members of the sharing tenant only; the public viewer returns `tenant-auth-required` to outsiders.                           |
| `public`             | Surfaceable via Open Graph and embed; opt-in per share.                                                                                 |

A share also chooses a `LIVING_SCENE_RENDITIONS` value — `full-motion` or
`reduced-motion` — so a user can deliberately share the still-image carousel
instead of the moving render. Note the internal-consistency fix this page makes
explicit: the privacy-level set has **four** members, and the shareability
matrix (below) likewise uses four reach levels — earlier prose that described
per-component grants as a _three_-level tag
(`shareable | tenant_internal_only | private_only`) omitted `link-with-password`
and was internally inconsistent with the same document's four-level privacy
list. The **code is the four-level authority.**

### Indexability, robots, and the attestation URL

The default robots meta is the literal constant
`LIVING_SCENE_DEFAULT_ROBOTS_META = 'noindex, nofollow, noarchive'`.
Indexability is opt-in _and_ only honored at the public level: `indexable` is
`input.indexable === true && privacyLevel === 'public'`, and only then does
`robotsMeta` flip to `LIVING_SCENE_INDEXABLE_ROBOTS_META = 'index, follow'`.
Even at the public level the artifact does not surface in any Oshun-curated
discovery feed at V1. The attestation URL is built as
`` `${origin}/scene/${encodeURIComponent(shortCode)}` `` from the visible mark's
**short code** (a deterministic 6-character Crockford-style code), not from an
artifact id and not against a hardcoded `oshun.app` host — the origin is
injected. Embed (`<iframe>` + oEmbed) is offered only for `private-link` and
`public` shares; it is denied for `link-with-password` and `tenant-internal`,
and any tenant `embedAllowed === false` denies it too.

### Pre-share Lilith re-check (honest about materialization)

`createLivingSceneShare` runs `evaluatePreShareLilithCheck` on the materialized
artifact before issuing the share, and **throws `lilith-blocked` if the verdict
is not `pass`**. The check blocks for a deleted artifact, an invalid C2PA hash
(`mediaHashSha256.length !== 64`), a missing 6-char visible mark, a requested
privacy level that exceeds the resolved shareability reach
(`privacy-exceeds-shareability:<reach>`), or any segment/cue that trips a
blocked safety phrase. Crucially, a **pending MP4 materialization is
non-blocking and reported honestly**: the deterministic render still serves a
watermarked, C2PA-signed (PNG) artifact, so an unbaked MP4 is surfaced as a
`materialization-pending` _note_, never silently claimed as baked-in and never a
block. The download grant is equally candid: `audioWatermarkIntact` is
`'not-applicable'` for the silent Living Scene render rather than a fabricated
`true`.

### Revocation, tombstones, and abuse reports

The originating user can revoke any share at any time; `revokeLivingSceneShare`
sets state `revoked`, kills Open Graph / embed / indexability and resets robots
to noindex. `tombstoneShare` does the same with state `tombstoned`. The public
viewer (`resolvePublicViewer`) returns a `tombstone` resolution whose message is
the constant `PUBLIC_VIEWER_TOMBSTONE_COPY = 'This offering has been retired.'`
for a missing/revoked share or a deleted artifact. The standard revocation SLA
is the constant `LIVING_SCENE_REVOCATION_SLA_SECONDS = 60` (one minute). Each
public page carries an independent abuse-report flow (`recordAbuseReport` →
`POST /v1/living-scenes/public/:shortCode/report`) routing to the
`lilith-living-scene-public-reports` queue; reports are stored idempotently by a
stable `reportId` and triaged through `open → resolved | dismissed`. The BFF
also implements `detectMassSharePattern` to gate one-user-to-many-channels spam
vectors, and the public surface is rate-limited per IP and per artifact. The
public viewer is contemplative by construction: `autoplayWithSoundAllowed` is
hardcoded `false`, there is no recommended-feed sidebar, and accessibility
artifacts ride along.

## The shareability matrix — nine components, four reach levels, min-reach

Every kept artifact carries a per-component shareability matrix, computed at
save time and re-evaluated at share time, resolved by `resolveShareability` in
`shareability/matrix.ts`. There are **nine** `SHAREABILITY_COMPONENTS`:

`voice`, `likeness`, `persona`, `source`, `music`, `intent`, `tenant-policy`,
`lilith-policy`, `crisis-state`.

Each component carries a `ComponentGrant` whose `reach` is one of **four**
ordered `REACH_LEVELS`:

| Reach                | Order | Meaning            |
| -------------------- | ----- | ------------------ |
| `private-only`       | 0     | Most restrictive.  |
| `link-with-password` | 1     |                    |
| `tenant-internal`    | 2     |                    |
| `public`             | 3     | Least restrictive. |

The resolution rule is **minimum reach across all components**: the artifact's
reach is the most-restrictive component's reach (`minReach`), and the verdict
returns the `decidingComponents` (every component sitting at the minimum) so the
UI can explain _why_. Two hard rules:

- **All nine components must be present.** `resolveShareability` throws
  `ShareabilityError('missing-grants', …)` if any of the nine is absent (and
  `duplicate-component` / `invalid-grant` for malformed input). There is no
  silent default — an artifact cannot be shareable by omission.
- **Crisis-state has hard precedence.** If the supplied `crisisLabel` is
  non-null and not `'safe'`, the verdict is
  `{ kind: 'crisis-private-only', crisisLabel }` _before_ any component is even
  examined. An artifact whose render history touched a Lilith crisis frame is
  `private-only` regardless of every other grant, with a soft notice.

```ts
type ResolutionVerdict =
  | {
      kind: 'resolved';
      resolvedReach: ReachLevel;
      decidingComponents: ShareabilityComponent[];
    }
  | { kind: 'crisis-private-only'; crisisLabel: string };
```

A per-component grant reflects the component's own consent record — a cloned
voice's share-out grant, a teacher persona's contractual share rights, a Sophia
source's redistribution license, a music workflow class's rights metadata,
tenant and Lilith policy, and crisis state. When full-motion sharing is
restricted the system offers reduced-shareability fallbacks (still-frame preview
with audio muted, transcript-only, audio-with-still) and the **user picks — the
system never silently degrades**; that fallback is observable on the scene feed
as a `scene.fallback-engaged` event with `trigger: 'shareability-restriction'`.

## Takedown cascade — seven dispositions, one structured impact

When an upstream input is retired, retracted, or terminated, the takedown
cascade extends into user-owned and user-shared Living Offerings.
`takedown/dispositions.ts` defines **seven** `DISPOSITION_KINDS`, each producing
a structured `DispositionImpact` the UI consumes — covering playback affordance,
banner copy, re-render path, and shared-link state:

| Disposition                                        | `playback`                                           | `rerenderOption`                | `sharedLinkState` |
| -------------------------------------------------- | ---------------------------------------------------- | ------------------------------- | ----------------- |
| `voice-profile-retired`                            | `voice-muted-with-banner`                            | `substitute-voice-or-text-only` | `banner-only`     |
| `sophia-source-retracted`                          | `unchanged`                                          | `corrected-source`              | `banner-only`     |
| `workflow-class-frozen`                            | `unchanged` (plays from cache; no re-render/reshare) | `none-workflow-frozen`          | `banner-only`     |
| `lilith-policy-drift`                              | `unchanged` (with policy-version notice)             | `reapply-current-policy`        | `banner-only`     |
| `re-detection-sweep` (PSE/strobe/luminance/motion) | `quarantined-until-rerender`                         | `recompute-detectors`           | `invalidated`     |
| `persona-contract-terminated`                      | `voice-muted-with-banner`                            | `substitute-persona`            | `banner-only`     |
| `originating-user-account-deletion`                | `hard-deleted-tombstoned`                            | `none-deleted`                  | `tombstoned`      |

Each `apply…` function returns a frozen impact with **concrete banner copy** —
for example, the voice-retired banner is _"The voice in this scene (profile …)
has been retired. Audio narration is muted; re-render this scene with a
substitute voice or as text-only narration."_ The `re-detection-sweep`
disposition is the one that quarantines and **invalidates the shared link**
until re-render under the updated detector completes — it is the takedown side
of the PSE/strobe killer described in
[Scene Safety, Determinism, Provenance, and Cue Privacy](./scene-governance.md).
`workflow-class-frozen` disables resharing precisely because reshare requires a
re-bake for channel-watermark robustness, which a frozen class cannot perform.
Nightly cascade-reach audits report SLA compliance across the artifact corpus
(BFF `GET /v1/living-scenes/tenant/audit`).

## Tenant share policy — fail-closed by construction

Tenant Console gains a Living Scenes governance surface, and the share flow
consults tenant policy at every step. In the BFF, `resolveTenantSharePolicy`
resolves the effective policy for the request: in non-production it can honor
header-policy overrides gated behind the
`OSHUN_LIVING_SCENES_HEADER_POLICY_OVERRIDES` flag, otherwise it reads the
stored tenant policy. `createLivingSceneShare` enforces it: a tenant policy
whose state is `ambiguous` (or not `resolved`, or not `shareAllowed`) throws
`tenant-policy-denied` — **ambiguous policy resolves to deny** (the route
surfaces a `denied_tenant_internal_scope`-style disposition). A tenant can
outright deny share, deny public share while allowing tenant-internal only
(`publicSharingAllowed === false`, `tenantInternalSharingAllowed`), set
retention defaults (`30-day | 1-year | indefinite`, with user preference
_within_ tenant policy), and apply a tenant-branded public viewer (logo, accent
color, attribution copy) for tenant-internal shares. Every share action is
recorded with full audit metadata, and the tenant operator can revoke any
tenant-member share (`POST /v1/living-scenes/tenant/shares/:shareId/revoke`).
Institution-tenant Metis Lesson Visualizer templates default student-generated
share scope to tenant-internal with operator override. See
[Tenant, Institution, and Operator Toolkit](./tenant-toolkit.md).

## Public share surface, embed, and the route map

The complete public/owner/tenant route surface in
`apps/oshun/bff/src/routes/living-scenes.ts`:

| Route                                                         | Purpose                                           |
| ------------------------------------------------------------- | ------------------------------------------------- |
| `POST /v1/living-scenes/artifacts/keep`                       | Keep a session as a lifetime artifact.            |
| `GET /v1/living-scenes/artifacts`                             | List the caller's kept offerings.                 |
| `GET /v1/living-scenes/artifacts/:artifactId`                 | Fetch one artifact.                               |
| `POST /v1/living-scenes/artifacts/:artifactId/share`          | Create a consented share.                         |
| `POST /v1/living-scenes/shares/:shareId/revoke`               | Owner revoke.                                     |
| `POST /v1/living-scenes/tenant/shares/:shareId/revoke`        | Tenant-operator revoke.                           |
| `POST /v1/living-scenes/admin/artifacts/:artifactId/takedown` | Admin takedown disposition.                       |
| `GET /v1/living-scenes/tenant/audit`                          | Tenant share-audit feed.                          |
| `GET /v1/living-scenes/public/:shortCode`                     | Public viewer resolution.                         |
| `GET /v1/living-scenes/public/:shortCode/media`               | Watermarked media bytes.                          |
| `POST /v1/living-scenes/public/:shortCode/unlock-token`       | Exchange a verified password for an unlock token. |
| `GET /v1/living-scenes/public/:shortCode/embed`               | oEmbed / iframe.                                  |
| `POST /v1/living-scenes/public/:shortCode/download-grants`    | Per-share download grant.                         |
| `GET /v1/living-scenes/public/:shortCode/transcripts/:locale` | Accessible transcript.                            |
| `POST /v1/living-scenes/public/:shortCode/report`             | Independent abuse report.                         |

`resolvePublicViewer` keys on the **short code**, returns one of
`playable | password-required | tenant-auth-required | tombstone`, and generates
Open Graph + Twitter Card previews from the first frame plus the public-redacted
intent — **previews never expose the private intent** (`publicTitle` /
`publicDescription` read only `score.intent.publicRedactedText`). The
`unlock-token` route lets the BFF skip the password gate on revalidation calls
without the raw password riding along (`unlockTokenVerified`). These flows are
exercised by the e2e specs `public-scene-abuse-report`,
`scene-viewer-deepening`, and `living-scene-card` (per
`WALKTHROUGH/results/v1-triage-2026-06-23.md`).

## Reshare, lineage, and remix attribution

The Output Gallery lineage tree (see
[Output Gallery, Lineage, Branch, and Replay](./output-gallery-lineage.md))
extends to user-owned artifacts; lineage itself is owned by the gallery, not by
a separate Living-Scenes module:

- **Reshare** of a public link preserves attribution to the originating user by
  default; a tenant-internal reshare **cannot escalate to public**.
- **Replay-with-tweak from a public artifact** produces a child artifact in the
  lineage tree with attribution-policy edges; the default strips identifying
  metadata of the original creator unless they opted into derivative
  attribution.
- **Branch from this moment** forks a new score from a kept artifact at a chosen
  segment boundary; the new score's seeds and Cue Plan are user-owned; the
  original is unchanged.
- **Lineage display** shows the user's own derivations and (with attribution
  consent) parent-creator attribution; the admin surface sees the full lineage
  including non-attributed parents.
- **Remix takedown** of a parent propagates a retraction notice to children but
  does **not** delete child artifacts authored by other users — each child
  retains its own content and provenance.

## Launch-locale note for shared/accessible artifacts

Per-template locale parity is the constant `V1_LAUNCH_LOCALES` (ten regionalized
locales: `en-US`, `en-GB`, `es-ES`, `es-419`, `fr-FR`, `de-DE`, `pt-BR`,
`ja-JP`, `ar-EG`, `he-IL`, with `RTL_LOCALES = { ar-EG, he-IL }`) in
`libs/isis/workflow-classes/src/living-scene/locale-parity.ts`. Note this
differs from the platform's eight-locale `OSHUN_LAUNCH_LOCALES` set
(`libs/oshun/i18n/src/index.ts`, bare `ar` / `he`) — the two lists disagree, and
the divergence is called out more fully in
[Scene Safety, Determinism, Provenance, and Cue Privacy](./scene-governance.md).
For shared artifacts the accessibility bundle (captions, audio description,
transcripts) is generated per the locales requested at keep time.

## Related

- [Scene Safety, Determinism, Provenance, and Cue Privacy](./scene-governance.md)
  — the PSE killer, crisis frame, determinism gate, and the provenance signals a
  share carries.
- [Living Scenes — Concept and Customer Promise](./living-scenes-overview.md) —
  the customer framing of kept Living Offerings.
- [Output Gallery, Lineage, Branch, and Replay](./output-gallery-lineage.md) —
  the lineage tree reshare and branch extend.
- [Tenant, Institution, and Operator Toolkit](./tenant-toolkit.md) — tenant
  share policy, branding, and audit.
- [Review, Compliance, and Trust & Safety](./review-trust-safety.md) — the
  queues abuse reports route into.
- [Privacy, Consent, Data Portability, and User Controls](./privacy-consent-portability.md)
  — the consent and deletion model keep/share inherit.
- The features hub: [../features.md](../features.md).
