oficina / meet Public

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
Paul Campbell pushed by paul@keymail.dev e684b97698f4b5d0ef2126beed045caaf750715f parent aa9b227
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/`.