Skip to content

feat(web,api): access model v2 P1b - free mode UI, preview machinery deleted#77

Merged
knzeng-e merged 5 commits into
feat/access-v2-p1from
feat/access-v2-p1b
Jul 9, 2026
Merged

feat(web,api): access model v2 P1b - free mode UI, preview machinery deleted#77
knzeng-e merged 5 commits into
feat/access-v2-p1from
feat/access-v2-p1b

Conversation

@knzeng-e

@knzeng-e knzeng-e commented Jul 8, 2026

Copy link
Copy Markdown
Owner

Stacked on #76 (P1a). Completes ticket 24 P1: the web half of the v2 access model, and the conscious deletion of the whole preview apparatus.

Free mode, end to end

  • AccessMode gains 'free' in app types, chain encoding (free=2), the publish wizard (third "Free for everyone" door + review copy), and catalog/release labels.
  • Guests can play Free tracks with no wallet: access probes use the zero address (only Free grants it), and requestFreeContentKey fetches the key with no signature. Gated tracks keep the signed path unchanged.
  • The artist door copy says it plainly: "You can change the door later without re-uploading" (backed by P1a's musicRegSetAccessMode).

Preview machinery deleted

  • useCatalog: 42% slicing, cutoff/limit refs, preview-asset resolution, preview fetch/decrypt branches - gone. Access is binary: authorized plays full, unauthorized gets the unlock gate and no audio.
  • playbackModeForAccess removed; cutoff wiring dropped from PlaybackProvider/PersistentAudio/ListenerShell; the WAV preview encoders (shared/utils/audio.ts + test) deleted outright.
  • Publish no longer generates preview assets; the API /api/uploads/preview route + tests removed. previewCID stays optional in the manifest schema (already-pinned manifests validate); nothing produces or reads it.
  • Rooms: an unauthorized host streams nothing and moves the room to a playable track. The playback-mode wire protocol stays (always 'full') for compatibility - removal scheduled with the signaling cleanup (P1c candidate).
  • UI copy de-previewed: "Locked" state chip (testid locked-player-state), unlock CTA on the cover, "Free to discover" on Home.

Docs (same PR, incl. the Pages rule)

Threat model (access modes + retired preview boundary, checklist updated), web README access policy, backlog doctrine + ticket rows (18 marked retired, 24 marked P1 delivered), ticket 18 retirement note, ticket 24 delivery notes, and docs/index.html positioning copy.

Verification

  • Web: 90/90 unit, lint 0 errors, tsc -b + vite build clean, fmt clean on touched files (deployments.ts warning pre-exists from dd886f5).
  • API: 24/24, typecheck clean.
  • Playwright e2e rewritten but NOT run (no browser here): classic-unlock expects locked-not-preview; room-join's protected scenario now asserts no-stream + gate + manual advance to the public track via 'Next track' + Play. These two specs need a local npm run test:e2e before merge - flagging this as the review gate.

Merge order: #76 first, then this.

knzeng-e and others added 2 commits July 8, 2026 19:57
…deleted

The web half of ticket 24 P1, on top of P1a (Free mode contracts + free-key
endpoint):

Free mode, end to end:
- AccessMode gains 'free' in the app types, chain encoding (free=2), publish
  UI (third "Free for everyone" door + review copy), and catalog/release
  labels.
- Guests probe access with the zero address, so Free tracks resolve playable
  with no wallet; requestFreeContentKey fetches their key with no signature.
  Gated tracks keep the signed path unchanged.

Preview machinery deleted (the doctrine is retired, not hidden):
- useCatalog: 42% slicing, preview cutoff/limit, preview-asset resolution,
  and the preview branches of fetch/decrypt are gone. Access is binary:
  authorized plays full, unauthorized gets the unlock gate and no audio.
- PlaybackProvider/PersistentAudio/ListenerShell lose the cutoff wiring;
  playbackModeForAccess removed from accessPolicy; the WAV preview encoders
  (shared/utils/audio.ts + test) are deleted outright.
- Publish no longer generates preview assets (uploadPreviewToBackend gone);
  the API /api/uploads/preview route and its tests are removed. previewCID
  stays optional in the manifest schema so already-pinned manifests validate;
  nothing produces or reads it.
- Room hosts without access stream nothing and move the room to a playable
  track. The room playback-mode wire protocol stays (always 'full') for
  compatibility; removal is scheduled with the signaling cleanup.
- UI copy de-previewed (locked state, unlock CTA, "Free to discover") and the
  e2e specs rewritten to v2 expectations (classic: locked-not-preview;
  room-join: no-stream-for-unauthorized-host, manual advance). Playwright not
  run here - needs a local pass.

Docs: threat model (access modes + retired preview boundary), web README
access policy, backlog doctrine + ticket rows, ticket 18 retirement note,
ticket 24 P1 delivery notes, and docs/index.html positioning (same-PR Pages
alignment rule).

Web: 90 unit tests, lint 0 errors, build clean. API: 24 tests, typecheck
clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
One SIWE-style signature opens a ~24h session; every later key request rides
a bearer token instead of a fresh wallet signature. The token proves identity
only - the on-chain access check still runs per track on every request.

Key service:
- sessionTokens.ts: HMAC-SHA256 tokens over a strict claim shape, key
  HKDF-derived from CONTENT_KEY_MASTER_SECRET with a dedicated info label (no
  new secret, no shared bytes with content keys), constant-time verify, 24h
  TTL, jti revocation (in-memory, process-lifetime - documented boundary).
- signatures.ts: canonical SIGN_IN message + verifySignInRequest (expiry ->
  signature -> nonce consumption, same fail-closed order).
- POST /api/auth/session (401 on bad signature, 503 when unconfigured so the
  per-request path remains) and idempotent POST /api/auth/logout.
- key-request route accepts {sessionToken, purpose}; the chain check runs
  against the token's address, never client input. 13 new tests (37 total).

Web:
- ensureDotifySession: reuse the stored per-address token (refresh margin),
  else one sign-in signature; requestContentKey rides the token, retries once
  with a fresh sign-in on 401, and falls back to per-request signing when the
  backend lacks session support (404/503).
- Wallet disconnect also signs the session out server-side and forgets the
  stored token.

Threat model gains the session-auth boundary section; ticket 24 delivery
notes updated. API 37/37 + typecheck; web 90 unit, lint 0 errors, build ok.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@knzeng-e knzeng-e self-assigned this Jul 9, 2026
@knzeng-e
knzeng-e merged commit 6d57416 into feat/access-v2-p1 Jul 9, 2026
1 check passed
@knzeng-e
knzeng-e deleted the feat/access-v2-p1b branch July 9, 2026 19:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant