# Journey: Scene · keep, share, and revoke a Living Offering

The BFF has a substantial Living Offering lifecycle: authenticated keep,
policy-checked share, public resolution, password/download protection, and
revoke/delete/takedown cascades. The customer write frontdoor is not complete.
The shipped generation card is rendered without keep/share callbacks, and the
public viewer only copies an already-created share; no customer management UI
calls the keep/share/revoke endpoints.

## Personas

- **Creator** — wants to retain an approved generated artifact and share it
  under an explicit privacy level.
- **Tenant member** — is constrained by tenant template, retention, locale, and
  public-sharing policy.
- **Recipient** — opens the resulting public/password/tenant URL and may request
  an attested download.
- **Owner or safety operator** — revokes, deletes, or takes down a share and
  expects every public derivative to stop working.

## Pre-conditions

- The member is authenticated and entitled to the Living Scene card.
- The artifact carries a normalized score and render envelope. Keep also
  requires an allowed retention preference: `30-day`, `1-year`, or `indefinite`
  within tenant policy.
- Share creation requires explicit consent, an allowed channel/privacy level,
  tenant policy, and a passing Lilith pre-share safety frame. Crisis-touched
  artifacts cannot be shared even through a crafted private-link request.
- Durable snapshot wiring is conditional on the BFF starting with its Postgres
  admin snapshot store.

## Steps

### 1. Keep through the authenticated API

`POST /v1/living-scenes/artifacts/keep` binds the artifact to the authenticated
owner and tenant, validates retention, stores the score/render envelope, and
returns `201`. `GET /v1/living-scenes/artifacts` lists only that owner's kept
records.

The visible `LivingSceneCard` supports optional `onKeep` and `onShare`
callbacks, but `/studio/generation/living-scene` does not pass them. Its buttons
there produce client notices only and do not call the BFF.

### 2. Create a policy-checked share through the API

`POST /v1/living-scenes/artifacts/:artifactId/share` verifies ownership,
consent, tenant policy, rate limits, crisis status, and the Lilith safety frame.
It creates an idempotent share for repeated artifact/channel intent, assigns a
short code, materializes APNG bytes, and records visible-mark, C2PA,
accessibility, robots, and optional tenant-branding data. Password-protected
shares store a verifier rather than a recoverable password.

### 3. Use the public result

`/scene/<shortCode>` resolves the share into playable, password-required,
tenant-auth-required, tombstone, or transient-unavailable states. The playable
viewer can copy the existing URL/iframe/oEmbed snippets and request a
short-lived download grant. Those controls do not create a new share or edit
privacy.

### 4. Revoke or remove the artifact

The authenticated revoke endpoint marks a share inactive. Originating-user
deletion and operator takedown cascade across public URLs and outstanding
download grants. The public viewer then renders a tombstone rather than stale
media.

There is no shipped customer Library/settings screen that lists these shares and
invokes revoke. Current end-to-end proof drives the lifecycle endpoints directly
and verifies the public result in the browser.

### 5. Observe the persistence boundary

With `wireDurableLivingSceneShares`, kept artifacts, shares, short-code index,
tenant audit, and takedown overlays hydrate and write through a durable
snapshot. Without it they are process-local. Materialized media bytes and
download grants are separate in-memory maps in both cases, so restart can
hydrate metadata without restoring playable bytes or grants.

## Post-conditions

- A successful API keep is owner-scoped and listable; a successful API share has
  a stable short code and auditable policy decision.
- Public/password/tenant access reveals only the authorized viewer branch.
- Revoke/delete/takedown prevents further public playback and grant redemption.
- The lifecycle is **API-complete but not customer-UI-complete**: no shipped
  creator surface performs the keep/share/revoke sequence end to end.

## Failure modes

- **Generation-card soft success** — keep/share notices on the shipped card do
  not prove persistence because no callbacks are supplied.
- **Missing consent or policy denial** — share creation fails closed with a
  structured reason.
- **Crisis/safety denial** — no public share is created; a private-link payload
  cannot bypass the block.
- **Mass-share pattern** — the covered burst exceeds the per-owner cap and
  returns `429 mass_share_pattern`.
- **Lost response/retry** — repeating the same artifact/channel share intent
  returns the same share rather than a duplicate.
- **Restart** — snapshot-backed share metadata may survive while
  `shareMediaById` and `downloadGrantsById` do not. The viewer can then report
  `media_not_materialized` until a rebuild path exists.
- **Telemetry overclaim** — no `living_scene_kept`, `share_dialog_opened`,
  share-created, or revoked customer analytics chain is emitted by these
  surfaces.

## E2E coverage

- [`apps/oshun/web/e2e/living-scene-card.spec.ts`](../../apps/oshun/web/e2e/living-scene-card.spec.ts)
  covers card behavior, viewer attestation, reduced motion, copy/embed, tenant
  gating, and deletion tombstones.
- [`apps/oshun/web/e2e/scene-password-and-grants.spec.ts`](../../apps/oshun/web/e2e/scene-password-and-grants.spec.ts)
  deeply covers API share creation, consent/policy/crisis/safety denials,
  mass-share limiting, passwords, grants, and delete cascade.
- [`apps/oshun/web/e2e/scene-viewer-deepening.spec.ts`](../../apps/oshun/web/e2e/scene-viewer-deepening.spec.ts)
  covers public viewer metadata/actions and lost-response idempotent re-share.
- `apps/oshun/web/e2e/scene-stored-tenant-policy.e2e.ts` additionally checks
  stored tenant policy at the BFF boundary.
- **Coverage depth: partial.** The API and public viewer are deep; the creator
  keep/share/revoke UI journey does not exist to automate.

## Per-view files touched

- [`studio/generation/studio-generation-living-scene.md`](../studio/generation/studio-generation-living-scene.md)
  — shipped card host, optional actions, and current soft-notice boundary.
- [`customer/12-scene/scene-id.md`](../customer/12-scene/scene-id.md) — public
  result, copy/embed, download, and tombstone.
- [`customer/02-home-discovery/library.md`](../customer/02-home-discovery/library.md)
  — expected management destination; no Living Offering manager is wired there.

## Cross-references

- [`scene-public-viewer-anonymous.md`](./scene-public-viewer-anonymous.md) — the
  complete viewer branch matrix.
- [`public-scene-abuse-report.md`](./public-scene-abuse-report.md) — report and
  safety continuation.
- [`lilith-studio-tara-scene-publish.md`](./lilith-studio-tara-scene-publish.md)
  — a separate Studio scene publication seam.
- Sources: `apps/oshun/web/src/components/living-scenes/LivingSceneCard.tsx`,
  `apps/oshun/web/src/app/studio/generation/living-scene/page.tsx`, and
  `apps/oshun/bff/src/routes/living-scenes.ts`.

## Open questions

- Which signed-in surface should own keep/share creation and share revocation?
- How should a customer distinguish a kept artifact from its one-or-many public
  shares and their independent states?
- Where should materialized media live so snapshot-hydrated shares remain
  playable after restart?
- Which customer telemetry is useful without exposing sensitive share details?
