docs: PRODUCT.md, with the scriptless exception under what is not here
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CzvnuKhLzudvq7jZ7SvVt2
1 file changed,
+210
−0
PRODUCT.md+210 −0
CI passed — run details
diff --git a/PRODUCT.md b/PRODUCT.md| new file mode 100644 |
| index 0000000..d24f6e2 |
| --- /dev/null |
| +++ b/PRODUCT.md |
| @@ -0,0 +1,210 @@ |
| +# 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/meet/` |
| +> 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. |
| + |
| +## Platform |
| + |
| +Web, plus native Mac and iPhone/iPad clients over the same server contracts (spec §8.4, |
| +`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, 15); a guest |
| +waits until an admitted member lets them in (ruling 9, §3.3). |
| + |
| +Access is a magic link gated by `MEET_ALLOWED_ADDRESSES` (`internal/meet/config.go`), with |
| +optional Home sign-in when an operator sets all four `MEET_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 |
| + |
| +Meet 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). |
| + |
| +## Positioning |
| + |
| +1. **The claim is made per process, not once** (§0.1): Meet's 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/meet/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); four confirm/POST pairs; |
| + 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 one named exception: browser media |
| + participation.** Every other screen — the list, creating a meeting, a link shown once, |
| + all four confirm pages, sign-in, `/welcome`, the agent token page, and the server-rendered |
| + parts of the holding screen and room — works with scripts off (§9; `AGENTS.md`'s UX line |
| + carries the same 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/meet/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` (models.go; §2.1). |
| + |
| +## 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 Meet's 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). |
| +- **`make race`** for any change to `internal/meet/hub` or the stream — |
| + the one place `CGO_ENABLED=1` is allowed, for a test binary only. **`make browser`** needs |
| + UDP, which the Claude Code bash sandbox blocks (`AGENTS.md`). |
| + |
| +## Brand Commitments |
| + |
| +- **Name:** Meet, `amadan.net/oficina/meet`, first served at |
| + `meet.oficina.works` (preamble; ruling 2 chose `oficina/meet` over `oficina/conferencia`). 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. No tag exists (`git ls-remote --tags` on `oficina/estilo` was empty on 2026-09-14); |
| + 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 Meet's 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 (`meet.oficina.works`) is promoted by hand by Paul, separately from |
| + documentation (slice 6 plan, "What slice 6 was"). |
| + |
| +## 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. **Every write names its real actor**, `person:<id>` or `agent:<id>` |
| + (§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**, |
| +and it is exact: reaching a camera and another browser needs script; 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 Meet spec or |
| +`AGENTS.md`. **[inferred — confirm]**: Docs states "WCAG 2.2 AA, no exemptions" for itself; |
| +nothing here says the same target binds Meet, and this file does not assume it does. |
| + |
| +## What is deliberately not here |
| + |
| +- **Scriptless media** — the one named exception to the scriptless |
| + floor: reaching a camera and another browser needs script; 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 same exception in the same words). |
| +- **No STUN or TURN server of Meet's own.** ICE servers come from the |
| + platform relay, present or not at all — never a server Meet 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 (60/min) 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, |
| + `native/README.md`). **No attendance claim, ever**: §0.3 says Meet 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. |
| + |
| +--- |
| + |
| +*See also:* the design spec, `README.md`, `AGENTS.md`, and the slice records under |
| +`docs/superpowers/reviews/`. |