openvibers/openvibe.surf Commits

AGENTS.md: the context an agent starts from (#158)

OP OpenVibers committed 1 days ago · 97c113437c · 2 files, +95 −0

Adds `AGENTS.md` (104 lines) as the context an agent starts from, plus `CLAUDE.md` (`@AGENTS.md`).

It covers: Surf's three surfs (Ride on Games, Watch on Live with in-memory parties, Web via Actor);
the deliberate absence of a database/Valkey/Events and what that means for state; the calls and
their failure contracts; origin/session security; the server-rendered progressive-enhancement
frontend; deploy and nginx shape; tests; conventions; and the restart-state, origin-check,
named-failure and no-question-in-URL pitfalls.

Checked: every cited path exists on main; no code, dependency or version changed.

Changes

ddiff --git a/AGENTS.md b/AGENTS.md
nnew file mode 100644
iindex 0000000..74eebdf
---- /dev/null
++++ b/AGENTS.md
@@@ -0,0 +1,94 @@
++# AGENTS.md — OpenVibe.Surf
++
++## Project overview
++
++OpenVibe.Surf (`https://openvibe.surf`) is one room with three surfs:
++
++- **Ride** — CS-style surf maps played inside OpenVibe.Games embeds; the catalogue is proxied from Games.
++- **Watch** — lean-back channel surfing over OpenVibe.Live: live streams, clips and AI moments merged into
++ one feed, plus in-memory watch-together parties over SSE.
++- **Web** — *Surf with Vibe*: a standing brief sent to OpenVibe.Actor so an agent browses for the person
++ and returns a cited report.
++
++- **Entry:** `server/index.js`; app `server/app.js`; port `5110`, service id `surf`; pages in
++ `server/views/index.js`, `server/render/layout.js`; API in `server/http/`; Watch/Ride/Web clients in
++ `server/watch/`, `server/ride/`, `server/web/`; auth in `server/auth/`.
++- **No database, no Valkey, no Events.** The only state is the 30 s Watch feed cache, the 60 s Ride
++ cache, the SDK's JWKS cache and the in-memory watch parties. **A restart ends every watch party.**
++- **Pages:** server-rendered through the OpenVibe Frame (`openvibe-shared/shell`), readable without
++ JavaScript; `public/js/surf.js` adds the TV (native HLS, else `/vendor/hls.light.min.js` only when
++ needed), the Web form and the home timer. Own CSS in `public/css`; no bundler.
++
++## Who calls it, what it calls
++
++Public reads (`/api/v1/watch/feed`, `/api/v1/ride/maps`, `/api/v1/ride/maps/:id`) need no token.
++Signed-in writes use the person's httpOnly `surf_at` Network cookie.
++
++- **OpenVibe.Live** — the Watch feed source (live streams, clips, moments, DVR playlist), with each
++ source given 2 s; a failed source is named in `sources` and makes the page `partial`, never fatal.
++- **OpenVibe.Games** — the Ride map catalogue and the embed origin
++ (`https://openvibe.games/surf/embed?map=<id>&embed=1`); until Games' surf routes exist, Games answers
++ 404 and Surf serves an empty catalogue plus `games: "not_ready"`.
++- **OpenVibe.Actor** — `POST /api/v1/tasks` with the person's own Bearer token, an `Idempotency-Key` and
++ the standing brief; the task belongs to the person's allowance and task list. Actor's 4xx keeps its
++ status and words; no answer is `502 actor.unavailable`.
++- **OpenVibe.Network** — OAuth `surf` client (PKCE S256, state, server-side code exchange), JWKS and
++ sign-in.
++- **OpenVibe.Chat** — only a validated room link the host pasted; Surf has no chat.
++
++## Frontend loading model
++
++Every page is server-rendered with the shared Frame; `sf-` ids/classes are stable for scripts and tests.
++A page for a signed-in person is `private, no-store`; a public page is `public, max-age=60` with
++`Vary: Cookie`. Interactive extras live in `public/js/surf.js` (TV, form, timer) and are progressive
++enhancement only. The SSE route `GET /watch/p/<id>/events` needs `proxy_buffering off`, no gzip and a
++1-hour read timeout in nginx.
++
++## Auth and security
++
++OAuth 2 authorization code + PKCE S256 with the `surf` Network client; the session lives in httpOnly
++cookies and no script can read or log it. The only place the token goes is the `Authorization` header
++of a request to the configured Actor origin. A Web task must come from this site's own `Origin` (a
++cross-site request could spend the visitor's allowance); `/web/go` also checks `Sec-Fetch-Site`; `next`
++after sign-in is this site's own path only and control characters/backslashes are refused.
++
++Watch-together: only the host may command, from this site's own Origin; a member's or another site's
++command is 403. Party ids are unguessable; at most 200 parties and 50 members each.
++
++## Deploy
++
++`sudo ovhost deploy surf`: checkout `/opt/openvibe.surf`, unit `openvibe-surf.service` on
++`127.0.0.1:5110`, env `/etc/openvibe/surf.env`. nginx
++`deploy/nginx/openvibe.surf.conf` (installed with `ov-vhost-install`): `/api` reads only,
++`/api/v1/web/` and `/web/go` the forms zone, `/auth` GET only, `/auth/me` the API budget,
++`/internal` and `/metrics` 404. Readiness answers `degraded` (HTTP 200) when Network, Live or Games is
++unreachable. Rollback with `sudo ovhost rollback surf --to <sha>`.
++
++## Commands and tests
++
++```bash
++npm ci
++npm run dev # http://127.0.0.1:5110
++npm test # every test/*.test.js against in-process stand-ins
++npm test -- watch # only matching files
++npm test -- --strict # a skipped file fails the run
++```
++
++`test/run.js` uses `openvibe-shared/test-runner` (60 s, serial); each file boots Surf against
++in-process Network/Live/Games/Actor stand-ins, never the internet. Tests cover the merged feed and
++partial sources, Ride fallbacks, the Actor task (Idempotency-Key, statuses), watch parties and SSE,
++the pages, sign-in origin rules, discovery/agent twins, contrast and links.
++
++## Conventions and pitfalls
++
++- CommonJS, 4-space indent, single quotes, semicolons; problem+json for API errors; escaped,
++ server-rendered HTML. No database and no schema: do not add a migration without a design change.
++- **Nothing durable may be assumed.** Parties, caches and tokens-in-flight die on restart; a feature
++ that needs to survive one belongs in another service, not in Surf's memory.
++- **Never weaken the origin checks** on `/api/v1/web/tasks`, `/web/go` or a party command; they protect
++ a person's Actor allowance and their party.
++- **A Live/Games/Actor failure is named, not hidden:** `sources`/`partial`, `games: "not_ready"` and
++ `502 actor.unavailable` are the contract; never fabricate feed entries or map data.
++- **The Web question never goes in a URL:** `/web/go` posts it and 303s to the Actor task page.
++- **Progressive enhancement:** every page must work without `public/js/surf.js`; the HLS vendor file is
++ loaded only when native HLS is missing.
ddiff --git a/CLAUDE.md b/CLAUDE.md
nnew file mode 100644
iindex 0000000..43c994c
---- /dev/null
++++ b/CLAUDE.md
@@@ -0,0 +1 @@