Learning Platformindex / ADRs / ADR-005E7

ADR-005 — Video & content (E7)

Status: Proposed (design-panel synthesis) · Date: 2026-07-22 · Track: E7 (video & content / media) Method: adversarial design panel — estate-scout + four thinker-canon lenses (simplicity · saturation/measurement · cost/CDN-topology · composition). Dissent preserved below.

Follows the ADR-001 template. Brand-free: hosts/engines/tools named by capability only. Inherits the I9/I10 reversibility discipline; adds I11–I13.


1. Context

The foundation names media as a first-class object (Pillar 3): a Member uploads → Media (self-recorded video | audio | external embed), and an Event produces → Recording (→ Media → Board Thread). Media transcripts feed the permissioned index (Pillar 4). Delivery is decoupled by design — "a paid video host is a URL repoint, not a rebuild" (Pillar 5 / §9 open question). The learning core gives us nothing here past a third-party embed URL field on course resources and a post-session video_recording_url on events. Everything else — ingest, storage, transcode, an authenticated player, transcripts, gated delivery — is greenfield.

The deciding variable is stated correctly in the track and must not drift: the simultaneous-play spike during live events. VOD is modest — steady, cacheable, single-digit dollars of egress. The live event is the opposite shape: rare, bursty, high-amplitude, and it hits at the platform's most visible, least-forgiving moment (the "heartbeat" surface). These two workloads share almost nothing and must not share a delivery path. That split is the whole track.

The estate-scout confirmed greenfield: no repo in the estate was built as a video or content-delivery system. The three closest (the learning core, the current learning front door, the blog pipeline) top out at embed-URL fields or public static publishing. The one real inheritance is architectural, not code — build media inside the learning core's content model (extend its typed resource_type discriminator so video is another enum value on the course-content table) rather than standing up a parallel media service.

2. Decision drivers

3. Options considered

4. The panel (rankings diverged; read the core argument, not the rank)

Lens Rank #1 Core argument
Simplicity (Torvalds) C (biased B) The spike is one URL field in a database. Don't build a transcode farm to solve it. Media is a row{media_id, member_sub FK, tier_visibility, kind, delivery_ref, transcript_ref}; where the bytes live is a value in one field, so a host swap is an UPDATE, not a rebuild. Own the row + object store + transcript + the permission check that issues a signed short-lived URL; rent the live spike; never build the transcoder. Transcoding a self-recorded upload? Hand it to the same paid host, store back its URL. One code path.
Saturation / measurement (Gregg) C (gated on data) The whole thing is a saturation problem in a build-vs-buy costume. Peak-to-average egress ratio is the ballgame, and it's high by construction. Greenfield means every number is zero today, so default-safe: rent the live spike from day one and instrument the first event as an active benchmark (peak concurrency shape, NIC egress + retransmits, segment-boundary RPS, live-transcode CPU concurrent with AI inference, player rebuffer ratio). Then the self-host-vs-rent threshold is a number, not a vibe — repoint on evidence.
Cost / CDN-topology hybrid (D_cost > C_cost) The number is peak concurrent Gbps, not monthly GB: a 90-min event at ~300 concurrent × ~3 Mbps ABR is ~600 GB total (trivial) but ~900 Mbps instantaneous — it saturates a 1 GbE NIC and takes every other service on the box down with it. Self-host VOD (cheap, owned), rent live end-to-end (the spiky hard part and the real-time transcode). Make the seam real: playback references an abstract owned media id resolved to a delivery URL at read time — never store a vendor playback URL.
Composition (Pike) C (via D as v0) The bytes are not the data — the transcript, tier stamp, and member FK are; the pixels are a re-pointable reference. The media pipeline fits on a postcard: ingest drops bytes and creates a Media row; a batch job enriches the row; delivery is a URL. Transcoding is a batch encode step, not a platform — a job that fails, not a service that goes down; it emits the transcript through the same ADR-001 outbox. If E7 runs to forty pages, someone invented a video product. Ship D as v0, evolve to C; never stand up a fleet or a transcode daemon.
Estate-scout Greenfield — no estate repo delivers video or gated content; closest matches top out at embed URLs or public static publishing. The one inheritance is architectural: extend the learning core's typed resource_type content model (video becomes another enum on the course-content table) instead of a separate media service.

5. The convergence (what all five agree on, under the different ranks)

Despite ranking A/B/C/D differently, the lenses converge on one architecture:

  1. Own the Media row, not the bytes. Every lens that keeps the moat says the source of truth is {media_id · member_sub (FK) · tier_visibility · kind (self|embed|audio) · delivery_ref · transcript_ref · poster · duration}. Where the bytes live is one repointable field. Four doors, one room: Kleppmann's "own the record," Fowler's "seam at the delivery boundary," the moat argument's "own the transcript, not the pixels," Torvalds' "rent the hard part."
  2. Rent the live-event spike; never self-host it blind. The peak-to-average egress ratio is high by construction; self-hosting means provisioning owned iron (and a live encoder that must not fail) for a peak you touch ~90 min/week. Rent the elasticity; own the boring steady state.
  3. VOD justifies no infrastructure. Modest, cacheable, embarrassingly parallel — serve it directly from the owned object store; a commodity cache goes in front only when metrics, not vibes, demand it.
  4. Never build a transcode farm/daemon. Transcoding is a commodity batch job triggered off new Media rows, emitting the transcript to the permissioned index via the ADR-001 outbox. It fails a job, not a service; it has no uptime to babysit.
  5. The moat contribution is the transcript in the index, tier-stamped and member-keyed — not delivery bandwidth. Delivery bytes carry no permission logic and are the one provably rentable layer.

And a second-order convergence that becomes a hard rule: rent delivery, never custody. A rented live stream (or embed recording) is the most valuable content that enters the system (Event → Recording → Media → Board Thread → indexed). If its only copy briefly lives in a vendor's pipeline, the "append-only source of truth" has a hole exactly where the best content enters. Always ingest a durable master back into the owned store before the recording is the record.

6. Decision — "Own the row · rent the spike · never build the transcoder"

A hybrid that takes the winning move from each lens: Option C, shipped as D-first, gated on measurement.

6.1 Own (the moat — cheap now, catastrophic to retrofit)

6.2 Rent (the commodity — don't hand-roll, don't let it become the record)

6.3 Defer / gate on measurement (the saturation lens' cut — scoped, not killed)

7. Tradeoff matrix

Dimension A full self-host B rent everything C hybrid D embed-only v0 Decision (C via D-first)
Own the Media row (moat) ❌ (vendor schema is the record) by construction
Survives the live-play spike ❌ (own NIC/transcode, unmeasured) ✅ (rented) ✅ (rented)
One permission authority (tier at issuance) ⚠️ (vendor ACL leaks)
Ops-service count / small team ❌ (transcode farm + edge) ✅ (batch job + URL)
Never build the transcoder ✅ (batch job)
Transcript AI-indexable at write ⚠️ ❌ (only in vendor) via outbox
Reversibility of delivery host ⚠️ ⚠️ (lock-in on masters) ✅ (repoint) ✅ (repoint)
Master custody (durable owned copy) ✅ if disciplined ✅ if disciplined locked (I13)
Cost at rest (modest VOD) ❌ (overpays)

8. Consequences

New locked invariants (promote into the foundation spec §7, continuing I9/I10 from ADR-001):

New required evals (the moat is only as trustworthy as these):

E7's first task is to own the row and rent the edge — not to build delivery (Pike + Gregg): define the Media object as another resource_type on the learning core's content table, stand up the batch transcript pipeline emitting to the index via the outbox, rent the live edge from the first event, and instrument that event as a benchmark. Decide owned-VOD-delivery and self-host-vs-rent-live on evidence.

Estate seams (what we actually reuse):

9. Preserved dissent (do not resolve away)

10. Follow-up (beads to file in Phase B under the E7 epic)

  1. Lock invariants I11 + I12 + I13 into the foundation spec §7.
  2. Define the Media object as source-of-truth (member FK · tier_visibility · kind · transcript_ref · poster · duration · abstract delivery_ref) as a new resource_type on the learning core's content table — delivery is a URL reference (priority high).
  3. Implement transcode + transcript as an idempotent batch job over a commodity encoder, triggered off new Media rows, emitting to the index via the ADR-001 outbox — not a standing service (priority high).
  4. P0b signed-URL tier-leak eval (fail-closed) + extend P0 to media transcripts — before any tier-gated video ships.
  5. Rent the live-event edge per-event; specify the ingest→record→VOD-repoint path so live recordings land as ordinary Media rows, and enforce the master-custody ingest (I13).
  6. Instrument the first live events as active benchmarks (P1); wire origin + player telemetry so the self-host-vs-rent-live repoint is triggered by a measured saturation threshold.
  7. Annotate the estate-integration map: identity/tier/Media-row = irreversible; delivery host, live edge, and the owned-VOD-delivery decision = repoint on measured evidence.

Panel transcript basis: estate-scout + Torvalds/Gregg/cost-CDN-topology/Pike lenses, 2026-07-22. Follows the ADR-001 template; inherits I9/I10 and adds I11–I13.