Serve time: nothing leaves without permission
Fail-closed. A clip with an unconsented depicted student returns 404.A clip is joined to the students it depicts by one row per student -- the same shape the photo lane uses for a pose. That join is what makes the gate decidable: the server does not ask “is this video public”, it asks “is every child in this frame permitted to be seen by this viewer”, and if it cannot answer yes it does not serve.
The refusal is a uniform 404. Not a 403, which would confirm the clip exists; not an error body naming the storage key. A stranger probing identifiers learns the same thing from a real clip and from a fabricated one: nothing.
The captions file rides the same gate. A transcript is speech, and speech identifies a child as surely as a face does, so the caption endpoint runs the identical consent decision rather than a looser one.
publicVideoUrlIfConsented (six consumers, three anonymous serving paths -- apps/api/src/domain/photo-serving-gate.ts:138); GET /v1/public/videos/:id (apps/api/src/routes/public-videos.ts) with two dedicated pending-leak security suites; GET /v1/public/videos/:id/captions on the same gate (apps/api/src/app.ts:3512-3515).