| 1 | # Product |
| 2 | |
| 3 | <!-- impeccable:product-schema 1 --> |
| 4 | |
| 5 | > **How this file was written.** Written unattended, on the same |
| 6 | > instruction that produced `oficina/docs`' `PRODUCT.md`: every fact |
| 7 | > below comes from repository evidence, not an interview — |
| 8 | > `docs/superpowers/specs/2026-09-07-meet-design.md` (revision 9, plus |
| 9 | > the 2026-09-10 and 2026-09-12 amendments), `AGENTS.md`, the slice |
| 10 | > records under `docs/superpowers/reviews/`, `native/README.md`, and the |
| 11 | > shipped code in `internal/calls/` on branch `slice/6-deploy`, main |
| 12 | > `9225ec2`. Facts are cited to a spec section or a file. A reading |
| 13 | > rather than a citation is marked **[inferred — confirm]** and should |
| 14 | > be corrected, not trusted. A bare "ruling N" is the Calls spec's §1; a |
| 15 | > doctrine ruling is always written "suite ruling N". |
| 16 | |
| 17 | ## Platform |
| 18 | |
| 19 | Web, plus native Mac and iPhone/iPad clients over the same server |
| 20 | contracts (spec §8.4's 2026-09-14 amendment, `native/README.md`). |
| 21 | |
| 22 | ## Users |
| 23 | |
| 24 | **Primary: a member of one team**, making and joining that team's video |
| 25 | calls. The instance boundary is the team — one instance per team |
| 26 | (preamble; suite ruling 1, 5, §0.2), the "lone shape" until a suite home |
| 27 | exists (§7.1). **Secondary: an outside guest**, admitted with a real |
| 28 | identity, never as an anonymous URL holder: every participant signs in, |
| 29 | and a meeting link gives an outsider a **guest grant**, never a way in |
| 30 | without a name (ruling 1; suite ruling 15); a guest waits until an |
| 31 | admitted member lets them in (ruling 9, §3.3). |
| 32 | |
| 33 | Access is a magic link gated by `CALLS_ALLOWED_ADDRESSES` |
| 34 | (`internal/calls/config.go`), with optional Home sign-in when an operator |
| 35 | sets all four `CALLS_HOME_*` settings (§7.1.1) — Home only *links* an |
| 36 | existing member after independent local sign-in; it never creates a |
| 37 | person or grants membership. Unlike Docs' §8.1, the spec states no |
| 38 | passkey ruling of its own. **[inferred — confirm]** the team is assumed |
| 39 | small enough that an address allowlist is appropriate; the spec states |
| 40 | no target size. |
| 41 | |
| 42 | ## Product Purpose |
| 43 | |
| 44 | Calls is **video calling for one Oficina team**: a member makes a |
| 45 | meeting, sends one link, and the people who follow it set up camera and |
| 46 | microphone, wait to be let in if not on the team, and talk (preamble). |
| 47 | "The instance carries the pages, the sign-in, the signalling and the |
| 48 | in-call chat, and never a frame of media" (§0.1) — falsifiable: publish |
| 49 | the SQLite file, its WAL, and every log line, and the worst anyone |
| 50 | learns is the shape of the team's meetings and who minted what, never a |
| 51 | title, chat text, or signalling payload, enforced by a WAL-grep test and |
| 52 | a captured-logger test, not merely asserted (§0.1). Since 2026-10-03 the log also |
| 53 | holds quality and problem reports under random tags (§15.26): the shape of |
| 54 | a call's connections, never which meeting or who. |
| 55 | |
| 56 | ## Positioning |
| 57 | |
| 58 | 1. **The claim is made per process, not once** (§0.1): Calls' own |
| 59 | process never touches a media packet; the platform TURN relay |
| 60 | forwards encrypted DTLS-SRTP it cannot read; a future platform SFU |
| 61 | decrypts to forward and gets its own spec revision when it ships |
| 62 | (§5.7). |
| 63 | 2. **No relay of its own, by ruling** (suite ruling 6, §5.2): present |
| 64 | `CARLOS_RELAY` mints ICE credentials per call; absent means host |
| 65 | candidates only, stated before the join button (§5.5). |
| 66 | 3. **Two authorities, not one, for a room.** *Standing* (§2.3, computed |
| 67 | per request, never stored) decides who may **enter**; a |
| 68 | *participation* (§2.4, hub state) decides who may **continue** — an |
| 69 | admitted guest keeps talking past its grant's expiry while a member |
| 70 | who loses standing is dropped at once (ruling 14). |
| 71 | |
| 72 | ## Operating Context |
| 73 | |
| 74 | - **One Go binary per instance**, `CGO_ENABLED=0`, deployed on CARLOS as |
| 75 | a **unit route** (§7.3). Native Apple bindings under `native/` are the |
| 76 | one place cgo is used, isolated in `native/go.mod` (`AGENTS.md`). |
| 77 | - **Routes today** (`internal/calls/app.go`): `GET /`, `POST /meetings`; |
| 78 | `GET /j/{token}` (outside the session group); the holding screen and |
| 79 | room; signalling (`stream`, `signal`, `ice`); `POST /m/{sqid}/share` |
| 80 | (2026-09-12); five destructive confirmation pairs (remove, takeover, |
| 81 | replace invitation, end meeting, revoke agent); Settings and agent |
| 82 | tokens; sign-in including optional Home routes; native handoff |
| 83 | endpoints; a bearer-only `/api/v1/*` group (`api.go`) outside CSRF and |
| 84 | the session group. `GET /healthz`/`GET /api/version` answer from |
| 85 | rastrillo itself, outside app middleware. |
| 86 | - **Scriptless, with browser media participation as the exception, and |
| 87 | two script-only parts inside a live room.** Reaching a camera and |
| 88 | another browser needs script; so, once a call exists, do the chat |
| 89 | panel (`partials/chat.html` renders it `hidden` until script un-hides |
| 90 | it) and the admission queue's Admit controls (`room.js`). Every other |
| 91 | screen — the list, creating a meeting, a link shown once, every |
| 92 | confirm page, sign-in, `/welcome`, the agent token page, the Share |
| 93 | page and the Home account-link screens (§7.1.1), and the |
| 94 | server-rendered parts of the holding screen and room — works with |
| 95 | scripts off (§9; `AGENTS.md`'s UX line carries the media exception; |
| 96 | full list below). |
| 97 | - **Nothing stored beyond a meeting's bones.** Rooms, peers, admission, |
| 98 | chat and presence live in an in-process hub and die with the process |
| 99 | (§2.2); a restart drops every live call (§4.5). Stored: `persons`, |
| 100 | `meetings` (sealed title, `internal/calls/seal/`), `meeting_links` |
| 101 | (hash-indexed plus a sealed copy for sharing since 2026-09-12), |
| 102 | `pending_joins` (a keyed address fingerprint, never plaintext), |
| 103 | `guest_grants`, `agents` (a member's minted principal; the token |
| 104 | itself is never stored, only its SHA-256) (models.go; §2.1). |
| 105 | - **Quality reports and "Report a problem" go to the log, by Paul's ruling |
| 106 | of 2026-10-03** (spec §15.26), which knowingly bends "stores nothing |
| 107 | about calls". Collected: every 30 s in a call and at leave, each client's |
| 108 | connection numbers per remote peer (bitrate, loss, RTT, jitter, audio |
| 109 | concealment and jitter buffer, video freezes and frames, frame size and |
| 110 | fps, the encoder's limitation, screen recovery steps), the candidate types |
| 111 | and transport of the path (host/srflx/relay, udp/tcp/tls), the notices |
| 112 | shown (Reconnecting, can't hear you yet, no sound, sound blocked, screen |
| 113 | stalled), app/browser/OS and their versions, and the *kind* of microphone |
| 114 | and speaker (bluetooth, usb, built-in…); all under a random call tag and a |
| 115 | random peer tag. When someone presses Send in "Report a problem": their |
| 116 | note, and their call diagnostics, which include device names and, on the |
| 117 | web, the user agent, with people as peer tags and the meeting scrubbed. |
| 118 | Never: names, emails, meeting titles or sqids, peer ids, IP addresses, |
| 119 | device names (in quality reports), chat, audio or video. Nothing goes to |
| 120 | the database; the log is the only store, kept for the platform's log |
| 121 | retention — seven days, or 50 MiB of compressed log per app per box, |
| 122 | whichever is first. `CALLS_QUALITY_REPORTS=off` stops quality reports; a |
| 123 | person can stop their own in Settings (apps) or the Devices panel (web). |
| 124 | |
| 125 | ## Capabilities and Constraints |
| 126 | |
| 127 | **Shipped:** meeting creation and listing; a link shown once, replaced |
| 128 | or shared via `POST /m/{sqid}/share`; sign-in with the pending-join |
| 129 | exception admitting an outsider only while holding a live link (ruling |
| 130 | 12, §3.2); optional Home account-linking (§7.1.1); the holding screen |
| 131 | and a P2P mesh room capped at four with perfect negotiation, resume, and |
| 132 | a replay ring (§4); relay-minted ICE credentials renewed before deadline |
| 133 | (§5.2, §5.3); ephemeral chat and screen share (§6, §5.4); a bearer-token |
| 134 | drive surface for agents re-checking operator membership on every |
| 135 | request (§8, §15.17); native Mac and iPhone/iPad apps sharing the Go |
| 136 | protocol core (`native/README.md`). |
| 137 | |
| 138 | **Deliberately not built yet** (§11, as amended): see "What is |
| 139 | deliberately not here" below for the full, cited list. |
| 140 | |
| 141 | **Binding technical constraints:** |
| 142 | |
| 143 | - **Media never touches Calls' own process** (§0.1); no handler accepts |
| 144 | a media packet. **`scope.Owned`, no bound request body**: every |
| 145 | user-owned query goes through it, reads and writes alike; a row that |
| 146 | isn't yours 404s, never 403s (`AGENTS.md`). |
| 147 | - **Standing is computed, never cached** — every request, every |
| 148 | 15-second hub tick (§2.3, §2.4). A shipped migration is never edited. |
| 149 | The agent API's rate limit keys on the resolved agent, not the client |
| 150 | address, deliberately (§15.17; see below). |
| 151 | - **`CGO_ENABLED=0 make ci` is the server's cgo-free gate.** Two |
| 152 | things run under `CGO_ENABLED=1` and never ship: `make race`, for |
| 153 | any change to `internal/calls/hub` or the stream, and `make |
| 154 | native-test`'s own `callscore` race sub-step. The native Mac and |
| 155 | iPhone/iPad apps are the one place real cgo does ship — `CallsBridge` |
| 156 | is a binary target `CallsKit` links (`native/apple/Package.swift`, |
| 157 | `native/scripts/build-mac.sh`) — while every server release stays |
| 158 | `CGO_ENABLED=0` (`AGENTS.md`). **`make browser`** needs UDP, which |
| 159 | the Claude Code bash sandbox blocks (`AGENTS.md`). |
| 160 | |
| 161 | ## Brand Commitments |
| 162 | |
| 163 | - **Name:** Calls, `amadan.net/oficina/calls`, served at |
| 164 | `calls.oficina.works`. Renamed from Calls on 2026-09-16. Ruling 2 chose |
| 165 | `oficina/meet` over `oficina/conferencia` (preamble); that ruling stands |
| 166 | as the record of what was decided then, and this row is the current |
| 167 | name. Built beside Docs, Calendar, Slides and the suite doctrine, |
| 168 | consuming a decision made there rather than restating it |
| 169 | (preamble, §0.2). |
| 170 | - **The theme is Estilo's**, adopted at whatever Estilo holds when each |
| 171 | slice lands. As recorded on 2026-09-14 (`git ls-remote --tags` |
| 172 | against `oficina/estilo`, empty at that check), no tag exists; |
| 173 | consumed at a pseudo-version, already adopted by the Home integration |
| 174 | on main. **[inferred — confirm]** the exact commit at any later date. |
| 175 | - **The chat panel is Calls' contribution to Estilo** — "one |
| 176 | self-contained template partial and one script file so a second app |
| 177 | can adopt it," the suite's first chat, built to be taken (§6). |
| 178 | - **Voice: [inferred — confirm].** Copy review was waived for this |
| 179 | documentation round (steering on `oficina/suite` discussion 5), so |
| 180 | shipped strings were not checked against a review record. |
| 181 | |
| 182 | ## Evidence on Hand |
| 183 | |
| 184 | - **Spec:** revision 9 plus the 2026-09-10 and 2026-09-12 amendments, |
| 185 | 2100+ lines, six rounds of Codex review (§14). **Plans:** |
| 186 | `docs/superpowers/plans/`, one programme (slices 2–6) plus a plan per |
| 187 | slice. **Slice records:** `docs/superpowers/reviews/`, one per slice |
| 188 | 2–5 (room, relay, chat/screen-share, drive surface), each an index |
| 189 | into the spec's own §15 rulings for that slice. |
| 190 | - **Native:** `native/README.md`, `native/DESIGN.md`, |
| 191 | `native/VERIFICATION.md` — build, gates, stated acceptance limits (no |
| 192 | real-device pass claimed for camera permissions, audio-device |
| 193 | switching, sleep/wake, or Home passkeys). |
| 194 | - **What does not exist and must not be invented:** customers, |
| 195 | testimonials, benchmarks, pricing, usage numbers, launch date, press, |
| 196 | or a production promotion from this slice — edge |
| 197 | (`calls.oficina.works`) is promoted by hand by Paul, separately from |
| 198 | documentation (slice 6 plan, "What slice 6 was"). The acknowledgements |
| 199 | page and the Estilo theme pin are the other two slice-6 items with a |
| 200 | disposition rather than a delivery; spec §15.18 is the record for all |
| 201 | three. |
| 202 | |
| 203 | ## Product Principles |
| 204 | |
| 205 | 1. **State the degradation rather than let it be discovered.** The |
| 206 | no-relay floor says calls usually connect only on the same network |
| 207 | (§5.5); §0.3 states what the privacy claim excludes (signalling in |
| 208 | flight, a replay ring of up to 32 messages for 30 seconds, a link as |
| 209 | a bearer); the mail-budget residue is written down, not claimed away |
| 210 | (§3.2). |
| 211 | 2. **A verification that cannot distinguish "no" from "I can't see it" |
| 212 | is not a verification.** The §0.1 claim is enforced by grepping raw |
| 213 | SQLite bytes and the WAL and capturing the logger during a real call; |
| 214 | `make browser` asserts a received track by `ontrack` and frame count, |
| 215 | never a label (§13). |
| 216 | 3. **Two authorities are named because one would be wrong in some |
| 217 | direction** (§2.3–§2.4, ruling 14): a single authority either drops |
| 218 | an admitted guest at the grant's two-hour mark or lets an expired |
| 219 | grant re-enter. |
| 220 | 4. **A contract is written for the next client, not the current one.** |
| 221 | Signalling, `/ice` and the meeting API are contracts a native client |
| 222 | could speak (ruling 15, §8.4); the native apps are that ruling paying |
| 223 | off. |
| 224 | 5. **Actors are `person:<id>` or `agent:<id>`, never fabricated** |
| 225 | (§7.2, doctrine ruling 9). |
| 226 | |
| 227 | ## Accessibility & Inclusion |
| 228 | |
| 229 | `AGENTS.md`'s vendored UI conventions apply: labelled fields with one |
| 230 | error envelope, destructive actions on their own confirm-page URL and |
| 231 | never a modal (`ui/confirm-form` — §3.1, §3.5, §4.2), state shown with a |
| 232 | text label and not colour alone, and empty states that say what the |
| 233 | screen is for. |
| 234 | |
| 235 | **The scriptless floor is the accessibility constraint this app adds |
| 236 | beyond that shared list.** Reaching a camera and another browser needs |
| 237 | script, and so, once a call exists, do the room's chat panel and its |
| 238 | admission-queue Admit controls; nothing else does. With scripts off |
| 239 | the holding screen's join button becomes one paragraph saying a call |
| 240 | needs JavaScript and everything else on the page works without it |
| 241 | (§3.3); the room does the same (§3.4). |
| 242 | |
| 243 | No target locale count or WCAG conformance level is stated in the Calls |
| 244 | spec or `AGENTS.md`. **[inferred — confirm]**: Docs states "WCAG 2.2 AA, |
| 245 | no exemptions" for itself; nothing here says the same target binds Calls, |
| 246 | and this file does not assume it does. |
| 247 | |
| 248 | ## What is deliberately not here |
| 249 | |
| 250 | - **Scriptless media, chat and admission** — the exceptions to the |
| 251 | scriptless floor: reaching a camera and another browser needs script, |
| 252 | and so, once a call exists, do the room's chat panel and its |
| 253 | admission-queue Admit controls; every other screen, including the |
| 254 | server-rendered parts of the holding screen and the room, works with |
| 255 | scripts off (spec §9; `AGENTS.md`'s UX-conventions line carries the |
| 256 | media exception in the same words). |
| 257 | - **No STUN or TURN server of Calls' own.** ICE servers come from the |
| 258 | platform relay, present or not at all — never a server Calls names |
| 259 | itself (suite ruling 6, §5.2, §5.5). |
| 260 | - **No SFU, and the room is capped at four admitted peers** — not a |
| 261 | claimed product limit, the mesh's own (ruling 5), lifted only when a |
| 262 | platform SFU that decrypts to forward ships, in its own spec revision, |
| 263 | not a quiet cap change (§5.7, §0.3, §11 "rooms above four"). |
| 264 | - **No client-address limiter on the drive surface.** The agent API's |
| 265 | per-agent rate limit (one request a second sustained, burst 60) keys |
| 266 | on the resolved agent, not the client address: behind the platform's |
| 267 | relay socket every request can share one address, and an |
| 268 | address-keyed limiter would let one flood take out every real agent |
| 269 | at once (§15.17). |
| 270 | - **No passkeys ruling of its own.** Unlike Docs §8.1, the spec states |
| 271 | no instance-local-passkey decision. **[inferred — confirm]**. |
| 272 | - **Chat is never stored.** A `chat` message fans out to admitted peers |
| 273 | and renders client-side; not replayed to a peer who joins or resumes |
| 274 | later, gone with the tab (ruling 8, §6). |
| 275 | - **No recurring meetings, Calendar integration, recording, attendance |
| 276 | records, reactions, dial-in, captions, multi-host signalling, |
| 277 | push-to-talk, or virtual backgrounds, and no way to deny a knock** — a |
| 278 | guest who isn't admitted simply waits or leaves (§11). |
| 279 | - **No native client sign-in method of its own** — a native client |
| 280 | authenticates through the same Home/local sign-in the browser uses, |
| 281 | handed off through a five-minute, one-use authorization; no separate |
| 282 | bearer or token scheme for native apps (§8.4's 2026-09-14 amendment, |
| 283 | `native/README.md`). |
| 284 | **No attendance claim, ever**: §0.3 says Calls does not record who was |
| 285 | in a call or for how long; an earlier revision implied one and the |
| 286 | spec dropped the implication rather than building the model. The |
| 287 | quality reports of §15.26 show that a call under a random tag happened |
| 288 | between two times with so many clients in it, for seven days; never |
| 289 | which meeting or who. |
| 290 | |
| 291 | --- |
| 292 | |
| 293 | *See also:* the design spec, `README.md`, `AGENTS.md`, and the slice |
| 294 | records under `docs/superpowers/reviews/`. |
| 295 | |
| 296 | |