# Product

<!-- impeccable:product-schema 1 -->

> **How this file was written.** Written unattended, on the same
> instruction that produced `oficina/docs`' `PRODUCT.md`: every fact
> below comes from repository evidence, not an interview —
> `docs/superpowers/specs/2026-09-07-meet-design.md` (revision 9, plus
> the 2026-09-10 and 2026-09-12 amendments), `AGENTS.md`, the slice
> records under `docs/superpowers/reviews/`, `native/README.md`, and the
> shipped code in `internal/calls/` on branch `slice/6-deploy`, main
> `9225ec2`. Facts are cited to a spec section or a file. A reading
> rather than a citation is marked **[inferred — confirm]** and should
> be corrected, not trusted. A bare "ruling N" is the Calls spec's §1; a
> doctrine ruling is always written "suite ruling N".

## Platform

Web, plus native Mac and iPhone/iPad clients over the same server
contracts (spec §8.4's 2026-09-14 amendment, `native/README.md`).

## Users

**Primary: a member of one team**, making and joining that team's video
calls. The instance boundary is the team — one instance per team
(preamble; suite ruling 1, 5, §0.2), the "lone shape" until a suite home
exists (§7.1). **Secondary: an outside guest**, admitted with a real
identity, never as an anonymous URL holder: every participant signs in,
and a meeting link gives an outsider a **guest grant**, never a way in
without a name (ruling 1; suite ruling 15); a guest waits until an
admitted member lets them in (ruling 9, §3.3).

Access is a magic link gated by `CALLS_ALLOWED_ADDRESSES`
(`internal/calls/config.go`), with optional Home sign-in when an operator
sets all four `CALLS_HOME_*` settings (§7.1.1) — Home only *links* an
existing member after independent local sign-in; it never creates a
person or grants membership. Unlike Docs' §8.1, the spec states no
passkey ruling of its own. **[inferred — confirm]** the team is assumed
small enough that an address allowlist is appropriate; the spec states
no target size.

## Product Purpose

Calls is **video calling for one Oficina team**: a member makes a
meeting, sends one link, and the people who follow it set up camera and
microphone, wait to be let in if not on the team, and talk (preamble).
"The instance carries the pages, the sign-in, the signalling and the
in-call chat, and never a frame of media" (§0.1) — falsifiable: publish
the SQLite file, its WAL, and every log line, and the worst anyone
learns is the shape of the team's meetings and who minted what, never a
title, chat text, or signalling payload, enforced by a WAL-grep test and
a captured-logger test, not merely asserted (§0.1). Since 2026-10-03 the log also
holds quality and problem reports under random tags (§15.26): the shape of
a call's connections, never which meeting or who.

## Positioning

1. **The claim is made per process, not once** (§0.1): Calls' own
   process never touches a media packet; the platform TURN relay
   forwards encrypted DTLS-SRTP it cannot read; a future platform SFU
   decrypts to forward and gets its own spec revision when it ships
   (§5.7).
2. **No relay of its own, by ruling** (suite ruling 6, §5.2): present
   `CARLOS_RELAY` mints ICE credentials per call; absent means host
   candidates only, stated before the join button (§5.5).
3. **Two authorities, not one, for a room.** *Standing* (§2.3, computed
   per request, never stored) decides who may **enter**; a
   *participation* (§2.4, hub state) decides who may **continue** — an
   admitted guest keeps talking past its grant's expiry while a member
   who loses standing is dropped at once (ruling 14).

## Operating Context

- **One Go binary per instance**, `CGO_ENABLED=0`, deployed on CARLOS as
  a **unit route** (§7.3). Native Apple bindings under `native/` are the
  one place cgo is used, isolated in `native/go.mod` (`AGENTS.md`).
- **Routes today** (`internal/calls/app.go`): `GET /`, `POST /meetings`;
  `GET /j/{token}` (outside the session group); the holding screen and
  room; signalling (`stream`, `signal`, `ice`); `POST /m/{sqid}/share`
  (2026-09-12); five destructive confirmation pairs (remove, takeover,
  replace invitation, end meeting, revoke agent); Settings and agent
  tokens; sign-in including optional Home routes; native handoff
  endpoints; a bearer-only `/api/v1/*` group (`api.go`) outside CSRF and
  the session group. `GET /healthz`/`GET /api/version` answer from
  rastrillo itself, outside app middleware.
- **Scriptless, with browser media participation as the exception, and
  two script-only parts inside a live room.** Reaching a camera and
  another browser needs script; so, once a call exists, do the chat
  panel (`partials/chat.html` renders it `hidden` until script un-hides
  it) and the admission queue's Admit controls (`room.js`). Every other
  screen — the list, creating a meeting, a link shown once, every
  confirm page, sign-in, `/welcome`, the agent token page, the Share
  page and the Home account-link screens (§7.1.1), and the
  server-rendered parts of the holding screen and room — works with
  scripts off (§9; `AGENTS.md`'s UX line carries the media exception;
  full list below).
- **Nothing stored beyond a meeting's bones.** Rooms, peers, admission,
  chat and presence live in an in-process hub and die with the process
  (§2.2); a restart drops every live call (§4.5). Stored: `persons`,
  `meetings` (sealed title, `internal/calls/seal/`), `meeting_links`
  (hash-indexed plus a sealed copy for sharing since 2026-09-12),
  `pending_joins` (a keyed address fingerprint, never plaintext),
  `guest_grants`, `agents` (a member's minted principal; the token
  itself is never stored, only its SHA-256) (models.go; §2.1).
- **Quality reports and "Report a problem" go to the log, by Paul's ruling
  of 2026-10-03** (spec §15.26), which knowingly bends "stores nothing
  about calls". Collected: every 30 s in a call and at leave, each client's
  connection numbers per remote peer (bitrate, loss, RTT, jitter, audio
  concealment and jitter buffer, video freezes and frames, frame size and
  fps, the encoder's limitation, screen recovery steps), the candidate types
  and transport of the path (host/srflx/relay, udp/tcp/tls), the notices
  shown (Reconnecting, can't hear you yet, no sound, sound blocked, screen
  stalled), app/browser/OS and their versions, and the *kind* of microphone
  and speaker (bluetooth, usb, built-in…); all under a random call tag and a
  random peer tag. When someone presses Send in "Report a problem": their
  note, and their call diagnostics, which include device names and, on the
  web, the user agent, with people as peer tags and the meeting scrubbed.
  Never: names, emails, meeting titles or sqids, peer ids, IP addresses,
  device names (in quality reports), chat, audio or video. Nothing goes to
  the database; the log is the only store, kept for the platform's log
  retention — seven days, or 50 MiB of compressed log per app per box,
  whichever is first. `CALLS_QUALITY_REPORTS=off` stops quality reports; a
  person can stop their own in Settings (apps) or the Devices panel (web).

## Capabilities and Constraints

**Shipped:** meeting creation and listing; a link shown once, replaced
or shared via `POST /m/{sqid}/share`; sign-in with the pending-join
exception admitting an outsider only while holding a live link (ruling
12, §3.2); optional Home account-linking (§7.1.1); the holding screen
and a P2P mesh room capped at four with perfect negotiation, resume, and
a replay ring (§4); relay-minted ICE credentials renewed before deadline
(§5.2, §5.3); ephemeral chat and screen share (§6, §5.4); a bearer-token
drive surface for agents re-checking operator membership on every
request (§8, §15.17); native Mac and iPhone/iPad apps sharing the Go
protocol core (`native/README.md`).

**Deliberately not built yet** (§11, as amended): see "What is
deliberately not here" below for the full, cited list.

**Binding technical constraints:**

- **Media never touches Calls' own process** (§0.1); no handler accepts
  a media packet. **`scope.Owned`, no bound request body**: every
  user-owned query goes through it, reads and writes alike; a row that
  isn't yours 404s, never 403s (`AGENTS.md`).
- **Standing is computed, never cached** — every request, every
  15-second hub tick (§2.3, §2.4). A shipped migration is never edited.
  The agent API's rate limit keys on the resolved agent, not the client
  address, deliberately (§15.17; see below).
- **`CGO_ENABLED=0 make ci` is the server's cgo-free gate.** Two
  things run under `CGO_ENABLED=1` and never ship: `make race`, for
  any change to `internal/calls/hub` or the stream, and `make
  native-test`'s own `callscore` race sub-step. The native Mac and
  iPhone/iPad apps are the one place real cgo does ship — `CallsBridge`
  is a binary target `CallsKit` links (`native/apple/Package.swift`,
  `native/scripts/build-mac.sh`) — while every server release stays
  `CGO_ENABLED=0` (`AGENTS.md`). **`make browser`** needs UDP, which
  the Claude Code bash sandbox blocks (`AGENTS.md`).

## Brand Commitments

- **Name:** Calls, `amadan.net/oficina/calls`, served at
  `calls.oficina.works`. Renamed from Calls on 2026-09-16. Ruling 2 chose
  `oficina/meet` over `oficina/conferencia` (preamble); that ruling stands
  as the record of what was decided then, and this row is the current
  name. Built beside Docs, Calendar, Slides and the suite doctrine,
  consuming a decision made there rather than restating it
  (preamble, §0.2).
- **The theme is Estilo's**, adopted at whatever Estilo holds when each
  slice lands. As recorded on 2026-09-14 (`git ls-remote --tags`
  against `oficina/estilo`, empty at that check), no tag exists;
  consumed at a pseudo-version, already adopted by the Home integration
  on main. **[inferred — confirm]** the exact commit at any later date.
- **The chat panel is Calls' contribution to Estilo** — "one
  self-contained template partial and one script file so a second app
  can adopt it," the suite's first chat, built to be taken (§6).
- **Voice: [inferred — confirm].** Copy review was waived for this
  documentation round (steering on `oficina/suite` discussion 5), so
  shipped strings were not checked against a review record.

## Evidence on Hand

- **Spec:** revision 9 plus the 2026-09-10 and 2026-09-12 amendments,
  2100+ lines, six rounds of Codex review (§14). **Plans:**
  `docs/superpowers/plans/`, one programme (slices 2–6) plus a plan per
  slice. **Slice records:** `docs/superpowers/reviews/`, one per slice
  2–5 (room, relay, chat/screen-share, drive surface), each an index
  into the spec's own §15 rulings for that slice.
- **Native:** `native/README.md`, `native/DESIGN.md`,
  `native/VERIFICATION.md` — build, gates, stated acceptance limits (no
  real-device pass claimed for camera permissions, audio-device
  switching, sleep/wake, or Home passkeys).
- **What does not exist and must not be invented:** customers,
  testimonials, benchmarks, pricing, usage numbers, launch date, press,
  or a production promotion from this slice — edge
  (`calls.oficina.works`) is promoted by hand by Paul, separately from
  documentation (slice 6 plan, "What slice 6 was"). The acknowledgements
  page and the Estilo theme pin are the other two slice-6 items with a
  disposition rather than a delivery; spec §15.18 is the record for all
  three.

## Product Principles

1. **State the degradation rather than let it be discovered.** The
   no-relay floor says calls usually connect only on the same network
   (§5.5); §0.3 states what the privacy claim excludes (signalling in
   flight, a replay ring of up to 32 messages for 30 seconds, a link as
   a bearer); the mail-budget residue is written down, not claimed away
   (§3.2).
2. **A verification that cannot distinguish "no" from "I can't see it"
   is not a verification.** The §0.1 claim is enforced by grepping raw
   SQLite bytes and the WAL and capturing the logger during a real call;
   `make browser` asserts a received track by `ontrack` and frame count,
   never a label (§13).
3. **Two authorities are named because one would be wrong in some
   direction** (§2.3–§2.4, ruling 14): a single authority either drops
   an admitted guest at the grant's two-hour mark or lets an expired
   grant re-enter.
4. **A contract is written for the next client, not the current one.**
   Signalling, `/ice` and the meeting API are contracts a native client
   could speak (ruling 15, §8.4); the native apps are that ruling paying
   off.
5. **Actors are `person:<id>` or `agent:<id>`, never fabricated**
   (§7.2, doctrine ruling 9).

## Accessibility & Inclusion

`AGENTS.md`'s vendored UI conventions apply: labelled fields with one
error envelope, destructive actions on their own confirm-page URL and
never a modal (`ui/confirm-form` — §3.1, §3.5, §4.2), state shown with a
text label and not colour alone, and empty states that say what the
screen is for.

**The scriptless floor is the accessibility constraint this app adds
beyond that shared list.** Reaching a camera and another browser needs
script, and so, once a call exists, do the room's chat panel and its
admission-queue Admit controls; nothing else does. With scripts off
the holding screen's join button becomes one paragraph saying a call
needs JavaScript and everything else on the page works without it
(§3.3); the room does the same (§3.4).

No target locale count or WCAG conformance level is stated in the Calls
spec or `AGENTS.md`. **[inferred — confirm]**: Docs states "WCAG 2.2 AA,
no exemptions" for itself; nothing here says the same target binds Calls,
and this file does not assume it does.

## What is deliberately not here

- **Scriptless media, chat and admission** — the exceptions to the
  scriptless floor: reaching a camera and another browser needs script,
  and so, once a call exists, do the room's chat panel and its
  admission-queue Admit controls; every other screen, including the
  server-rendered parts of the holding screen and the room, works with
  scripts off (spec §9; `AGENTS.md`'s UX-conventions line carries the
  media exception in the same words).
- **No STUN or TURN server of Calls' own.** ICE servers come from the
  platform relay, present or not at all — never a server Calls names
  itself (suite ruling 6, §5.2, §5.5).
- **No SFU, and the room is capped at four admitted peers** — not a
  claimed product limit, the mesh's own (ruling 5), lifted only when a
  platform SFU that decrypts to forward ships, in its own spec revision,
  not a quiet cap change (§5.7, §0.3, §11 "rooms above four").
- **No client-address limiter on the drive surface.** The agent API's
  per-agent rate limit (one request a second sustained, burst 60) keys
  on the resolved agent, not the client address: behind the platform's
  relay socket every request can share one address, and an
  address-keyed limiter would let one flood take out every real agent
  at once (§15.17).
- **No passkeys ruling of its own.** Unlike Docs §8.1, the spec states
  no instance-local-passkey decision. **[inferred — confirm]**.
- **Chat is never stored.** A `chat` message fans out to admitted peers
  and renders client-side; not replayed to a peer who joins or resumes
  later, gone with the tab (ruling 8, §6).
- **No recurring meetings, Calendar integration, recording, attendance
  records, reactions, dial-in, captions, multi-host signalling,
  push-to-talk, or virtual backgrounds, and no way to deny a knock** — a
  guest who isn't admitted simply waits or leaves (§11).
- **No native client sign-in method of its own** — a native client
  authenticates through the same Home/local sign-in the browser uses,
  handed off through a five-minute, one-use authorization; no separate
  bearer or token scheme for native apps (§8.4's 2026-09-14 amendment,
  `native/README.md`).
  **No attendance claim, ever**: §0.3 says Calls does not record who was
  in a call or for how long; an earlier revision implied one and the
  spec dropped the implication rather than building the model. The
  quality reports of §15.26 show that a call under a random tag happened
  between two times with so many clients in it, for seven days; never
  which meeting or who.

---

*See also:* the design spec, `README.md`, `AGENTS.md`, and the slice
records under `docs/superpowers/reviews/`.

