openvibers/openvibe.events AGENTS.md

AGENTS.md

main 9.8 KB · 160 lines · markdown

1# AGENTS.md — OpenVibe.Events
2
3## Project overview
4
5The durable event backbone of the network: durable events, subscriptions, signed delivery, retry, dead
6letters and replay, plus the Realtime SSE plane (ADR-005 put Realtime inside Events). Every authoritative
7service writes its domain change and its outbox row in one transaction; a relay publishes here; consumers
8use inbox/idempotency receipts; browsers get authorized, resumable projections. Node.js/Express,
9PostgreSQL (`ov_events`), CommonJS, **no build step**.
10
11- Service id `events`, port `4300`, origin `https://openvibe.events` (API and product origin), unit
12 `openvibe-events.service`, release root `/opt/openvibe.events`. The old `events.openvibe.network` was
13 retired on 2026-10-10.
14- `package.json` `main` is [lib/client.js](lib/client.js) — the client library services import — while
15 `npm start` runs [server/index.js](server/index.js).
16
17## What it owns
18
19- Tables: `events`, `events_archive` (the replay tier), `subscriptions`, `deliveries`,
20 `consumer_checkpoints`, `idempotency_receipts`, `app_revocations`, `app_usage`, `billing_readings`,
21 `billing_periods`, `telemetry_samples`; the realtime fan-out is in-process (one Events process).
22- The canonical event envelope (event_id, trace_id, type, version, source, actor, subject + revision,
23 payload), visibility (`public|internal|subject`), priority classes `critical|important|low`, loop
24 guards, backpressure, the DLQ and replay.
25- Developer-app event scope, sandbox separation and per-project quotas (ADR-014).
26- Realtime: SSE topics, `Last-Event-ID`/cursor resume and gap detection (WebSocket and presence are not
27 built).
28- The resource index (`/api/v1/resources`), the fabric (carriers, planner, explain), the home page and its
29 stats, and the developer limits (`/limits.json`).
30- Retention: three tiers (hot window, archive/replay, then deletion), checked at boot and in the config.
31
32Not owned: the domain events themselves (each service), identity (Network), the consumers' inboxes, the
33service that decides what is private (Events enforces visibility, it does not invent it).
34
35## Who calls it, and whom it calls
36
37- Callers: every service with a Network client-credentials token audience `openvibe.events`; developer
38 apps; browsers on the realtime stream with a ticket/cookie/Bearer; services reading the resource index.
39- Depends on: **OpenVibe.Contracts** (`events.event-envelope@1`, service tokens, problem details, ids)
40 and **OpenVibe.Network** (the JWKS signing key and user subject ids for topic authorization). No
41 product service is required for delivery.
42
43## Repository map
44
45- **Entry:** [server/index.js](server/index.js) — store, keys, config, worker and realtime; graceful stop.
46- **App:** [server/app.js](server/app.js) — `/auth`, `/shared`, the `/api` routers, realtime, health/
47 readiness, metrics, home, discovery, legal and release files.
48- **API:** [server/api/](server/api/) — `publish.js`, `subscriptions.js`, `read.js`, `policies.js`,
49 `placement.js`, `resources.js`, `resource-control.js`.
50- **Fabric:** [server/fabric/](server/fabric/) — `planner.js`, `signals.js`, `policy.js`, `nats-core.js`,
51 `rate-cards.json`.
52- **Delivery/store:** [server/egress.js](server/egress.js), the store/worker modules, `server/realtime.js`,
53 [server/home.js](server/home.js), [server/home-stats.js](server/home-stats.js),
54 [server/discovery.js](server/discovery.js), [server/openapi.js](server/openapi.js).
55- **Client library:** [lib/client.js](lib/client.js) (also shipped to services through the SDK's events
56 client); [server/endpoints.js](server/endpoints.js), [server/limits.js](server/limits.js).
57- **Migrations:** [migrations/](migrations/) (events, subscriptions, deliveries, checkpoints, receipts,
58 app usage/billing, telemetry).
59- **Deploy:** [deploy/systemd/openvibe-events.service](deploy/systemd/openvibe-events.service),
60 [deploy/nginx/openvibe.events.conf](deploy/nginx/openvibe.events.conf).
61
62## Commands
63
64```bash
65npm ci
66cp .env.example .env
67npm run dev # http://127.0.0.1:4300
68npm start # node server/index.js
69npm test # test/run.js: every file in its own process, temp databases
70npm run test:pg # same on PostgreSQL (EVENTS_TEST_STORE=pg)
71npm test -- delivery # filter by file name; --strict makes a skip fail
72ov test # the harness's sharded run of what the change reaches
73```
74
75Node 22 in production (`fnm exec --using=22.22.1 npm test`).
76
77## Database and migrations
78
79`ov_events` on the host's data role (ADR-035); temp/PGlite databases in tests. Async through
80`openvibe-sdk/db`; migrations `NNNN_*.sql` applied at boot; never edit an applied one, add a new number.
81The event and archive tables are append-only; a delivery is claimed/updated, an event is not rewritten
82except for a redaction tombstone. The delivery worker is the only writer of `deliveries` state; a replay
83never mutates the original event.
84
85## Auth and capabilities
86
87- Services use a Network client-credentials token (audience `openvibe.events`), verified offline against
88 the JWKS with `openvibe-sdk/auth` `verifyServiceToken` and the pinned contracts rules. People use
89 `verifyUserToken`, which refuses service principals and typed tokens.
90- Capabilities: `events.event.publish`, `events.subscription.manage` (own subscriptions only),
91 `events.event.read` (+ checkpoints/realtime), `events.delivery.admin`, `events.resource.read`
92 (`first-party`). Developer apps get the public app capabilities on the same routes; a sandbox token is
93 accepted **only** on developer-app routes (`401 token.sandbox_refused` elsewhere); an app token is never
94 judged on a first-party capability.
95- Realtime accepts a **single-use Network ticket**, the `ov_token` cookie, a Bearer user JWT or a service
96 token with `events.event.read`; a ticket is never a session and is never logged.
97- `/api/v1/deliveries` stays host-local; `/metrics` answers direct loopback callers only; nginx refuses
98 both paths publicly.
99
100## Frontend loading model
101
102The only page is the server-rendered home (`server/home.js`): the bus at work from this service's own
103tables (counts and names only — never a payload, actor, subject id or principal), with a clear empty state
104and a 1.5 s bound; `/` is HTML by default, and the text API index answers `Accept: text/plain` or
105`?format=text`. Discovery (`robots.txt`, `sitemap.xml`, `llms.txt`) and the JSON-LD `ItemList`/`FAQPage`
106come from the same data. There is no SPA, bundler or transpiler; `openvibe-shared` supplies the Frame and
107`/shared/*` from the pinned node_modules. The realtime stream is SSE at `/realtime/stream`.
108
109## Deploy
110
111`sudo ovhost deploy events` (git-checkout: fetch, fast-forward `/opt/openvibe.events`, install on a
112lockfile change, restart, wait for `/api/ready`). Unit `openvibe-events.service` on
113`127.0.0.1:4300`, env `/etc/openvibe/events.env`; nginx
114[deploy/nginx/openvibe.events.conf](deploy/nginx/openvibe.events.conf) exposes the home/discovery,
115`/realtime/stream`, health and the token-guarded developer API; `/api/v1/deliveries` stays host-local.
116ovhost treats open realtime connections as a report-only drain (`--wait-idle` waits for them). Rollback
117`sudo ovhost rollback events --to <sha>`; migrations only add tables and columns. `EVENTS_LIMITS=off`
118turns the per-actor limits off without a deploy.
119
120## Tests
121
122`npm test` runs each `test/*.test.js` in its own process with temp databases and in-process Network keys;
123`npm run test:pg` runs the same on PostgreSQL. The suite covers publish/envelope/validation, visibility and
124redaction, subscriptions and endpoint egress rules, signed v2 delivery and the replay window, retries/
125backoff, DLQ and replay, checkpoints/cursors, developer apps and sandbox separation, realtime resume/gap,
126the resource index and the home stats bounds. `ov test` runs the same.
127
128## Conventions
129
130- CommonJS, 4-space indent, single quotes, semicolons; factories with injectable config/store/clock/fetch/
131 log; `openvibe-contracts` shapes for the envelope, errors and ids.
132- Errors are RFC 9457 problem+json with stable codes.
133- Priority is data (`critical|important|low`), not a new queue; retries use exponential backoff; a
134 delivery attempt is idempotent by delivery id.
135- Sign with **v2 only** (`X-OpenVibe-Signature-V2`); a subscription secret is shown once.
136
137## Pitfalls
138
139- **`internal` events never reach a browser; `subject` events reach only that person.** A guessed topic
140 returns nothing; do not "helpfully" widen a topic.
141- **Redacted events replay as tombstones**, never as the original payload.
142- **Endpoints are allow-listed:** first-party subscriptions must be `http(s)` on `127.0.0.1` or an
143 OpenVibe host (`EVENTS_ENDPOINT_HOSTS`); app webhooks must be public https, checked at creation and at
144 every delivery's DNS lookup; **redirects are never followed**.
145- **A sandbox token is refused everywhere but the developer-app routes**; an app token is never judged on
146 a first-party capability.
147- **The delivery worker is the only mutator of delivery state**; a replay is a new delivery, not an edit.
148- **`/api/v1/deliveries` is host-local and `/metrics` is loopback-only.** Do not add them to the vhost.
149- **The home page leaves only counts and names.** No payload, actor, subject id or principal in the stats.
150- **Don't wait on the database for the shell:** the stats read has a 1.5 s bound and a clear empty state;
151 a slow read must not take the page down.
152- **One Events process owns the realtime fan-out.** A second process needs a shared fan-out design first.
153
154## Documentation
155
156- [README.md](README.md) — purpose, run, auth/capabilities, publishing/redaction, subscriptions/delivery,
157 retention, resource index, fabric, developer apps, client library, realtime, security, deploy.
158- [docs/](docs/) — delivery, subscriptions, realtime and operations notes.
159- `lib/client.js` is the reference client; `openvibe-sdk/events` is the supported one.
160