openvibers/openvibe.community AGENTS.md

AGENTS.md

main 10.0 KB · 165 lines · markdown

AGENTS.md — OpenVibe.Community

Project overview

The OpenVibe community home: pastes (the only authority for them since 2026-09-22), the forum
(spaces, threads, posts, votes), the typed comment threads every other product embeds, Pulse
(the network's public activity), submissions, the Discord relay, and members-only gates through
VIP. A small Node.js/Express app, CommonJS, no frontend framework, one PostgreSQL database
(ov_community), server-rendered pages with a little progressive JavaScript.

  • Origin https://openvibe.community, port 4200, unit openvibe-community.service, release root
    /opt/openvibe.community.
  • The forum moved back here from Space on 2026-10-08; Space redirects its old forum URLs here.
    Space remains the code-hosting / "spaces" product.

What it owns

  • Pastes, their versions, likes, visibility (public/unlisted/private/burn), locked pastes, spam and
    image review, and the /api/pastes/* native API. Pages, raw text, screenshots and downloads read
    from the paste store — never from Media (Media redirects its old paste URLs here).
  • The typed comment threads every product embeds (comments with a type + ref, not a copy of the
    product's content; the product stores only the thread id).
  • The forum: spaces, groups, threads, posts, votes, categories, pinned starters, the Roadmap space
    (docs/roadmap/public.json), a space's Chat room, members-only spaces/threads (VIP).
  • Pulse, the network's public activity; submissions and their review queue; the Discord relay.
  • community.* events, Search documents for public threads, the community.profile user module.
  • ov_community (host data role, ADR-035), Valkey for per-actor limits.

Not owned: identity/SSO (Network), file bytes (Media), memberships (VIP), chat rooms (Chat), the
contents of products whose threads it hosts.

Who calls it, and whom it calls

  • Callers: people in the browser; Live (old VOD/clip comment imports), every product embedding a
    comment thread, Chat/Space linking a forum thread, and services with a Network service token
    audience openvibe.community and the route's capability.
  • Depends on: Network (SSO, JWKS, service tokens, identity.subject.resolve, the profile module);
    Media (screenshots/attachments; metadata stripped before upload); VIP (members-only gates,
    fails closed); Chat (a space's room); Events (outbox and the Pulse/account subscriptions);
    Vision (OV_VISION_URL, unsafe-image review; unset means no image review); Search for public
    thread documents; openvibe-contracts, openvibe-sdk and openvibe-shared pins.

Repository map

  • Entry: [server/index.js](server/index.js) — database/migrations first, app, Discord relay, Events,
    Roadmap and starter sync; graceful stop.
  • App: [server/app.js](server/app.js) — helmet/CSP, /auth, /api/pastes, /api/v1/* (comments,
    spaces, posts, pulse, feed, submissions, profiles, relay, resources), /shared, /internal/events,
    server-rendered pages.
  • Config/db: [server/config.js](server/config.js), [server/db.js](server/db.js).
  • Pastes: [server/pastes/](server/pastes/) — api.js, service.js, store.js, catalog.js,
    review.js, content-hash.js, spam.js.
  • Comments: [server/comments/](server/comments/) — service.js, api.js, store.js, routes.js.
  • Forum: [server/forum/](server/forum/) — service.js, api.js, routes.js, roadmap.js,
    starters.js.
  • Pulse/submissions/relay: [server/pulse/](server/pulse/), [server/submissions/](server/submissions/),
    [server/relay/](server/relay/).
  • Identity/auth: [server/identity/](server/identity/), [server/auth/](server/auth/),
    [server/vip/](server/vip/).
  • Render: [server/render/](server/render/) — pages, comments, forum, feed, pulse, markdown,
    highlight, JSON-LD.
  • Browser: [public/js/community.js](public/js/community.js), inline-comments.js, composer.js;
    CSS in [public/css/](public/css/); assets in [public/assets/](public/assets/).
  • Deploy: [deploy/systemd/openvibe-community.service](deploy/systemd/openvibe-community.service),
    [deploy/nginx/openvibe.community.conf](deploy/nginx/openvibe.community.conf).

Commands

npm ci
npm run dev                   # NODE_ENV=development node server/index.js
npm start                     # node server/index.js, http://127.0.0.1:4200
npm test                      # test/run.js, PGlite, mock Live/Network
npm run test:pg               # same suite on PostgreSQL (COMMUNITY_TEST_STORE=pg)
npm test -- seo proxy         # filter by file name; --strict makes a skip fail
npm run n-1:record            # record the N-1 fixtures after a deploy
ov test                       # the harness's sharded run of what the change reaches

Database and migrations

ov_community on the host's data role (ADR-035); embedded PGlite in development/tests. Async through
openvibe-sdk/db. Migrations are migrations/NNNN_*.sql, applied at boot; the schema is created
idempotently in every mode (comments, forum, Pulse and the relay live here whichever service owns
pastes), and three seed spaces are inserted. Never edit a migration that has run; add a new number.
A rollback is safe because migrations only add tables and columns.

Auth, identity and comments

  • People are Network subjects (usr_…, gst_…), never service-local integers. A browser's owner
    is the JWT's subject_id (older tokens resolved once through Network and cached in legacy_id_map);
    names/avatars come from subject_projection, refreshed through the Network's resolve-batch.
  • Identity is never read from bodies or queries; a token issued before token_valid_after is
    refused. openvibe-sdk/sso owns the cookie flow.
  • Service callers need a service token audience openvibe.community and the route's capability
    (community.paste.create|write|moderate, the comment/forum equivalents, …). An X-OV-Subject scopes
    a first-party service call.
  • Comments are typed: a product passes its own ref and gets a thread id; it never stores comment text.
  • Members-only spaces/threads go through VIP and fail closed; locked pastes are sealed and never
    readable from the text path ([docs/locked-pastes.md](docs/locked-pastes.md)).

Frontend loading model

Every page is server-rendered — crawlers and no-JS readers get the whole thing — with small no-build
scripts for pagination, copy buttons, the upload path and the inline composer. There is no SPA,
bundler or transpiler. openvibe-shared supplies the Frame, the share sheet and /shared/* from the
pinned node_modules (including /shared/ov-glyphs.js). Icons are OpenVibe glyphs and brand logos,
never Font Awesome
(see the pitfall). Markdown is rendered through the local renderer, which escapes
first and only links http(s).

Deploy

sudo ovhost deploy community (git-checkout: fetch, fast-forward /opt/openvibe.community, install
on a lockfile change, restart, wait for /api/ready). Unit openvibe-community.service on
127.0.0.1:4200, env /etc/openvibe/community.env (0600), vhost
[deploy/nginx/openvibe.community.conf](deploy/nginx/openvibe.community.conf). Database ov_community
on the data role (add-service.sh community). Record the N-1 fixtures after a deploy
(npm run n-1:record). Rollback sudo ovhost rollback community --to <sha>.

Tests

npm test runs each test/*.test.js in its own process through openvibe-shared/test-runner
(60 s/file) with in-process Live/Network mocks. Notable guards: the no-FA guard (no FA link, font or unknown
fa- name; every icon page loads the glyph drop-in),
test/empty-states.test.js, test/markdown.test.js (an unsafe
scheme never becomes a link), test/members-only.test.js, test/privacy.test.js, the Pulse source
rules, test/nginx-auth-limit.test.js, test/nginx-internal-paths.test.js and the N-1 fixtures.
ov test runs the same.

Conventions

  • CommonJS, 4-space indent, single quotes, semicolons; domain logic in server/pastes|comments|forum,
    rendering in server/render/, no SQL in routes.
  • Escape every interpolated value (esc), embed JSON with the <-escaping helper; never build HTML
    from unsanitised Markdown.
  • Errors are small JSON/problem bodies with stable codes; never leak a private paste, a member-only
    body or another person's data through an error or a cache header.
  • Works with JavaScript off; a feature that requires a script must not be the only path.

Pitfalls

  • No Font Awesome. The no-FA guard fails on a page/stylesheet that links it, or on
    an fa- name no glyph/brand draws. Use openvibe-shared/glyphs and openvibe-shared/brands.
  • Community is the only paste authority. Do not add a second store or read pastes from Media/Live.
  • Identity comes from the verified token only — never a body or a query.
  • Locked pastes are sealed at rest, readable only through their gate; a share row must not make an
    unpublished/private paste readable.
  • Pulse excludes unlisted, private, burn, NSFW and AI pastes; do not "fix" a missing item by
    widening that filter.
  • Strip image metadata before upload; a 1×1 PNG's tEXt chunk must not reach Media.
  • Members-only content fails closed when VIP is unavailable: teaser, not body.
  • Phone first is a standing product rule. There is no automated overflow test here: check a
    390 px viewport by hand (and the forum/commenter grids) before shipping a layout change.
  • Empty states are part of the product; a blank page for no posts/pastes/comments is a bug.
  • /internal/* and /metrics stay off the public vhost (nginx tests).
  • N-1 fixtures are recorded after a deploy; keep them current.

Documentation

  • [README.md](README.md) — every surface, the endpoint list, principles, acceptance and deploy.
  • [docs/locked-pastes.md](docs/locked-pastes.md), [docs/sharing.md](docs/sharing.md),
    [docs/discord-relay.md](docs/discord-relay.md).
  • [docs/forum/](docs/forum/), [docs/roadmap/](docs/roadmap/), [docs/screenshots/](docs/screenshots/).
  • openvibe-contracts holds community.* shapes/capabilities; VIP and Media own their own.