openvibers/openvibe.community AGENTS.md

AGENTS.md

main 10.0 KB · 165 lines · markdown

1# AGENTS.md — OpenVibe.Community
2
3## Project overview
4
5The OpenVibe community home: **pastes** (the only authority for them since 2026-09-22), the **forum**
6(spaces, threads, posts, votes), the **typed comment threads** every other product embeds, **Pulse**
7(the network's public activity), **submissions**, the Discord relay, and members-only gates through
8VIP. A small Node.js/Express app, CommonJS, no frontend framework, one PostgreSQL database
9(`ov_community`), server-rendered pages with a little progressive JavaScript.
10
11- Origin `https://openvibe.community`, port `4200`, unit `openvibe-community.service`, release root
12 `/opt/openvibe.community`.
13- The forum moved back here from Space on 2026-10-08; Space redirects its old forum URLs here.
14 Space remains the code-hosting / "spaces" product.
15
16## What it owns
17
18- Pastes, their versions, likes, visibility (public/unlisted/private/burn), locked pastes, spam and
19 image review, and the `/api/pastes/*` native API. Pages, raw text, screenshots and downloads read
20 from the paste store — never from Media (Media redirects its old paste URLs here).
21- The typed comment threads every product embeds (`comments` with a type + ref, not a copy of the
22 product's content; the product stores only the thread id).
23- The forum: spaces, groups, threads, posts, votes, categories, pinned starters, the Roadmap space
24 (`docs/roadmap/public.json`), a space's Chat room, members-only spaces/threads (VIP).
25- Pulse, the network's public activity; submissions and their review queue; the Discord relay.
26- `community.*` events, Search documents for public threads, the `community.profile` user module.
27- `ov_community` (host data role, ADR-035), Valkey for per-actor limits.
28
29Not owned: identity/SSO (Network), file bytes (Media), memberships (VIP), chat rooms (Chat), the
30contents of products whose threads it hosts.
31
32## Who calls it, and whom it calls
33
34- Callers: people in the browser; Live (old VOD/clip comment imports), every product embedding a
35 comment thread, Chat/Space linking a forum thread, and services with a Network service token
36 audience `openvibe.community` and the route's capability.
37- Depends on: **Network** (SSO, JWKS, service tokens, `identity.subject.resolve`, the profile module);
38 **Media** (screenshots/attachments; metadata stripped before upload); **VIP** (members-only gates,
39 fails closed); **Chat** (a space's room); **Events** (outbox and the Pulse/account subscriptions);
40 **Vision** (`OV_VISION_URL`, unsafe-image review; unset means no image review); Search for public
41 thread documents; `openvibe-contracts`, `openvibe-sdk` and `openvibe-shared` pins.
42
43## Repository map
44
45- **Entry:** [server/index.js](server/index.js) — database/migrations first, app, Discord relay, Events,
46 Roadmap and starter sync; graceful stop.
47- **App:** [server/app.js](server/app.js) — helmet/CSP, `/auth`, `/api/pastes`, `/api/v1/*` (comments,
48 spaces, posts, pulse, feed, submissions, profiles, relay, resources), `/shared`, `/internal/events`,
49 server-rendered pages.
50- **Config/db:** [server/config.js](server/config.js), [server/db.js](server/db.js).
51- **Pastes:** [server/pastes/](server/pastes/) — `api.js`, `service.js`, `store.js`, `catalog.js`,
52 `review.js`, `content-hash.js`, `spam.js`.
53- **Comments:** [server/comments/](server/comments/) — `service.js`, `api.js`, `store.js`, `routes.js`.
54- **Forum:** [server/forum/](server/forum/) — `service.js`, `api.js`, `routes.js`, `roadmap.js`,
55 `starters.js`.
56- **Pulse/submissions/relay:** [server/pulse/](server/pulse/), [server/submissions/](server/submissions/),
57 [server/relay/](server/relay/).
58- **Identity/auth:** [server/identity/](server/identity/), [server/auth/](server/auth/),
59 [server/vip/](server/vip/).
60- **Render:** [server/render/](server/render/) — pages, comments, forum, feed, pulse, markdown,
61 highlight, JSON-LD.
62- **Browser:** [public/js/community.js](public/js/community.js), `inline-comments.js`, `composer.js`;
63 CSS in [public/css/](public/css/); assets in [public/assets/](public/assets/).
64- **Deploy:** [deploy/systemd/openvibe-community.service](deploy/systemd/openvibe-community.service),
65 [deploy/nginx/openvibe.community.conf](deploy/nginx/openvibe.community.conf).
66
67## Commands
68
69```bash
70npm ci
71npm run dev # NODE_ENV=development node server/index.js
72npm start # node server/index.js, http://127.0.0.1:4200
73npm test # test/run.js, PGlite, mock Live/Network
74npm run test:pg # same suite on PostgreSQL (COMMUNITY_TEST_STORE=pg)
75npm test -- seo proxy # filter by file name; --strict makes a skip fail
76npm run n-1:record # record the N-1 fixtures after a deploy
77ov test # the harness's sharded run of what the change reaches
78```
79
80## Database and migrations
81
82`ov_community` on the host's data role (ADR-035); embedded PGlite in development/tests. Async through
83`openvibe-sdk/db`. Migrations are `migrations/NNNN_*.sql`, applied at boot; the schema is created
84idempotently in every mode (comments, forum, Pulse and the relay live here whichever service owns
85pastes), and three seed spaces are inserted. Never edit a migration that has run; add a new number.
86A rollback is safe because migrations only add tables and columns.
87
88## Auth, identity and comments
89
90- People are **Network subjects** (`usr_…`, `gst_…`), never service-local integers. A browser's owner
91 is the JWT's `subject_id` (older tokens resolved once through Network and cached in `legacy_id_map`);
92 names/avatars come from `subject_projection`, refreshed through the Network's resolve-batch.
93- Identity is **never read from bodies or queries**; a token issued before `token_valid_after` is
94 refused. `openvibe-sdk/sso` owns the cookie flow.
95- Service callers need a service token audience `openvibe.community` and the route's capability
96 (`community.paste.create|write|moderate`, the comment/forum equivalents, …). An `X-OV-Subject` scopes
97 a first-party service call.
98- Comments are typed: a product passes its own ref and gets a thread id; it never stores comment text.
99- Members-only spaces/threads go through VIP and **fail closed**; locked pastes are sealed and never
100 readable from the text path ([docs/locked-pastes.md](docs/locked-pastes.md)).
101
102## Frontend loading model
103
104Every page is server-rendered — crawlers and no-JS readers get the whole thing — with small no-build
105scripts for pagination, copy buttons, the upload path and the inline composer. There is no SPA,
106bundler or transpiler. `openvibe-shared` supplies the Frame, the share sheet and `/shared/*` from the
107pinned node_modules (including `/shared/ov-glyphs.js`). **Icons are OpenVibe glyphs and brand logos,
108never Font Awesome** (see the pitfall). Markdown is rendered through the local renderer, which escapes
109first and only links http(s).
110
111## Deploy
112
113`sudo ovhost deploy community` (git-checkout: fetch, fast-forward `/opt/openvibe.community`, install
114on a lockfile change, restart, wait for `/api/ready`). Unit `openvibe-community.service` on
115`127.0.0.1:4200`, env `/etc/openvibe/community.env` (0600), vhost
116[deploy/nginx/openvibe.community.conf](deploy/nginx/openvibe.community.conf). Database `ov_community`
117on the data role (`add-service.sh community`). Record the N-1 fixtures after a deploy
118(`npm run n-1:record`). Rollback `sudo ovhost rollback community --to <sha>`.
119
120## Tests
121
122`npm test` runs each `test/*.test.js` in its own process through `openvibe-shared/test-runner`
123(60 s/file) with in-process Live/Network mocks. Notable guards: the no-FA guard (no FA link, font or unknown
124`fa-` name; every icon page loads the glyph drop-in),
125`test/empty-states.test.js`, `test/markdown.test.js` (an unsafe
126scheme never becomes a link), `test/members-only.test.js`, `test/privacy.test.js`, the Pulse source
127rules, `test/nginx-auth-limit.test.js`, `test/nginx-internal-paths.test.js` and the N-1 fixtures.
128`ov test` runs the same.
129
130## Conventions
131
132- CommonJS, 4-space indent, single quotes, semicolons; domain logic in `server/pastes|comments|forum`,
133 rendering in `server/render/`, no SQL in routes.
134- Escape every interpolated value (`esc`), embed JSON with the `<`-escaping helper; never build HTML
135 from unsanitised Markdown.
136- Errors are small JSON/problem bodies with stable codes; never leak a private paste, a member-only
137 body or another person's data through an error or a cache header.
138- Works with JavaScript off; a feature that requires a script must not be the only path.
139
140## Pitfalls
141
142- **No Font Awesome.** The no-FA guard fails on a page/stylesheet that links it, or on
143 an `fa-` name no glyph/brand draws. Use `openvibe-shared/glyphs` and `openvibe-shared/brands`.
144- **Community is the only paste authority.** Do not add a second store or read pastes from Media/Live.
145- **Identity comes from the verified token only** — never a body or a query.
146- **Locked pastes are sealed at rest**, readable only through their gate; a share row must not make an
147 unpublished/private paste readable.
148- **Pulse excludes unlisted, private, burn, NSFW and AI pastes**; do not "fix" a missing item by
149 widening that filter.
150- **Strip image metadata before upload**; a 1×1 PNG's tEXt chunk must not reach Media.
151- **Members-only content fails closed** when VIP is unavailable: teaser, not body.
152- **Phone first is a standing product rule.** There is no automated overflow test here: check a
153 390 px viewport by hand (and the forum/commenter grids) before shipping a layout change.
154- **Empty states are part of the product**; a blank page for no posts/pastes/comments is a bug.
155- **`/internal/*` and `/metrics` stay off the public vhost** (nginx tests).
156- **N-1 fixtures** are recorded after a deploy; keep them current.
157
158## Documentation
159
160- [README.md](README.md) — every surface, the endpoint list, principles, acceptance and deploy.
161- [docs/locked-pastes.md](docs/locked-pastes.md), [docs/sharing.md](docs/sharing.md),
162 [docs/discord-relay.md](docs/discord-relay.md).
163- [docs/forum/](docs/forum/), [docs/roadmap/](docs/roadmap/), [docs/screenshots/](docs/screenshots/).
164- `openvibe-contracts` holds `community.*` shapes/capabilities; VIP and Media own their own.
165