openvibers/openvibe.today AGENTS.md

AGENTS.md

main 5.9 KB · 101 lines · markdown

1# AGENTS.md — OpenVibe.Today
2
3## Project overview
4
5OpenVibe.Today (`https://openvibe.today`) is the network's time bus and calendar authority: a person
6puts a stream or an event on the schedule, the network subscribes through RFC 5545 calendars, reminders
7go out through Network notifications, and every change publishes a `today.event.*` event. It also
8serves the daily brief and imports public calendars/holidays.
9
10- **Entry:** `server/index.js`; app `server/app.js`; port `5100`, service id `today`.
11- **API:** `server/http/api.js` (`/api/v1`), `principal.js` (person resolution), `discovery.js`
12 (robots/sitemap/llms); pages in `server/http/pages.js`, `server/views/index.js`,
13 `server/render/layout.js`.
14- **Events:** `server/events/` — `publisher.js`/`store.js` (transactional outbox), `consumer.js`
15 (Live stream started/ended, account export/deletion), `validate.js`.
16- **Calendar:** `server/ics.js` (RFC 5545 output), `server/reminders.js`, `server/agenda.js`,
17 `server/brief.js`; HTTP fetch guard `server/net/`.
18- **Imports:** `server/imports/` (`index.js`, `normalize.js`, `holidays.js`), `scripts/import-now.js`.
19- **Browser:** `public/js/today.js` is the only own script; all pages are server-rendered through
20 `openvibe-shared/shell`, readable without JavaScript, with stable `td-` ids/classes. No bundler.
21
22## Owns, does not own
23
24Owns `today_events` (`tev_…`: owner subject, kind `stream|event`, Live channel, title, description,
25start/end, IANA tz, status, visibility, url, ICS `seq`), `today_idempotency` (subject + key → the first
26event), `today_reminders`, `today_feeds` (one private 32-byte token per person) and `today_outbox`
27(fixed `evt_` id, lease, backoff).
28
29Does not own: identity, follows and notifications (Network — Today verifies tokens/JWKS, reads follows
30with the person's own token, pushes reminders through `/internal/notifications/push` with its own
31service token); the live stream and its lifecycle (Live — Today consumes `live.stream.started|ended`
32and never starts/stops a stream); the Rundown (Live's public API, linked not copied); event delivery
33(Events — Today publishes its outbox and subscribes to live/account topics).
34
35## Who calls it, what it calls
36
37People use the pages/API with the `today_at` session cookie or a `Bearer` Network user token. SDK
38`openvibe-sdk/today` is the client surface (events, own agenda, feed token/rotate, brief, reminders).
39Other sites link into Today, for example `GET /remind?title=&at=&url=&from=` for a reminder. Today
40calls Network (JWKS, follows, identity resolve-batch, notification push), Events (outbox and
41subscriptions), Live (the public Rundown for the brief) and, for imports, the configured public
42calendar/holiday hosts through the guarded fetcher.
43
44## Database and migrations
45
46PostgreSQL through `openvibe-sdk/db` (PgBouncer for the app, owner role for migrations); tests use
47PGlite. `migrations/0001_initial.sql` creates the event, idempotency, reminder, feed and outbox
48tables; later migrations add the rest. Applied at boot; never edit a migration that has run. A change
49writes its `today_outbox` row **in the same transaction** as the event. The inbound event id is
50recorded in `today_processed` in the same transaction as its effect, so a replay does nothing.
51
52## Auth
53
54Network RS256 JWTs verified offline with JWKS; the `today_at` cookie is the page session, a Bearer
55token the API. A write made with the session cookie must come from `https://openvibe.today`, else
56`403 today.cross_site`. A person acts only on their own events, reminders and feed. `/internal/events`
57is loopback-only, verified with the v2 signature over the raw body and the ±300 s window; `/internal`
58and `/metrics` answer 404 from nginx.
59
60## Deploy
61
62`deploy/systemd/openvibe-today.service` (`127.0.0.1:5100`, env `/etc/openvibe/today.env`) and
63`deploy/nginx/openvibe.today.conf` (TLS, www → apex, sign-in/API/ICS limits, `/auth/me` on the API
64zone, `/ics/` GET only, `/internal` and `/metrics` 404). `sudo ovhost deploy today` is the production
65path (git-checkout, install on lockfile change, restart, wait for `/api/ready`); rollback with
66`sudo ovhost rollback today --to <sha>`.
67
68## Commands and tests
69
70```bash
71npm ci
72npm run dev # http://127.0.0.1:5100
73npm test # every test/*.test.js, PGlite + mock Network
74npm test -- events ics # only matching files
75npm run test:pg # the same on PostgreSQL
76npm run imports:run # run the calendar imports once
77```
78
79`test/run.js` uses `openvibe-shared/test-runner` (60 s, serial); temp PGlite and an in-process Network
80mock, never the network. Tests cover validation, ICS folding/escaping and cancellation, the outbox and
81replay, reminders claimed once, the brief, the pages, imports and account export/deletion.
82
83## Conventions and pitfalls
84
85- CommonJS, 4-space indent, single quotes, semicolons; no linter. Errors are
86 `{ code, field, message }`/problem+json; never a raw row.
87- **The ICS output is a compatibility surface:** CRLF, 75-octet folding that never splits a UTF-8
88 sequence, TEXT escaping, UTC DTSTART/DTEND (one hour when no end), `SEQUENCE` from `seq`, and a
89 cancelled event emitted with `STATUS:CANCELLED` for 7 days. A calendar client is not a test you can
90 apologize to.
91- **Reminders are claimed atomically** (`UPDATE … WHERE sent_at IS NULL RETURNING`); a failed push
92 releases the claim for the next tick, and a cancelled event never sends.
93- **The event id is fixed at write time** and the outbox is retried with backoff; Events is
94 idempotent on that id, so a retry/restart must not make a second event.
95- **The private feed is a bearer token:** rotate replaces it and the old URL is 404 at once; export
96 includes the feed row without the token.
97- **Followers-only events are absent from public lists**, not filtered in the page; `follows_unavailable`
98 is the honest answer when Network is down.
99- **Imports never invent a date:** a parsed calendar entry keeps its source and time; a holiday is
100 labelled as a holiday.
101