openvibers/openvibe.today Commits
AGENTS.md: the context an agent starts from (#173)
Changes
nnew file mode 100644@@@ -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.nnew file mode 100644@@@ -0,0 +1 @@
new file mode 100644
new file mode 100644
@@ -0,0 +1,100 @@
@@ -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.
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@