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
- The deciding variable is peak concurrent egress (Gbps at
go-live), not monthly GB. Size or rent against the spike
shape (p100 in the first ten seconds), never the average.
- The moat is the transcript in the index + the tier stamp +
the member FK — not the pixels. A transcoded byte is the single
most commodity thing in the architecture; it carries no permission logic
and nothing the AI indexes.
- Small ops team: every always-on stateful service (a transcode
daemon, a self-run edge fleet) is a 3am liability — the exact reason
ADR-001 deferred presence.
- Reversibility: delivery is a repoint by construction, so host choice
must stay a config value forever — but a repoint you never
measure is a coin flip.
3. Options considered
- A — Full self-host media stack. Own upload →
transcode farm (ABR ladder) → packaging/origin/edge → self-served
player, for VOD and live. Eat the spike on your own NIC. You
are a video company.
- B — Pure paid host / rent everything. Every video
(VOD + live) is an upload to an external streaming provider; store only
a URL + metadata. Player is their embed.
- C — Hybrid: own the Media object + modest VOD, rent the live
spike. Self-recorded VOD sits in our object store served
directly (or one cheap cache); the live event + its simultaneous-play
spike go to a paid live-streaming host; external embeds are pure
URLs.
- D — Embed-only / store-only v0. Own the Media row +
transcript pipeline; no owned VOD delivery infra yet (external
embeds + audio need none); rent live from event one; add owned VOD
delivery later as a repoint on the unchanged row.
4.
The panel (rankings diverged; read the core argument, not the
rank)
| 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:
- 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."
- 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.
- 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.
- 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.
- 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)
- The Media object is the single source of truth,
modeled as another
resource_type on the learning core's
content table (estate-scout's inheritance), never a parallel media
model:
media_id · member_sub (FK) · tier_visibility (immutable on the object) · kind (self|embed|audio) · delivery_ref (abstract, resolved to a delivery URL at read time) · transcript_ref · poster · duration · created_at.
- The object/media store holds the durable masters of
every self-recorded upload and every recorded live stream (the
custody rule) — never a vendor as the only copy.
- The transcode + transcript step is an idempotent batch job
over a commodity command-line encoder, triggered off new Media
rows: it emits renditions + poster + transcript and writes back to the
row; the transcript flows into the permissioned index via the
same at-least-once, idempotent ADR-001 outbox. It is a pipe,
not a platform — no standing service, no queue-worker fleet to
babysit.
- The permission check owns URL issuance. Our layer
performs the tier check against the reader's current tier and
issues a short-lived, signed, per-member delivery
token; the host only ever serves an already-authorized request.
This is I9/I10 applied to the media boundary.
6.2
Rent (the commodity — don't hand-roll, don't let it become the
record)
- The live-event delivery + spike + real-time
transcode go to a paid live-streaming host, per-event,
elastically. This is the one genuinely hard concurrency problem (many
readers, one writer, latency-bound, at max visibility).
- External embeds are pure URLs — a
kind=embed Media row with a delivery_ref and a
pulled transcript. Zero ingest infra.
- Transcoding a self-recorded upload that needs it is
handed to the same paid host; store back its URL into
delivery_ref. One code path — no second transcode
stack.
6.3
Defer / gate on measurement (the saturation lens' cut — scoped, not
killed)
- v0 ships as D: own the Media row + the batch
transcript pipeline from day one; store no bytes you don't have to yet
(embeds + audio need none); rent the live edge from the first
event. Owned VOD delivery (object store served through
a commodity pull-cache) is added when self-recorded volume justifies it
— a repoint on the unchanged row, not a rebuild.
- The first live events are benchmarks, not just
events. Instrument the origin and the player from event one —
peak-concurrency shape (p100, first 10s), aggregate egress vs.
NIC line rate and the provider egress cap, segment-boundary
requests/sec, live-transcode cores measured alongside AI inference
load, player rebuffer ratio + join latency. The
self-host-vs-rent-live threshold becomes a saturation number you can
see, and the "repoint back onto owned iron" is a measurement-driven
lever, not a one-time guess.
- The Media row + tier-at-read + transcript-into-index +
signed-URL issuance are built from day one anyway — cheap up
front and, per every lens, the single most expensive thing to
retrofit.
7. Tradeoff matrix
| 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):
- I11 — The Media object is the source of truth:
media_id · member FK · tier_visibility · kind · transcript_ref · poster · duration · abstract delivery_ref.
The delivery location is one repointable field. No vendor playback URL,
DRM token, or transcode-service database is ever the stored
record. (All four lenses — own the row; the bytes are a
reference.)
- I12 — Tier-gated media is issued only as a short-lived,
signed, per-member, tier-checked delivery token from our layer; the
delivery host (self-hosted or rented) serves only already-authorized
requests and never holds the permission authority.
(Torvalds + Gregg — I9/I10 applied to the media boundary; a
permissioned index in front of a publicly-fetchable file is a wall with
the back door open.)
- I13 — Rent delivery, never custody: a durable master of
every rented/live stream and self-recorded upload is ingested into the
owned object store before the recording becomes the record.
(Cross-lens hardening — the append-only source of truth must not
have a hole where the most valuable content enters.)
New required evals (the moat is only as trustworthy as
these):
- Extend P0 (ADR-001) to cover media transcripts —
the retrieval-permission tier-leak golden set must include
media-transcript fixtures, so a tier-gated video's transcript can never
surface to an under-tier member. Fail-closed, run on every model bump /
prompt change / schema migration.
- P0b — Signed-URL tier-leak eval, fail-closed. A
tier-gated delivery token cannot be obtained by, replayed by, or shared
to an under-tier member, and it expires. Extends the tier check from the
index to the delivery boundary. Build before the first
tier-gated video ships.
- P1 — Live-event load benchmark (active, not
passive). The first live events are driven to realistic peak
concurrency and instrumented (egress vs. line rate + provider cap,
segment-boundary RPS, live-transcode CPU concurrent with AI inference,
player rebuffer ratio). The self-host-vs-rent-live decision is triggered
by a measured saturation threshold, not an incident.
- P1 — Master-custody assertion. Every recorded
stream is verified to have a durable master in the owned store before it
becomes a Media row / board thread (enforces I13).
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):
- The learning core's typed
resource_type discriminator →
video/audio are new enum values on the existing course-content table;
its event video_recording_url field → the
Event→Recording→Media path. (Estate-scout: ADAPT the substrate, not an
E7-specific donor.)
- The ADR-001 surface→index outbox → the transcript
emission path (same at-least-once, idempotent seam).
- The signed audit-journal pattern → the moderation/audit trail on
media publish + delivery-token issuance.
9. Preserved dissent
(do not resolve away)
- Rent live vs. own the whole moment (cost/CDN lens vs. the
field): a disciplined own-origin + commodity pull-CDN could
almost certainly serve every live event this premium community of
hundreds will ever hold — while keeping 100% of media on owned
iron (foundation Invariant 5) at a fixed, known cost, and not
handing a vendor the one monthly flagship moment (their
outage/region-limit is your outage, when it's least
recoverable). Renting live may solve a scale problem we don't have (the
Torvalds-dissent echo from ADR-001). The hybrid honors this by
defaulting to rent only because greenfield gives no
measurement — and by making the repoint back to
self-host the reversible lever the moment three events of real data
justify it. The dissent may win on evidence; the architecture is built
so it can, cheaply.
- Measure-first could flip the top option (Gregg's
self-dissent): at 60→low-hundreds concurrent at 3–5 Mbps
(~0.3–1 Gbps), a single 10 GbE box eats the spike without blinking, and
renting live is then premature optimization that adds a
public-URL permission-leak surface. Correct — and it makes the point:
the numbers may say self-host wins outright. We default-rent for lack of
evidence, not conviction.
- "Rent everything" (B) is genuinely tempting for a small team
(Pike + Torvalds self-dissent): if a managed host will ingest,
transcode, caption, and deliver both VOD and live, owning an object
store plus a batch transcoder is hand-rolling three commodities to dodge
one URL. The counter holds only as long as discipline
does — the moment the transcript + tier stamp live
only in the vendor's schema, B has quietly eaten the record and
a host swap becomes a data migration. The deciding factor is purely
whether we reliably copy the transcript into a row we own (I11 + I13).
This is the exact failure mode ADR-001 spent its length preventing, now
at the media boundary.
10.
Follow-up (beads to file in Phase B under the E7 epic)
- Lock invariants I11 + I12 + I13 into the foundation
spec §7.
- 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).
- 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).
- P0b signed-URL tier-leak eval (fail-closed) +
extend P0 to media transcripts — before any tier-gated video ships.
- 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).
- 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.
- 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.