openvibers/openvibe.today Commits

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

OP OpenVibers committed 1 days ago · 4eb17e27a4 · 2 files, +101 −0

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

It covers: Today as the time bus and calendar authority; the five `today_*` tables and what stays in
Network/Live/Events; the person and service token model and the cross-site cookie rule; the
transactional outbox and replay handling; the RFC 5545 calendar surface, atomic reminder claims and
the brief; imports; deploy; tests; conventions; and the ICS, reminder, event-id and private-feed
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..9c3a1ae
---- /dev/null
++++ b/AGENTS.md
@@@ -0,0 +1,100 @@
++# AGENTS.md — OpenVibe.Today
++
++## Project overview
++
++OpenVibe.Today (`https://openvibe.today`) is the network's time bus and calendar authority: a person
++puts a stream or an event on the schedule, the network subscribes through RFC 5545 calendars, reminders
++go out through Network notifications, and every change publishes a `today.event.*` event. It also
++serves the daily brief and imports public calendars/holidays.
++
++- **Entry:** `server/index.js`; app `server/app.js`; port `5100`, service id `today`.
++- **API:** `server/http/api.js` (`/api/v1`), `principal.js` (person resolution), `discovery.js`
++ (robots/sitemap/llms); pages in `server/http/pages.js`, `server/views/index.js`,
++ `server/render/layout.js`.
++- **Events:** `server/events/` — `publisher.js`/`store.js` (transactional outbox), `consumer.js`
++ (Live stream started/ended, account export/deletion), `validate.js`.
++- **Calendar:** `server/ics.js` (RFC 5545 output), `server/reminders.js`, `server/agenda.js`,
++ `server/brief.js`; HTTP fetch guard `server/net/`.
++- **Imports:** `server/imports/` (`index.js`, `normalize.js`, `holidays.js`), `scripts/import-now.js`.
++- **Browser:** `public/js/today.js` is the only own script; all pages are server-rendered through
++ `openvibe-shared/shell`, readable without JavaScript, with stable `td-` ids/classes. No bundler.
++
++## Owns, does not own
++
++Owns `today_events` (`tev_…`: owner subject, kind `stream|event`, Live channel, title, description,
++start/end, IANA tz, status, visibility, url, ICS `seq`), `today_idempotency` (subject + key → the first
++event), `today_reminders`, `today_feeds` (one private 32-byte token per person) and `today_outbox`
++(fixed `evt_` id, lease, backoff).
++
++Does not own: identity, follows and notifications (Network — Today verifies tokens/JWKS, reads follows
++with the person's own token, pushes reminders through `/internal/notifications/push` with its own
++service token); the live stream and its lifecycle (Live — Today consumes `live.stream.started|ended`
++and never starts/stops a stream); the Rundown (Live's public API, linked not copied); event delivery
++(Events — Today publishes its outbox and subscribes to live/account topics).
++
++## Who calls it, what it calls
++
++People use the pages/API with the `today_at` session cookie or a `Bearer` Network user token. SDK
++`openvibe-sdk/today` is the client surface (events, own agenda, feed token/rotate, brief, reminders).
++Other sites link into Today, for example `GET /remind?title=&at=&url=&from=` for a reminder. Today
++calls Network (JWKS, follows, identity resolve-batch, notification push), Events (outbox and
++subscriptions), Live (the public Rundown for the brief) and, for imports, the configured public
++calendar/holiday hosts through the guarded fetcher.
++
++## Database and migrations
++
++PostgreSQL through `openvibe-sdk/db` (PgBouncer for the app, owner role for migrations); tests use
++PGlite. `migrations/0001_initial.sql` creates the event, idempotency, reminder, feed and outbox
++tables; later migrations add the rest. Applied at boot; never edit a migration that has run. A change
++writes its `today_outbox` row **in the same transaction** as the event. The inbound event id is
++recorded in `today_processed` in the same transaction as its effect, so a replay does nothing.
++
++## Auth
++
++Network RS256 JWTs verified offline with JWKS; the `today_at` cookie is the page session, a Bearer
++token the API. A write made with the session cookie must come from `https://openvibe.today`, else
++`403 today.cross_site`. A person acts only on their own events, reminders and feed. `/internal/events`
++is loopback-only, verified with the v2 signature over the raw body and the ±300 s window; `/internal`
++and `/metrics` answer 404 from nginx.
++
++## Deploy
++
++`deploy/systemd/openvibe-today.service` (`127.0.0.1:5100`, env `/etc/openvibe/today.env`) and
++`deploy/nginx/openvibe.today.conf` (TLS, www → apex, sign-in/API/ICS limits, `/auth/me` on the API
++zone, `/ics/` GET only, `/internal` and `/metrics` 404). `sudo ovhost deploy today` is the production
++path (git-checkout, install on lockfile change, restart, wait for `/api/ready`); rollback with
++`sudo ovhost rollback today --to <sha>`.
++
++## Commands and tests
++
++```bash
++npm ci
++npm run dev # http://127.0.0.1:5100
++npm test # every test/*.test.js, PGlite + mock Network
++npm test -- events ics # only matching files
++npm run test:pg # the same on PostgreSQL
++npm run imports:run # run the calendar imports once
++```
++
++`test/run.js` uses `openvibe-shared/test-runner` (60 s, serial); temp PGlite and an in-process Network
++mock, never the network. Tests cover validation, ICS folding/escaping and cancellation, the outbox and
++replay, reminders claimed once, the brief, the pages, imports and account export/deletion.
++
++## Conventions and pitfalls
++
++- CommonJS, 4-space indent, single quotes, semicolons; no linter. Errors are
++ `{ code, field, message }`/problem+json; never a raw row.
++- **The ICS output is a compatibility surface:** CRLF, 75-octet folding that never splits a UTF-8
++ sequence, TEXT escaping, UTC DTSTART/DTEND (one hour when no end), `SEQUENCE` from `seq`, and a
++ cancelled event emitted with `STATUS:CANCELLED` for 7 days. A calendar client is not a test you can
++ apologize to.
++- **Reminders are claimed atomically** (`UPDATE … WHERE sent_at IS NULL RETURNING`); a failed push
++ releases the claim for the next tick, and a cancelled event never sends.
++- **The event id is fixed at write time** and the outbox is retried with backoff; Events is
++ idempotent on that id, so a retry/restart must not make a second event.
++- **The private feed is a bearer token:** rotate replaces it and the old URL is 404 at once; export
++ includes the feed row without the token.
++- **Followers-only events are absent from public lists**, not filtered in the page; `follows_unavailable`
++ is the honest answer when Network is down.
++- **Imports never invent a date:** a parsed calendar entry keeps its source and time; a holiday is
++ labelled as a holiday.
ddiff --git a/CLAUDE.md b/CLAUDE.md
nnew file mode 100644
iindex 0000000..43c994c
---- /dev/null
++++ b/CLAUDE.md
@@@ -0,0 +1 @@