Watching what the house holds
Rendered from spec at76d46f3 (2026-10-25) · its source · Edit this page
Status: Accepted · Audience: Household · Area: D — Content & household
Purpose
What a member needs from the stack to find a film, see what it is and play it, on their own account and under their own limits.
N3 and G10 are the two surfaces a member plays on. Both render what the core answers and decide nothing themselves, so everything they draw about a title — its artwork, what it is, how far through it a member got, where it streams from — has to be an answer the core gives. This feature is those answers, and the grant and the guard that keep each one to the member who asked (ADR-0027).
Behaviour
A grant on the member’s own account
A member’s paired client asks the core for a grant for the device it plays on, named by an id the client keeps. The core, as the media server’s administrator, signs that device in for the member’s own user and answers the session’s token to the client once; it keeps no copy, and the client speaks to no media-server API of its own. The session is the member’s, so every question asked with it is answered under the member’s age limit and library access (D8). Nobody learns the member’s password (D6). A grant lasts thirty days from the last time the member’s client spoke to the core, so a phone in use keeps playing and one left in a drawer stops. The core ends it when it lapses and at once when the member is removed from the household.
The front door guards every byte
The media server answers its byte endpoints — a stream, a segment, a subtitle, an item’s image — without asking who is there. So every one of them is reached only through a guard the stack keeps in front of the media server, in every form that serves it (B1, G5). Before it passes a request on, the guard asks the media server whether the session presented may see that item. An item it may not see, a session that is absent, ended or wrong, and a question that is not answered are all refusals. The media server’s own port is not published to the household beside it.
A client presents its session as Authorization: Bearer <token>, in no media
server’s own form, so the app a member plays in does not change with the server
behind the door. The guard puts a token of exactly the shape the media server
issues into the server’s own form before it asks, and carries nothing else written
after Bearer over, so a client cannot add to what the media server is told.
The guard serves over TLS with a certificate the core makes for it, so no authority outside the house is asked for one, and the core states the fingerprint of that certificate beside every location it gives there. A client pins the door the way it pins the stack.
Where each title’s pictures and stream are
For each title the core lists for a member, it states where its poster and backdrop are served at the guarded door, and where the title streams from. Each location is built from the household address the stack publishes for the media server, so a client never puts one together. Where the core does not know that address, it states no location and says why. Only an item’s own images are located: a person’s, a genre’s or a studio’s image is not an item’s bytes, and the guard’s question does not reach it.
What a title is, and how far through it a member got
A title’s details — what it is about, how long it runs, its genres, its certificate, when it came out, and a series’ seasons and episodes — are answered as the member’s own session reads them, so a title outside their limits is answered as absent. What a member was part-way through, and how far, is answered the same way.
How far a member got
A player tells the core how far through a title the member is, as it plays, when it is paused or moved, and when it stops. The core hands that to the media server as the member’s own progress and keeps no copy, so what a member was part-way through is the media server’s answer on every device they play on.
Live TV, on a pass the core issues
A live stream is not an item the guard can ask about. The media server serves the bytes of whichever live stream a request names, under any item, and answers its item question for a channel even to a member it does not let watch live TV. So the guard refuses every live stream’s bytes on an item’s route, and a member watches a channel only through the core.
The member’s client asks the core to play a channel. The core asks the media server, with the member’s own session, whether the member may watch that channel, and reads the programme on it now against the member’s limits (D8). Where both allow it, the core opens the live stream as the member and answers the client where its bytes are at the guarded front door, with a pass naming that live stream and that member, which lapses after a short time. The guard asks the core, on every request for a live stream’s bytes, its playlists and segments included, whether the pass it carries is good for exactly that live stream, and serves nothing the core does not confirm. The key the pass is made with never leaves lemonfiber’s own processes: the core issues passes, and its pass service, beside the guard, confirms them (ARCH-R291). The core closes the live stream when its pass lapses or the member is removed.
States
| State | Meaning |
|---|---|
| Granted | The member’s client holds a session on the member’s own account. |
| Lapsed | Thirty days passed with no word from the member’s client; the session is refused at the server and at the door. |
| Revoked | The member was removed; every session their devices held is ended. |
| Located | A title carries where its artwork and stream are served. |
| Unlocated | The core does not know the household address, and the title says why. |
Edge cases
| Situation | Behaviour |
|---|---|
| The household address is not known | No location is stated for any title, and each says why. Playback is declined on the surface. |
| A title has no artwork | No artwork location is stated for it, and the core answers its picture as absent. The surface draws its name. |
| A browser shows a member’s shelf | It reads each picture from the core with the member’s own session, because it can neither present a session to the door nor pin the door’s certificate. |
| A picture is not an image, or is larger than 2 MiB | The core answers it as absent rather than passing it on. |
| A member asks for an item outside their libraries by id | The door refuses it, because the media server says the session cannot see it. |
| A request presents no session | The door refuses it. |
| The media server does not answer the door’s question | The door refuses the request rather than passing it through. |
| A member is removed mid-film | Their sessions are ended, and the next byte the door is asked for is refused. |
| A member without live TV asks to play a channel | The core refuses it and opens nothing. |
| A request names a live stream on an item’s route | The door refuses it, whatever item is in the path. |
| A live stream’s bytes are asked for with a lapsed pass, a pass for another live stream, or none | The door refuses them. |
| The core does not answer the door’s question about a pass | The door refuses the request rather than passing it through. |
| The door presents a certificate other than the one the core stated | A client refuses it. |
Acceptance criteria
| ID | Requirement |
|---|---|
| D11-R1 | A member’s paired client MUST be able to obtain a grant to play on the member’s own media-server account for a device it names, authorised by the core without anybody learning the member’s password and answered to the client once with the session’s token, which the core MUST NOT keep. The core MUST end it when it lapses and when the member is removed (D6-R8). |
| D11-R2 | Every byte the media server serves for an item, its images included, MUST pass a guard that asks the media server whether the presented session may see that item, and MUST be refused where it may not, where no session is presented, or where the answer does not come. The media server’s own port MUST NOT be published to the household beside the guard. The guard MUST accept a session presented as Authorization: Bearer <token>, and MUST carry nothing written after the token over to the media server. |
| D11-R3 | The core MUST state, for each title it lists for a member, where its poster and backdrop are served at the guarded front door, built from the household address the stack publishes for the media server. It MUST state nothing where it does not know that address, and MUST NOT state the location of an image that is not an item’s own. |
| D11-R4 | The core MUST answer a title’s details (overview, runtime, genres, certificate, release date, and seasons and episodes) as the member’s own session reads them, and MUST answer a title outside the member’s limits as absent (D8-R9). |
| D11-R5 | The core MUST answer what a member was part-way through, with how far through each was, as the member’s own session reads it. |
| D11-R6 | Each title a member may watch MUST carry the location that streams it at the guarded front door where the core knows the household address and the title streams, and MUST carry the reason in its place where it does not. |
| D11-R7 | The guarded front door MUST serve over TLS, and every location the core states at it (D11-R3, D11-R6) MUST be answered beside the fingerprint of the certificate the door presents, so that a client pins the door as it pins the stack (N1-R22). |
| D11-R8 | The core MUST accept, from a member’s paired client, how far through a title the member is and whether they finished it, and MUST hand it to the media server as that member’s own playback progress, keeping no copy of its own. |
| D11-R9 | The core MUST serve a title’s poster and backdrop to the member whose shelf holds it, read from the media server as that member, at GET /api/held/{id}/poster and GET /api/held/{id}/backdrop. It MUST answer only an image/* body of at most 2 MiB, marked Cache-Control: private, MUST keep no copy, and MUST answer a title outside the member’s limits, or a picture the media server does not hold, as absent. |
| D11-R10 | The core MUST serve the poster of a title a member’s search offered and the house does not hold, to that member alone, fetched by the core from the location the request service gave for that title and nowhere else, image/* only, at most 2 MiB, with Cache-Control: private; it MUST refuse a location that is not HTTPS or that resolves to a loopback, private or link-local address, and the member’s client MUST NOT be sent to the catalogue’s host. |
| D11-R11 | A member’s paired client MUST be able to watch a live TV channel only through the core, which MUST open the channel’s live stream on the member’s behalf only where the media server answers the channel’s live TV read with the member’s own session and the programme on it is within the member’s limits (D8-R9). The guard’s item question MUST NOT stand in for either answer. |
| D11-R12 | The core MUST answer the client the location of the live stream’s bytes at the guarded front door with a pass the core issued for that live stream and that member alone, which lapses within a bounded time, and MUST close the live stream when its pass lapses or the member is removed (D6-R8). |
| D11-R13 | The guard MUST serve a live stream’s bytes, its playlists and segments included, only to a request whose pass the core confirms, asked of the core on every such request, for exactly the live stream the request names, and MUST refuse one whose pass is absent, lapsed or not confirmed, or where the core does not answer. A live stream’s bytes MUST NOT be served on an item’s byte route (D11-R2). |