openvibers/openvibe.fun AGENTS.md

AGENTS.md

main 10.8 KB · 152 lines · markdown

1# AGENTS.md — OpenVibe.Fun
2
3## Project Overview
4
5OpenVibe.Fun is the Fun room: IRL quest boards attached to a public OpenVibe.Live channel, a deterministic
6"what's fun near me" guide from OpenStreetMap (Ticketmaster only when a key is configured), and group plans with one
7vote each and a winner the viewer can add to their own OpenVibe.Today.
8
9- **Domain:** `openvibe.fun` · **Port:** 5140 · **Service id:** `fun` · **Env prefix:** `FUN`
10- **Runtime:** Node 22, Express 4, PostgreSQL 18 through `openvibe-sdk/db` (embedded PGlite in development/tests),
11 Valkey 9 for shared limit counters.
12- **Read first:** [README.md](README.md) (ownership, API and safety rules) and [STATUS.json](STATUS.json).
13
14**Network context:** identity, roles and the OpenCoins wallet are OpenVibe.Network (OAuth client `fun`, PKCE S256;
15staff means Network role `global_mod` or above). Fun **reads** Live's public channel state and Chat's public history
16through `server/upstreams.js`; it never posts to Live or Chat. Today is written only with the signed-in person's own
17Network token (`server/today.js`). Account export/deletion arrives from OpenVibe.Events at `/internal/events`. Search
18documents wait in `fun_outbox` and are delivered by `openvibe-publishing/search-relay` (`server/search.js`). Fun owns
19no other service's state.
20
21## Commands
22
23```bash
24npm ci # install (the repo pins contracts/sdk/shared by tag)
25npm run dev # http://localhost:5140, NODE_ENV=development
26npm start # node server/index.js
27npm test # every test/*.test.js in its own process, temp PGlite, mocked Network/upstreams
28npm run test:pg # the same through PostgreSQL + PgBouncer (openvibe-sdk scripts/test-services.sh up)
29npm test -- quests # only test files whose name contains the word
30npm test -- --strict # a skipped test fails the run
31node --check <file.js> # syntax check (no linter/formatter in this repo)
32```
33
34`test/run.js` uses `openvibe-shared/test-runner`; tests never reach the internet — upstream fixtures are local. There
35is **no build step**: pages are server-rendered and assets are served from `public/` with content-hash cache headers.
36
37## Architecture at a Glance
38
39- **Entry:** [server/index.js](server/index.js) — starts the listener, telemetry tick/retention, the read-only chat
40 sync, the OpenCoin retry timer and the Events subscriptions; `FUN_DRILL` keeps boot-time work off. Graceful stop is
41 `createLifecycle()` in the same file.
42- **App factory:** [server/app.js](server/app.js) — helmet/CSP, SSO middleware, `/auth`, `/internal/events`,
43 `/shared` + static, `/api/v1`, the agent layer (Markdown twins and `/.well-known/agent.json`), pages, 404 and error
44 handler. Tests build this app with `createApp({ store, fetchImpl, ... })`.
45- **Config:** [server/config.js](server/config.js) — `load(env)` is pure; production reads `/etc/openvibe/fun.env`.
46 Only variable **names** appear in code and docs.
47- **Database:** [server/db.js](server/db.js) — migrations in [migrations/](migrations/) run at boot with
48 `DATABASE_DIRECT_URL` (owner); `DATABASE_URL` serves through PgBouncer. No table is created at runtime.
49- **Store/domain:** [server/store.js](server/store.js), [server/quests/](server/quests/) (`ideas.js`, `safety.js`),
50 [server/guide/](server/guide/) (`index.js`, `places.js`, `upstream.js`, `events.js`),
51 [server/chat-sync.js](server/chat-sync.js), [server/coins.js](server/coins.js),
52 [server/sharing.js](server/sharing.js), [server/today.js](server/today.js).
53- **HTTP:** [server/http/router.js](server/http/router.js) is the async router; [server/http/api.js](server/http/api.js)
54 mounts `/api/v1`, [server/http/pages.js](server/http/pages.js) the pages, [server/http/principal.js](server/http/principal.js)
55 decides person vs service token and capabilities. Views are under [server/views/](server/views/).
56- **Auth:** [server/auth/sso.js](server/auth/sso.js) + [server/auth/keys.js](server/auth/keys.js) — Network OAuth2
57 PKCE and offline JWKS verification; staff from the Network role claim ([server/identity/staff.js](server/identity/staff.js)).
58 Account export/deletion: [server/identity/account-data.js](server/identity/account-data.js).
59- **Frontend:** server-rendered HTML through [server/render/layout.js](server/render/layout.js) and
60 [server/render/html.js](server/render/html.js), which pull the OpenVibe Frame/kit from `openvibe-shared`. Assets:
61 [public/css/fun.css](public/css/fun.css), [public/js/fun.js](public/js/fun.js). Pages work without JavaScript; JS
62 only adds convenience. Markdown twins come from [server/agent-pages.js](server/agent-pages.js) and
63 [server/agent-descriptor.js](server/agent-descriptor.js).
64- **Deploy:** [deploy/systemd/openvibe-fun.service](deploy/systemd/openvibe-fun.service) (127.0.0.1:5140,
65 `/etc/openvibe/fun.env`) and [deploy/nginx/openvibe.fun.conf](deploy/nginx/openvibe.fun.conf) (TLS, www → apex,
66 `/internal` and `/metrics` closed). `sudo ovhost deploy fun` on the host; database `ov_fun`; /opt/openvibe.fun.
67
68## Ownership and Boundaries
69
70Fun is the authority for the tables its migrations create: quest boards/quests/votes/checks/badges/rewards, plans and
71their options/votes, `fun_reports`, `fun_chat_seen`/`fun_chat_cursors`, `fun_geo_cache`, `fun_outbox`,
72`account_data_events`, sharing tables and `telemetry_samples`. It is **not** the authority for:
73
74- identity, accounts, roles, sessions or the wallet — OpenVibe.Network;
75- Live channels, Chat rooms/history, Today calendars — read-only upstreams or the person's own token;
76- OSM/Ticketmaster data — cached, attributed public sources, never paid providers;
77- event delivery (OpenVibe.Events), the host/database/Valkey (OpenVibe.Host), or the search index (OpenVibe.Search).
78
79Public quests/plans are anonymized on account deletion; shared plans stay for their other members. Never silently keep
80a deleted person's name on a row.
81
82## Auth and Capabilities
83
84- A person is the `fun_at` session cookie or `Authorization: Bearer <Network user token>`, verified offline. A
85 cookie-authenticated write must come from `https://openvibe.fun` (403 `fun.cross_site`).
86- Per-actor limits: `FUN_LIMITS_MINUTE`, `FUN_LIMITS_HOUR`; shared counters use Valkey.
87- **Inbound v1: no service capability guards a route.** Do not invent one.
88- **Outbound:** `events.subscription.manage` at boot; `network.account.export.contribute` and
89 `network.account.deletion.confirm` through `openvibe-sdk/account-data`; `search.document.write` for the Search
90 relay; `network.coins.credit` + `identity.subject.resolve` only when `FUN_COINS=on`.
91- The OpenCoin credit is an outbox (`fun_quest_rewards`) with retries; the local badge is always paid.
92
93## API (JSON under `/api/v1`)
94
95`GET /guide`, `GET /quests`, `GET /quests/ideas`, `GET /quests/boards/:id`, `GET /quests/:slug`,
96`POST /quests/boards`, `POST /quests/boards/:id/suggest|idea`, `POST /quests/:slug/vote|accept|live|complete|verify|hide|report`;
97`GET /plans`, `GET /plans/:slug`, `POST /plans`, `POST /plans/:slug/options|vote|decide|today|hide|report`;
98`GET /reports` (staff). Discovery/health: `/api/v1/ping`, `/openapi.json`, `/llms.txt`, `/sitemap.xml`, `/robots.txt`,
99`/api/health`, `/api/ready`, `/release.json`, `/metrics` (loopback only). Errors are RFC 9457
100`application/problem+json` with a stable `code` (`fun.invalid`, `fun.quest.*`, `fun.plan.*`, `fun.rate_limited`, …).
101
102## Conventions
103
104- CommonJS (`require`/`module.exports`), 4-space indent, single quotes, semicolons; no linter/formatter.
105- Every route handler goes through [server/http/router.js](server/http/router.js) so async rejections reach the error
106 handler. Never log request bodies; never return raw rows — page/view code renders explicit fields.
107- IDs are `prefix_` + ULID via the store; timestamps are ISO strings from the injected clock.
108- New schema = a new `migrations/NNNN_name.sql`; never edit a migration that has run. Keep table prefixes `fun_`.
109- CSP: add every external origin to the `helmet` directives in [server/app.js](server/app.js); images/frames default
110 to self plus the named OpenVibe hosts. Do not widen `frameAncestors` or `objectSrc`.
111- Phone first: pages must work at 390 px with no horizontal overflow; the navbar/footer come from `openvibe-shared`.
112- "Calm reduces, only Off removes": a user preference may de-emphasize content, never delete it; an Off switch is the
113 only removal.
114- Public pages must have real content in the first paint — no placeholder text or fake counts (`home-real-content`,
115 `home-real-first` tests); link-outs must be real (`remind-links`).
116- Do not add model calls or a new AI capability here: v1 is deterministic.
117
118## Key Pitfalls
119
120- **No build step.** Editing `public/` is live on deploy; do not add `?v=` by hand — the layout's asset version does
121 that.
122- **Upstreams are public and optional.** Ticketmaster is off without `TICKETMASTER_API_KEY`; OSM must be paced,
123 cached, attributed (ODbL) and has a stale fallback. Never fetch an arbitrary user URL.
124- **Read-only chat.** `server/chat-sync.js` ingests `!quest`/`!plan` commands idempotently per chat message id; it
125 must never write to Chat or Live.
126- **Drill mode.** Anything started at boot (timers, loops, outbound calls) must be off under `FUN_DRILL`.
127- **Agent layer.** `.md` paths and `?format=markdown` are handled before page routes so `/q/x.md` is a twin, not a
128 quest slug. New top-level page routes must also be visible to [server/http/pages.js](server/http/pages.js) and the
129 sitemap/llms artifacts.
130- **Safety filter.** `server/quests/safety.js` is explainable and must keep rejecting dangerous, illegal,
131 trespassing and harassing suggestions before storage.
132- **Account deletion** runs through `/internal/events` only (loopback, signature-verified); a redelivered event must
133 change nothing.
134- **No Font Awesome** in new UI unless the shared kit/glyph test for the page allows it; use the shared glyph set.
135
136## Testing
137
138`npm test` boots every `test/*.test.js` in its own process on a temporary PGlite database with a mock Network and
139fixture upstreams (`test/run.js`). The suite covers migration, quest safety/state machine, chat idempotency, plan
140voting/tie/Today, Search documents, SSO, cache/pages, coins opt-in off/on, sharing, account export/deletion, agent
141layer, real-content-first and broken-link regressions. `npm run test:pg` runs the same suite through PostgreSQL +
142PgBouncer. Run the narrowest file first (`npm test -- quests`), then the suite. Always `node --check` changed files.
143
144## Documentation
145
146- [README.md](README.md) — purpose, owns/does-not-own, capabilities, API, quest safety, guide, plans, pages, Search,
147 run/test, deploy.
148- [STATUS.json](STATUS.json) — current stage, deployed flag, features and not-yet list.
149- [.env.example](.env.example) — every variable (names only; never commit values).
150- [CHANGELOG.md](CHANGELOG.md) — released changes.
151- [SECURITY.md](SECURITY.md) — vulnerability reporting.
152