spec: Meet design, revision 1
Video calling for the suite: sign-in plus guest grant (suite discussion 3), start-time scheduling, P2P mesh capped at four, ICE servers from CARLOS_RELAY or none (relay spec revision 6), SSE plus POST signalling (platform 113), ephemeral chat, screen share, a drive surface under suite discussion 4. Six slices. Awaiting Paul's review and an independent review.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
1 file changed,
+492
−0
docs/superpowers/specs/2026-09-07-meet-design.md+492 −0
diff --git a/docs/superpowers/specs/2026-09-07-meet-design.md b/docs/superpowers/specs/2026-09-07-meet-design.md| new file mode 100644 |
| index 0000000..1257c16 |
| --- /dev/null |
| +++ b/docs/superpowers/specs/2026-09-07-meet-design.md |
| @@ -0,0 +1,492 @@ |
| +# 🤖 Meet — design |
| + |
| +Design, 2026-09-07. Direction approved by Paul in conversation; this is the |
| +written form. **Revision 1.** Drafted by the Meet session (Fable 5.1) from |
| +a brainstorm with Paul on 2026-09-06 and 2026-09-07. Every ruling in §1 |
| +attributed to Paul was given in that conversation or on the discussion it |
| +cites. |
| + |
| +Meet is video calling for the Oficina suite: a member makes a meeting, |
| +now or for later, sends one link, and the people who follow it set up |
| +their camera and microphone, wait to be let in if they are not on the |
| +team, and talk. Calls are browser to browser. The instance carries the |
| +pages, the sign-in, the signalling and the in-call chat, and never a |
| +frame of media. One instance per team, on the CARLOS platform, at |
| +`meet.oficina.works` for the first team. |
| + |
| +Built beside [Docs](https://amadan.net/oficina/docs), |
| +[Calendar](https://amadan.net/oficina/calendar), |
| +[Slides](https://amadan.net/oficina/slides) and the suite |
| +[doctrine](https://amadan.net/oficina/suite). Where a decision is already |
| +made over there, this spec consumes it rather than restating it (§0.2). |
| + |
| +--- |
| + |
| +## 0. The rules this app lives under |
| + |
| +### 0.1 The one rule |
| + |
| +**Media never touches the server.** The instance holds a sealed title, |
| +who was in the room and when, and nothing that was said or shown in a |
| +call. Chat, presence and peer state live in memory for the life of a |
| +room and are gone when it ends. |
| + |
| +Falsifiable: publish the database, the WAL and the logs, and the worst |
| +anyone learns is the shape of the team's meetings and who was in them, |
| +never what a meeting was called, what was said, or what was on anyone's |
| +screen. Enforced by a test that greps the raw SQLite file after a |
| +checkpoint for a known title, and by the absence of any handler that |
| +accepts a media byte. |
| + |
| +The claim narrows when the platform SFU (stage 4b) arrives: media will |
| +then pass through a platform process the team already trusts with its |
| +pages. The README says so in the paragraph that introduces the relay, |
| +and this spec's §5.6 records the change of claim in advance. |
| + |
| +### 0.2 Consumed suite rulings |
| + |
| +This app cites the suite sharing doctrine (`oficina/suite`, |
| +`docs/superpowers/specs/2026-09-05-suite-sharing-doctrine.md`, revision |
| +8 at 1ac4323) by ruling number and never restates it. |
| + |
| +| Ruling | Used for | |
| +|---|---| |
| +| 1, 5 | One instance per team; the first deploy is the **lone shape** (§7.1). | |
| +| 4, 12 | Identity and roster belong to the home. Until the home mints assertions, the lone instance keeps its own allowlist exactly as Docs and Slides do. | |
| +| 6 | No product-wide egress. Meet never names a STUN or TURN server of its own; ICE servers come from the platform (§5.2) or there are none (§5.5). | |
| +| 9 | Every write names its real actor: a member, a guest who signed in, or an agent (§8). | |
| +| 15 | Three rungs plus a read-only link. A meeting link is a capability to obtain a **guest grant at sign-in**, the `granted` rung, and never a way into a room without a name (§3). Ruled for Meet on `oficina/suite` discussion 3, Paul, 2026-09-06. | |
| +| 16 | A link token is stored hashed and shown once (§3.1). Ruling 16 is on the doctrine's `worktree-worktree-token-at-rest` branch (suite discussion 2), not yet on main; Meet adopts it now so it never has the Docs defect that produced it. | |
| + |
| +Also consumed: `oficina/suite` discussion 4 (agents as their own |
| +principal, on behalf of a person) for the drive surface (§8); the CARLOS |
| +media roadmap (`carlos/platform`, |
| +`docs/superpowers/specs/2026-09-07-media-platform-roadmap-design.md`, |
| +merged at 8291cb2) and the stage 4a relay spec |
| +(`docs/superpowers/specs/2026-09-07-relay-stun-turn-design.md`, revision |
| +6, contract frozen 2026-09-07) for §5; `carlos/platform` discussion 113 |
| +for the signalling transport (§4.1). |
| + |
| +### 0.3 What this spec does not claim |
| + |
| +- No claim that a call connects across every network. Without a relay, |
| + calls connect on the same network only (§5.5). With the platform relay, |
| + a network that permits only TCP 443 still fails, and the pre-join |
| + screen says so. |
| +- No claim about the home. Meet is built lone and adopts the home's |
| + assertions when `oficina/home` ships them. |
| +- No claim that the four-participant cap is a product limit. It is the |
| + mesh's limit, lifted by stage 4b. |
| + |
| +--- |
| + |
| +## 1. Settled decisions (don't relitigate casually) |
| + |
| +| # | Ruling | Rejected | Who, when | |
| +|---|---|---|---| |
| +| 1 | **Every participant signs in.** An outsider following a meeting link takes a guest grant scoped to that meeting. No anonymous join. | An ephemeral participant named only by what they typed, admitted from a waiting room: a fourth actor kind for one app | Paul, 2026-09-06, suite discussion 3 | |
| +| 2 | **The repo is `oficina/meet`.** English name, like docs, sheets, calendar. | `oficina/conferencia` | Paul, 2026-09-06 | |
| +| 3 | **File upstream, then build in-app and extract.** Platform pieces (UDP, relay) were filed first because no app can substitute for them; framework pieces are built here and offered to rastrillo on the second consumer. | Asking rastrillo for a WebSocket helper before a consumer exists | Paul, 2026-09-06 | |
| +| 4 | **Scheduling is a start time on the meeting.** The link works whenever a signed-in person follows it; the join page shows when the meeting is meant to start. A guest grant expires two hours after the start time, or two hours after creation when there is no start time. | A start-to-end window with a clock-driven state machine; recurring meetings | Paul, 2026-09-07 | |
| +| 5 | **Pure P2P, room capped at four.** Each participant holds one peer connection per other participant. | A relay Meet runs; a third-party TURN by default | Paul, 2026-09-06; cap is this session's, from mesh bandwidth | |
| +| 6 | **ICE servers come from platform config or nowhere.** `CARLOS_RELAY` present: STUN and TURN from the platform, TURN credentials minted per call. Absent: no ICE servers, calls connect on the same network only, the pre-join screen says so. | A per-team STUN or TURN URL field (doctrine §8: the recipient is the app's decision, never the team's); one app-named public STUN provider as an opt-in interim | Paul, 2026-09-07, choosing "no relay" as the interim once stage 4a was ruled to start | |
| +| 7 | **Signalling is SSE plus POST**, one stream per room, following Docs live-sync §3. | A WebSocket: rastrillo has no helper, `Origin` would be hand-checked, and no upgrade crosses a tunnel-backed host (platform 113) | This session, 2026-09-07; platform 113 confirmed the constraint | |
| +| 8 | **Chat is ephemeral**, a signalling message rendered client-side, held nowhere at rest. | Persisted chat as content with named actors, the suite's first chat product | This session, 2026-09-07 | |
| +| 9 | **A guest waits to be admitted.** A member joins directly; a guest who signed in and holds a grant still waits until any member in the room admits them. | Auto-admit on grant | This session, 2026-09-07, built regardless of ruling 1's outcome | |
| +| 10 | **No calendar integration in version one.** Meet owns rooms and a start time. Calendar's "recorded, never invited" rule stands; a sealed conference URL on an event is Calendar's later change. | Meet growing a calendar; Calendar sending invitations | This session, 2026-09-07 | |
| +| 11 | **An agent can make, list, link and end meetings; it cannot join a call.** The media plane is browser-only. | An agent participant | This session, 2026-09-07, under suite discussion 4 | |
| + |
| +--- |
| + |
| +## 2. The meeting model |
| + |
| +Three tables of Meet's own beside the scaffold's persons table, and `agents` in slice 5 (§8.1), gorm on SQLite through rastrillo, migrations numbered as |
| +the scaffold does. |
| + |
| +**`meetings`** — `id`, `sqid` (public, in URLs), `title_sealed`, |
| +`starts_at` (UTC, nullable), `created_by` (actor, §7.2), `created_at`, |
| +`ended_at` (nullable), `ended_by` (actor, nullable). A meeting without |
| +`starts_at` is "now". A meeting with `ended_at` set refuses joins and |
| +its links answer 404 exactly as a revoked one does. |
| + |
| +**`meeting_links`** — `id`, `meeting_id`, `token_hash` (SHA-256 of the |
| +token, ruling 16), `created_by`, `created_at`, `revoked_at`. One meeting |
| +may hold several links so a revoked one can be replaced. The token is |
| +shown to its maker once, on the page that created it, and never again. |
| + |
| +**`guest_grants`** — `id`, `meeting_id`, `person_id`, `granted_at`, |
| +`expires_at`, `admitted_at` (nullable), `admitted_by` (actor, nullable). |
| +Minted at sign-in when a non-member follows a live link (§3.2). One row |
| +per person per meeting; following the link twice touches nothing. |
| + |
| +**`persons`** — the scaffold's users table: `id`, `email`, `name`, |
| +`created_at`. `member` is a boolean derived at request time from the |
| +allowlist in lone shape, and from the roster copy once the home exists. |
| +It is not stored, so a change to the allowlist takes effect on the next |
| +request. |
| + |
| +Sealing reuses the shape of calendar's `internal/calendar/seal`: one |
| +DEK from platform config (`MEET_DEK`), HKDF purpose keys, AES-GCM, and |
| +the DEK never written to the database it opens. Meet seals one column. |
| +Lifting the package into estilo or rastrillo is the extraction trigger |
| +the calendar spec already names; this is the second consumer, so the |
| +plan carries a task to propose it. |
| + |
| +Nothing else is at rest. Rooms, peers, admission state, chat and |
| +presence live in the hub (§4.2). |
| + |
| +--- |
| + |
| +## 3. Joining |
| + |
| +### 3.1 Making a meeting and its link |
| + |
| +`GET /` lists the team's meetings, upcoming first, ended ones in a |
| +collapsed section. `POST /meetings` takes a title and an optional start |
| +time and creates the meeting and its first link in one transaction. The |
| +response page shows the link once, with a copy button when scripts are |
| +on and a selectable text field when they are off. `POST /meetings/{sqid}/links` |
| +mints a replacement and revokes the rest. |
| + |
| +Links are `https://<host>/j/<token>`, with a token of 32 random bytes, |
| +base64url. The route is mounted outside the session group like |
| +calendar's feed route, because the person following it is by definition |
| +not signed in yet. |
| + |
| +### 3.2 Following a link |
| + |
| +`GET /j/{token}` looks the hash up. Unknown, revoked, ended or expired |
| +gives one 404 for all four. Live gives one of three redirects: |
| + |
| +- **A signed-in member** goes to the holding screen, `/m/{sqid}/join`. |
| +- **A signed-in non-member** with no grant gets one minted now, then |
| + goes to the holding screen. With a grant, straight there. |
| +- **Nobody signed in** goes to `/signin?return=/j/<token>`. The token |
| + rides in the return path and nowhere else, so sign-in completes and |
| + the same route runs again with a session. Sign-in refuses a return |
| + path that is not a `/j/` path on this host. |
| + |
| +A non-member may sign in at all because the allowlist gates |
| +**membership**, not sign-in, on this app alone: the magic link is sent |
| +to any syntactically valid address, and what the address gets is a |
| +grant or nothing. This is the one place Meet departs from Docs' |
| +`auth.go`, and it is stated here because the departure is the whole |
| +guest feature. An address that signs in without following a live link |
| +lands on a page that says it is not a member of this team and has no |
| +meetings, and can do nothing else. |
| + |
| +The expiry of a grant is set at mint time from ruling 4. The grant is |
| +checked, not cached, on every request under `/m/{sqid}` (doctrine |
| +ruling 3). |
| + |
| +### 3.3 The holding screen |
| + |
| +`/m/{sqid}/join` is the only page in Meet that requires scripts, and it |
| +says so in place when they are off (§9). It shows the meeting's title, |
| +its start time if it has one, the person's own name, a camera preview, |
| +device pickers for camera, microphone and speaker, and mute toggles. |
| +Choices persist in the browser's local storage per device id, nothing |
| +of them reaches the server. If `CARLOS_RELAY` is absent the screen |
| +carries the floor sentence from §5.5 above the join button. |
| + |
| +Pressing join opens the room stream (§4) and posts `join`. A member |
| +enters. A guest sees "waiting for someone to let you in" until a member |
| +in the room posts `admit` for them, and leaves the queue if they close |
| +the tab. Every member in the room sees the knock with the guest's name |
| +and one button. |
| + |
| +### 3.4 The room |
| + |
| +`/m/{sqid}` renders the grid of participants, own controls (mute, |
| +camera, share screen, leave), the chat panel and the admission queue. |
| +The creator or any member can end the meeting from the room or from |
| +the list, which sets `ended_at`, closes every stream with an `ended` |
| +message and revokes every link. Leaving is a `leave` message and the |
| +tab closing; the server also treats a stream that has been closed for |
| +thirty seconds as a leave. |
| + |
| +--- |
| + |
| +## 4. Signalling |
| + |
| +### 4.1 Transport |
| + |
| +One SSE stream per room and participant, `GET /m/{sqid}/stream`, under |
| +the session, with the Docs live-sync heartbeat (15 s) and connection TTL |
| +(5 min) so hibernation cannot be held off by a dead stream. Upstream |
| +messages are `POST /m/{sqid}/signal` with a JSON body, under CSRF. A |
| +client that loses its stream reconnects and posts `join` again; peers |
| +that see a second `join` from the same peer id renegotiate. |
| + |
| +A WebSocket would fit this shape better and is deliberately not used: |
| +Docs live-sync §3 lists the costs, and platform discussion 113 confirmed |
| +that no upgrade crosses a tunnel-backed host and no placement signal |
| +exists. `rastrillo/rastrillo` discussion 17 records the intent to build |
| +one when a second consumer appears. |
| + |
| +### 4.2 The hub |
| + |
| +An in-process room hub in the shape of Sheets' `live/hub.go`: opaque |
| +payloads, a bounded per-subscriber buffer, a slow subscriber declared |
| +lost and told so. It holds, per room: the set of peers (peer id, actor, |
| +display name, member or guest, admitted or waiting, joined at), and |
| +nothing else. Peer ids are random per tab. The hub is per process, |
| +which is fine on one host and would not survive a multi-host fleet; |
| +that is noted, not solved. |
| + |
| +### 4.3 Messages |
| + |
| +Every message carries `type`, `from` (set by the server, never trusted |
| +from the client) and, where targeted, `to`. Types: |
| + |
| +| Type | Direction | Who may send | Server does | |
| +|---|---|---|---| |
| +| `join` | up | anyone with access | Adds the peer. Member: admitted, broadcast `peer` to the room, send the roster to the joiner. Guest: waiting, broadcast `knock` to members only. Refuses with `full` at four admitted peers. | |
| +| `admit` | up | a member in the room | Marks the guest admitted, broadcasts `peer`, sends the guest the roster. | |
| +| `leave` | up | the peer | Removes the peer, broadcasts `gone`. | |
| +| `offer`, `answer`, `candidate` | up, targeted | an admitted peer | Relays to `to` unchanged. Never broadcast. | |
| +| `chat` | up | an admitted peer | Broadcasts with `from` and the sender's display name. Not stored. 2 KB limit. | |
| +| `share` | up | an admitted peer | Broadcasts `{on: bool}` so peers can label the tile. | |
| +| `roster`, `peer`, `gone`, `knock`, `full`, `ended`, `lost` | down | server | As named. | |
| + |
| +The server validates type and shape and the sender's standing, and |
| +reads nothing inside `offer`, `answer` or `candidate`. Which peer makes |
| +the offer is decided by peer id order, so two peers never both offer. |
| + |
| +--- |
| + |
| +## 5. Media |
| + |
| +### 5.1 The mesh |
| + |
| +One `RTCPeerConnection` per other admitted peer, created when a `peer` |
| +message arrives or the roster is received. The lower peer id offers. |
| +Tracks: microphone and camera from the holding screen's choices, and a |
| +display-media track when sharing. All ICE candidates are trickled as |
| +`candidate` messages. A connection that reaches `failed` shows the |
| +tile as "could not connect" with a retry that restarts ICE; nothing is |
| +silently spun. |
| + |
| +### 5.2 ICE servers from the platform |
| + |
| +At startup the instance reads `CARLOS_RELAY`, standard base64 of a JSON |
| +array, nearest region first, each element `{region, urls, key, |
| +username_suffix}`. Absent or empty means §5.5. The decoded value is |
| +held in memory and never logged. |
| + |
| +Per call, `GET /m/{sqid}/ice` (under the session and the room's access |
| +check) returns the `RTCIceServer` list: every `stun:` URL bare, every |
| +`turn:` URL with `username = "<unix expiry>:" + username_suffix` and |
| +`credential = base64(HMAC-SHA256(rawkey, username))`, where `rawkey` is |
| +the element's key decoded from base64 to 32 bytes. Expiry is three |
| +hours ahead, inside the four-hour maximum. The list is built from |
| +whatever URLs are present; no shape is assumed, and no `turns:` URL is |
| +expected in version one. |
| + |
| +The minting function has one test that reproduces the platform's |
| +vector: key `uMhwW4P/9O5dQUIs91t2CCHGhwJbeiM0xQMQ2G0CJLU=`, username |
| +`1900000000:abc:meet:0123456789abcdef:k1`, credential |
| +`jRU479Y3GO45nrRHqxBf3BMeBHE6Fip1cwAccNv28MQ=`. |
| + |
| +### 5.3 Re-mint before the deadline |
| + |
| +Credential expiry is a hard deadline on the relay: the allocation ends |
| +whether or not the browser sends anything. The client fetches a fresh |
| +list at two and a half hours, calls `setConfiguration` on every peer |
| +connection and restarts ICE. Under three hours nothing happens. This |
| +is a small client task, and it is the reason §5.2 mints at three |
| +hours rather than four. |
| + |
| +### 5.4 Screen share |
| + |
| +`getDisplayMedia`, added as a track to every peer connection, with a |
| +`share` message so peers label the tile, and renegotiation on each. |
| +Stopping removes the track and renegotiates. One sharer at a time is |
| +not enforced; the grid shows every shared screen it receives. |
| + |
| +### 5.5 The floor |
| + |
| +With no `CARLOS_RELAY`, the ICE list is empty and browsers gather host |
| +candidates only. Calls connect between participants on the same |
| +network and nowhere else. The pre-join screen says, above the button: |
| +"This team's Meet has no relay yet, so calls connect only between |
| +people on the same network." The README's first paragraph says the |
| +same. Nothing pretends. |
| + |
| +With `CARLOS_RELAY` and a network that permits only TCP 443, the call |
| +still fails, because the relay's TLS listener is deferred by the |
| +platform. The failed tile's text names that case. |
| + |
| +### 5.6 What changes at stage 4b |
| + |
| +When the platform SFU exists and a team opts in, media passes through |
| +a platform process. §0.1's claim becomes "media never touches this |
| +app's server". The mesh code is replaced by one connection to the SFU, |
| +the cap is lifted, and this spec gets a revision, not a footnote. |
| + |
| +--- |
| + |
| +## 6. Chat |
| + |
| +A `chat` message with text up to 2 KB, rendered in the panel with the |
| +sender's name and a local timestamp, escaped as text. It is not stored, |
| +not sent to a peer who joins later, and gone with the tab. Links are |
| +not linkified in version one. This is the suite's first chat, and |
| +estilo's rule makes Meet the first builder: the panel is written as one |
| +self-contained template partial and one script file so a second app can |
| +adopt it. |
| + |
| +--- |
| + |
| +## 7. Identity and the instance |
| + |
| +### 7.1 Lone shape |
| + |
| +Sign-in is Docs' `internal/docs/auth.go` adopted with the one change in |
| +§3.2: `MEET_ALLOWED_ADDRESSES` decides membership, not whether a magic |
| +link is sent. `MEET_ORIGIN`, `MEET_INSTANCE_KEY`, `MEET_SMTP_*` and |
| +`MEET_DEK` are startup refusals outside `-env development`. The same |
| +303 for every address. Slides already recorded that its copy of this |
| +file is the extraction trigger; Meet's is the third, and the plan |
| +carries the same task rather than a new one. |
| + |
| +### 7.2 Actors |
| + |
| +Every write records an actor: `person:<id>`, or `agent:<id>` for the |
| +drive surface. Meetings, links, grants, admissions and endings carry it. |
| +A guest's actor is `person:<id>` like a member's; what differs is |
| +standing, decided per request (§2). |
| + |
| +### 7.3 Deploy |
| + |
| +One static binary, `CGO_ENABLED=0`, `linux-arm64`, `make release` with |
| +the version stamped, `carlos deploy --app meet --account oficina` with |
| +the build probe on `/api/version`. `carlos domains attach |
| +meet.oficina.works`. Secrets `MEET_INSTANCE_KEY`, `MEET_SMTP_PASS` and |
| +`MEET_DEK` via `carlos secrets`, the rest via `carlos env`. The route is |
| +a unit route, which is what `carlos relay enable --app meet` needs. The |
| +relay itself is switched on by an operator when stage 4a reaches the |
| +account's region; Meet needs no change for it beyond reading the value |
| +at the restart the platform performs. |
| + |
| +--- |
| + |
| +## 8. The drive surface |
| + |
| +### 8.1 Principals |
| + |
| +Adopted from Slides §7.1 unchanged: a member mints an agent token from |
| +Settings, `agents(id, name, operator, token_hash, created_at, |
| +revoked_at)`, principal `<name>~<operator email>`, robot badge and |
| +operator name on every write, bearer on the API only, never a cookie |
| +on the API and never a bearer on HTML routes. Cited to suite discussion |
| +4 for the credential shape at the home; until that is ruled, the token |
| +is instance-minted exactly as Slides does. |
| + |
| +### 8.2 Operations |
| + |
| +`GET /api/v1/meetings` (list, with `sqid`, title, `starts_at`, |
| +`ended_at`, `link_present`), `POST /api/v1/meetings` (title, optional |
| +`starts_at`; returns the meeting and its link, the only time the API |
| +returns a token), `POST /api/v1/meetings/{sqid}/links` (replace the |
| +link), `POST /api/v1/meetings/{sqid}/end`, `GET /api/v1/meetings/{sqid}` |
| +(the meeting and, while it runs, the count and names of admitted |
| +peers, never the chat). No operation joins a call (ruling 11). |
| + |
| +### 8.3 MCP |
| + |
| +A thin adapter over §8.2, deferred until a consumer asks by name. |
| + |
| +--- |
| + |
| +## 9. The scriptless floor |
| + |
| +Everything except the holding screen and the room works with scripts |
| +off: the list, creating a meeting, seeing and copying a link, replacing |
| +it, ending a meeting, signing in, the agent token page. The holding |
| +screen with scripts off renders the title, the start time, the floor |
| +sentence if it applies, and one paragraph saying a call needs |
| +JavaScript and why, with nothing hidden behind a broken button. This |
| +is the suite's first app whose core surface needs scripts, and |
| +PRODUCT.md says so in the "what is deliberately not here" list rather |
| +than letting the scaffold's claim stand. |
| + |
| +Hand-written static JavaScript, no bundler, no npm, in three files: |
| +`devices.js` (the holding screen), `room.js` (signalling and the mesh), |
| +`chat.js` (the panel). Roughly two thousand lines between them, the |
| +largest hand-written script in the suite so far, which is why they are |
| +three files and not one. |
| + |
| +--- |
| + |
| +## 10. Estilo and copy |
| + |
| +Meet adopts, in this order, whatever estilo holds at the time each |
| +slice lands: the theme, the sign-in composition once one is extracted, |
| +error and empty states. Meet builds nothing in estilo in version one |
| +except what §6 prepares. Every new user-facing string goes through the |
| +copy review before it lands (`copy-review`, the suite's mechanism), and |
| +the plan lists the strings per slice so the review is one sitting per |
| +slice, not one per string. |
| + |
| +--- |
| + |
| +## 11. Deliberately not built yet |
| + |
| +Recurring meetings. Calendar integration. Recording. A relay of Meet's |
| +own. Rooms above four. Persisted chat. Reactions. Dial-in. Captions. |
| +Home integration. Multi-host signalling. Push-to-talk. Virtual |
| +backgrounds. Each is named so the next session does not rediscover it. |
| + |
| +--- |
| + |
| +## 12. Slices |
| + |
| +1. **Meetings and links.** The four tables, sealing, the list, create, |
| + the link shown once, replace, end. Scriptless throughout. Sign-in |
| + with the §3.2 change and the `/j/` route with all three redirects and |
| + the one 404. Grants minted and checked. No room yet: joining lands on |
| + a page that says the room is the next slice. |
| +2. **Holding screen and room, same network.** `devices.js`, the hub, |
| + the stream, the signalling messages except `chat` and `share`, `room.js` with the mesh, |
| + admission, leave, end. `CARLOS_RELAY` absent. Two headless browsers |
| + complete a call on localhost. |
| +3. **Relay.** `CARLOS_RELAY` parsing, minting with the vector test, |
| + `/ice`, the re-mint. Testable without a relay by asserting the list |
| + the browser receives; tested against the real relay when stage 4a |
| + reaches flagship. |
| +4. **Chat and screen share.** `chat.js`, the `chat` and `share` |
| + messages, display-media tracks and renegotiation. |
| +5. **Drive surface.** Agent tokens, the five operations, the badge. |
| +6. **Deploy.** Release, domain, secrets, canary, `PRODUCT.md`, |
| + `AGENTS.md`, the README with the floor sentence in its first |
| + paragraph, and the `Consumed suite rulings` commit that suite |
| + Appendix A.6 will point at. |
| + |
| +Each slice is its own branch, discussion and review, per the suite's |
| +overnight protocol (`oficina/suite` discussion 5). |
| + |
| +--- |
| + |
| +## 13. Testing |
| + |
| +Go: store and grant tests including expiry and the one-404 rule; hub |
| +tests for cap, admission, targeted relay, slow subscriber; handler |
| +tests for every redirect in §3.2 and every refusal in §4.3; the sealing |
| +grep test from §0.1; the minting vector from §5.2; `make ci` green. |
| + |
| +Browser, with chromedp as calendar does, and Chrome's |
| +`--use-fake-device-for-media-stream` and `--use-fake-ui-for-media-stream`: |
| +the holding screen enumerates devices and previews; two tabs in one |
| +headless Chrome complete a call on localhost with host candidates and |
| +each sees the other's track; a guest knocks and is admitted; chat |
| +arrives; a share labels a tile; the scriptless holding page renders its |
| +paragraph. No test touches a relay until one exists. |
| + |
| +--- |
| + |
| +## 14. Review record |
| + |
| +Revision 1: awaiting Paul's review of the written form and an |
| +independent review by a model at least as capable as the author, per |
| +the suite protocol. Neither has happened; this line is the record. |