casset/docs
FeaturesOpen app
docs indexreference
00Overview01Thesis02Architecture03System reality04Roadmap05Investor brief06Technical brief07Full tech HTML08API reference09Playback10Audio pipeline11Commerce12Base anchoring13Hook system14Music video15Theming16Creator guide17Glossary
the stack, end to end

Architecture.

One page, one pass. Enough to load the product in your head without reading the source.

Casset is a Next.js App Router app with a Postgres database (Prisma), Vercel Blob for audio + artwork, Stripe for payments, Redis for presence / rate limiting, and a canonical release layer for manifests, provenance, permissions, lineage, and quiet Base anchoring. The surface you see at casset.fm is a mix of static docs/marketing pages, dynamic Profile Worlds, creator Studio surfaces, Visual Studio/runtime layers, internal release dossier tooling, and API routes that connect them.

For the living route classification, source-of-truth ownership, peripheral system posture, and code verification commands, read system reality. This page explains the architecture; system reality keeps the claims tied to current source files.

At a glance

                         HUMAN EXPERIENCE
┌──────────────────────────────────────────────────────────────────────┐
│ Browser / PWA                                                        │
│ Profile Worlds · Hook Objects · Listening Rooms · Studio · Dossier   │
└──────────────▲───────────────────────┬───────────────────────────────┘
               │ HTML / RSC / JSON     │ SSE: activity, presence, room
               │                       │
┌──────────────┴───────────────────────┴───────────────────────────────┐
│ Next.js App Router                                                    │
│ app/[slug] · app/studio · app/internal · app/docs · app/api/*         │
└──────┬──────────────────┬──────────────────────┬────────────────────┘
       │                  │                      │
       ▼                  ▼                      ▼
┌──────────────┐   ┌─────────────────────┐   ┌─────────────────────────┐
│ Profile data │   │ Canonical release   │   │ Agent-readable layer    │
│ Artist/Track │   │ ReleaseVersion      │   │ /api/agents/releases/*  │
│ theme/runtime│   │ Manifest/Provenance │   │ manifest · dna · policy │
└──────┬───────┘   │ Permissions/Lineage │   │ access · license · 402  │
       │           └──────────┬──────────┘   └───────────┬─────────────┘
       │                      │                          │
       ▼                      ▼                          ▼
┌──────────────┐   ┌─────────────────────┐   ┌─────────────────────────┐
│ Vercel Blob  │   │ Postgres / Prisma   │   │ MPP access rail         │
│ audio/art    │   │ canonical source    │   │ challenge · credential  │
│ artifacts    │   │ manifests + jobs    │   │ receipt · provenance    │
└──────────────┘   └──────────┬──────────┘   └─────────────────────────┘
                              │
                              ▼
                    ┌─────────────────────┐
                    │ Optional Base proof │
                    │ ReleaseAnchor jobs  │
                    │ tx receipt/explorer │
                    └─────────────────────┘

Supporting rails: Stripe Connect/Apple Pay for human commerce, Redis for
presence/rate limits/job acceleration, Vercel for edge delivery.

Data model

schema.prisma defines the application's identity, playback, social, commerce, and release infrastructure models. You rarely need to know all of them: most public flows touch a small ring of Profile World models, while the newer release layer records canonical manifests, contributors, permissions, provenance, and lineage.

The core ring

  • User — an account. Owns auth providers (OAuthAccount), optional Profile World/casset rows through Artist, purchase history, co-casset memberships, visual assets, and Follow edges.
  • Artist — the current Profile World/casset record. Holds the public handle, cover, avatar, themeJson, footer theme, price, privacy/publish state, Stripe Connect account id, and tracks.
  • Track — a song. Points at the audio blob, stores previewStartSec (the hook), duration, optional per-track rewards, sort order, lyrics, and rendered visual artifacts.
  • Purchase — the ledger entry that says "user X unlocked casset Y for $Z on date D." Entitlement checks resolve here.

Artist.themeJson is the flexible profile identity payload: stored color tokens, custom swatch source, profile details, cassetBackgroundSource, profilePatternId, and the mutually exclusive aiUsedProfile / noAiUsedProfile badge flags.

Community + activity

  • CassetActivity + ActivityEvent — the feed that surfaces "@connor collected Lil Durden’s casset" to followers.
  • CassetComment, HookComment, HookCommentLike, HookLike — comment + like threads on both the casset page (Side B / Listening Room) and per-hook discussion.
  • Emoji, TrackReaction — custom per-artist emoji fans can purchase and drop as reactions.

Release quests, campaigns, and rewards

  • Campaign / CampaignParticipant / CampaignSubmission — a "drop" is a time-boxed release-quest system where fans submit hook videos and winners split a prize pool.
  • SubmissionMetrics + SubmissionScore — scoring pipeline that ranks entries. Integrity scoring lives in lib/scoring/.
  • Bounty, HookShare, HookVideoSubmission — per-hook rewards attached directly to a track (no campaign needed).

Visual identity + co-cassets

  • VisualPack, VisualAsset, HookVisualAttachment, VisualGenerationJob — reusable visual-world assets, generated outputs, hook attachments, and render/source metadata.
  • CoCasset, CoCassetMember, CoCassetTrack, CoCassetRoomMessage — shared music identity objects with members, tracks, reactions, cover state, and room messages.

Intelligence + strategy

  • ArtistDropPlan, DropIntelligenceSnapshot, DropStrategyRecommendation — the advisor layer that suggests release-quest configuration based on historic engagement.
  • PromoterProfile, PromoterReputation, PromoterSubscription — optional campaign/release-quest reputation graph. This is not the core product category.

Canonical release layer

  • Release / ReleaseVersion — the canonical release definition and immutable version snapshots behind the public music experience.
  • ReleaseManifest — deterministic JSON snapshot with schema version, canonical hash, tracks, audiovisual refs, contributors, splits, and permission state.
  • ReleaseAnchor — optional Base-backed proof record for a manifest hash. Base is infrastructure, not the product surface.
  • Contributor, ReleaseContributor, and Split — film-credit-style contributor graph and settlement references.
  • RightsScope, PermissionPolicy, and AgentAccessPolicy — release-authored rules for remix, stems, sync, visual reuse, AI training, AI generation, derivatives, and commercial usage.
  • ProvenanceEvent and DerivativeLink — append-only release history and source/derivative lineage.

Accounting

  • CreditLedger — append-only credits + debits so any balance can be reconstructed (never mutate rows; insert reversing entries).
  • CampaignPayout, PerformancePayout — payout records with statuses mirrored from Stripe Connect transfers.

Request lifecycle

Here's what happens when someone taps casset.fm/lildurden from an iMessage link.

  1. Edge. Vercel routes the request to the nearest region.middleware.ts does auth cookie validation + minor rewrites (e.g. collapsing /share/[slug] → /[slug]).
  2. RSC. app/[slug]/page.tsx runs on the server, calls getData(slug) (cached per-request via React.cache), and returns the HTML shell with artist data baked in. generateMetadata runs in parallel so crawlers get OG tags on the first byte.
  3. Hydrate. The client bundle takes over. The dynamic theme hook (useDynamicTheme) samples the cover image and computes UI tokens; the media footer boots audio; next/image streams artwork with the blurhash placeholder.
  4. Playback. Tap play → the audio element points at /api/audio/[trackId] with a signed token. Server verifies entitlement via lib/audio-access.ts and returns a byte range covering the hook window (or the full track for unlocked users).
  5. Realtime. The client opens an SSE connection to /api/casset/[slug]/sideb for comment + activity updates, which stream in without a refresh.

Release manifest lifecycle

The release layer runs beneath the public cinematic surface. A listener experiences a Profile World; an owner or agent can resolve the canonical release definition.

Artist/Profile data
  -> Release + draft ReleaseVersion
  -> freeze/publish ReleaseVersion
  -> deterministic ReleaseManifest snapshot
  -> canonical JSON + canonical hash + signature metadata
  -> append ProvenanceEvent chain
  -> expose agent-readable manifest / DNA / permissions / lineage

Optional proof branch:
  -> queue ReleaseAnchor (idempotency key = chain + version + hash)
  -> BASE_ANCHOR job claims QUEUED -> ANCHORING
  -> submit manifest-hash calldata to Base Sepolia by default
  -> normalize tx receipt + explorer refs
  -> ANCHOR_VERIFY job confirms hash/proof integrity
  -> append ANCHOR_RECORDED or ANCHOR_FAILED provenance

Permissioned access branch:
  -> PermissionPolicy / RightsScope / AgentAccessPolicy
  -> ALLOW | DENY | CONTACT_OWNER | REQUIRE_LICENSE
  -> REQUIRE_LICENSE returns HTTP 402 + MPP challenge
  -> verified MPP credential returns Payment-Receipt + resource
  -> ACCESS_GRANTED / LICENSE_RECEIPT_RECORDED provenance events
  • lib/releases/manifest-builder.ts builds stable manifest payloads and hashes.
  • lib/releases/manifest-runtime.ts creates idempotent manifest snapshots from release versions.
  • lib/releases/permissions.ts evaluates rights scopes and agent policies.
  • lib/releases/provenance.ts appends hashed provenance events.
  • lib/base/release-anchor.ts queues, retries, and records Base anchors for manifest hashes. lib/base/sepolia-anchor.tssubmits zero-value calldata on Base Sepolia by default and blocks mainnet unless explicitly enabled.
  • lib/releases/agent-api.ts serializes agent-visible manifest, DNA, provenance, permission, and lineage responses.
  • lib/releases/access/mpp-release-access.ts turns a REQUIRE_LICENSE permission decision into an MPP challenge, verifies credentials, returns receipt headers, and records access lifecycle events as provenance.

Agent access and MPP

MPP is not a second release model. It is the payment/access negotiation rail after Casset has already resolved the canonical release and evaluated permission policy. The source of truth remains ReleaseManifest, PermissionPolicy, AgentAccessPolicy, and ProvenanceEvent.

Agent request
  -> GET /api/agents/releases/<releaseId>/manifest | dna | provenance | lineage
  -> POST /api/agents/releases/<releaseId>/permission-check
  -> decision:
       ALLOW            return resource
       DENY             deterministic denial
       CONTACT_OWNER    contact route, no payment challenge
       REQUIRE_LICENSE  HTTP 402 + WWW-Authenticate: MPP challenge
  -> agent presents MPP credential
  -> verify challenge id, signature, expiry, resource binding, request hash
  -> return Payment-Receipt + manifest/license/stems/dna resource
  -> append provenance: ACCESS_CHALLENGE_ISSUED,
                        ACCESS_CREDENTIAL_FAILED,
                        ACCESS_GRANTED,
                        LICENSE_RECEIPT_RECORDED
  • lib/mpp/challenge.ts creates signed or demo MPP challenges bound to release id, release version id, canonical hash, scope, resource type, request digest, and settlement split refs.
  • /api/agents/releases/[releaseId]/license and /api/agents/releases/[releaseId]/access return 402 problem responses when payment/licensing is required, then return a Payment-Receipt header after verification.
  • /openapi.json is generated from lib/mpp/discovery.tsso machine clients can discover release manifest, permission, license, and access routes without reading the human UI.

Base proof role

Base remains quiet proof infrastructure. It anchors release manifest hashes and receipt/proof references; it does not issue creator coins, replace MPP, or become the public mental model for a listener.

ReleaseManifest.canonicalHash
  -> ReleaseAnchor(status: QUEUED, chain: base-sepolia, idempotencyKey)
  -> app/api/cron/releases/process-jobs
  -> claimNextReleaseAnchorJob()
  -> submitReleaseAnchorToBase()
       - default chain: base-sepolia
       - mainnet requires BASE_ANCHOR_ALLOW_MAINNET=1
       - calldata: casset.release-anchor.v1:<versionId>:<hash>
       - zero-value transaction
  -> normalizeBaseReceipt()
  -> record txHash, blockNumber, contractAddress, explorer refs
  -> append provenance + schedule anchor verification

The audio pipeline

Upload (Studio)            Storage              Stream (listener)
──────────────────        ──────────────        ────────────────────
  pick file                Vercel Blob           /api/audio/<trackId>
    │                        ▲                         │
    ▼                        │                         ▼
  magic-byte        put(sha256-keyed)           audio-access.ts
  validation        with content-type                  │
    │                        │                ┌────────┴────────┐
    ▼                        │                ▼                 ▼
  sha256 dedup               │         entitled?            hook only
    │                        │         sign+302         byte-range proxy
    ▼                        │                              (30s window)
  Track row + peaks          │
  (waveform pre-compute)     │

The key invariant: the raw audio URL never leaves the server. Even unlocked listeners get a signed redirect that expires in ~1 minute, and unpurchased listeners get a proxied byte range limited to the 30s hook window — computed from PREVIEW_START_SEC and AUDIO_PROXY_WINDOW_SEC in lib/audio-access.ts.

Full deep-dive: audio pipeline.

The commerce pipeline

Fan taps Collect ──► Apple Pay sheet (Stripe Payment Request)
         │
         ▼
  POST /api/checkout/payment-intent
         │
         ▼
  Stripe creates PaymentIntent (Destination charge, app fee)
         │
         ▼
  Client confirms ──► webhook: payment_intent.succeeded
                              │
                              ▼
                       grant Purchase + Credit row
                              │
                              ▼
                      SSE push to /sideb ("@fan collected …")
                              │
                              ▼
                      Referral attribution (if tracked cookie)
                              │
                              ▼
                Stripe Transfer ─► artist's connected account

The webhook is the source of truth, not the client. Even if the tab crashes mid-confirm, once Stripe fires payment_intent.succeeded, we grant entitlement and emit the activity. Deep-dive: commerce.

Realtime (SSE over HTTP/2)

Casset uses plain text/event-stream SSE rather than WebSockets — cheaper on Vercel's serverless model, strictly one-way (client never writes back), and trivially survives middleboxes that break WS.

  • /api/casset/[slug]/sideb — comment stream + activity feed for a casset page. Fans get a live count + avatar of who's browsing.
  • /api/activity/stream — the global activity stream that powers the /feed page and notification badges.
  • Presence is stored in Redis with a 30s TTL, refreshed by a heartbeat from each connected client. Falling off the list happens naturally.

Rendering strategy

The mental model: static for marketing, dynamic for identity, client for playback.

  • Static pre-rendered (○ in next build): /, /features, /about, /docs/*, /login, /signup. No database round-trip on cold load.
  • Dynamic SSR (ƒ): /[slug], /preview/[slug], /u/[username], /studio/casset/[id]. Each renders with live artist data and returns owner-only fields only when the session cookie matches.
  • Client islands: the media footer, the preview scrubber, the visualizer, the TikTok export modal, and all of /studio. These are hydrated behind next/dynamic with ssr: false so the server-rendered shell ships fast and the interactive bits stream in after.

Open-graph images

Every casset has a dynamic OG card rendered by app/[slug]/opengraph-image.tsx via next/og. The card uses the artist's pfp + cover + name with AeonikPro burned in so chat apps see a branded 1200×630 preview even before the artist claims custom artwork.

The boring but important stuff

  • Migrations. Production runs prisma migrate deploy only. Local uses prisma migrate dev. See docs/MIGRATION_POLICY.md.
  • Env contract. GET /api/env-check lists the envs each route depends on;pnpm dev will bail if one is missing.
  • Testing. vitest for unit + route tests. Tests that touch Stripe or Prisma run against an isolated sandbox, never shared credentials.
  • Observability. Sentry for errors; Vercel Analytics for traffic; Redis for short-term metrics. No heavy APM layer.
← Docs home→ Audio pipeline→ Commerce
© Casset 2026
PrivacyTrustTerms