openvibers/openvibe.zone AGENTS.md

AGENTS.md

main 6.0 KB · 107 lines · markdown

1# AGENTS.md — OpenVibe.Zone
2
3## Project overview
4
5OpenVibe.Zone (`https://openvibe.zone`) provides private S3-compatible storage **zones** for OpenVibe
6projects: one named object zone per project environment (sandbox or production), reached by any S3 tool
7at `https://s3.openvibe.media` (path style; `https://s3.openvibe.network` is the same surface behind
8Cloudflare). The design is ADR-031. Zone owns the zone record and control API; **Media owns everything
9inside a zone**.
10
11- **Entry:** `server/index.js`; app `server/app.js`; port `5050`, service id `zone`.
12- **API:** `server/http/api.js` (`/api/v1`), `events.js` (`/internal/events`, v2-signed,
13 loopback-only), `pages.js` (console + docs), `discovery.js`, `principal.js`, `caller-limits.js`.
14- **Domain:** `server/zone/` — `zones.js` (records, names, revisions, provisioning, deletion, the job),
15 `usage.js` (Media's hourly rollups, kept and summed), `clients.js` (Media provision/report/keys and
16 Network roles/projects), `media-limits.js`.
17- **Pages:** `server/render/` (`layout.js`, `html.js`, `markdown.js`, `short.js`); CSS in `public/css`.
18 Console changes are same-origin forms; no SPA.
19- **Secrets:** an access key's secret is shown once and never stored here (Media keeps it encrypted).
20
21## Owns, does not own
22
23Owns the zone record: its name (never reused), environment, state, description, per-zone quota; the
24control API and console; its metered usage as Media reports it (`zone_usage`); the resource index/control
25views. Tables in `ov_zone` (migrations below).
26
27Does not own: **Media owns the objects, bytes, replicas, S3/native data APIs, access keys, quota
28enforcement, usage counting and the one deletion path.** Zone never lists keys, never holds bytes and
29never counts. Identity/projects are Network's; the price comparison is Host's.
30
31## Capabilities and API
32
33Base `https://openvibe.zone/api/v1` (and `https://api.openvibe.zone/api/v1`). A caller is either a
34developer app of the project with the route's capability for audience `openvibe.zone` (a sandbox app
35reaches only the sandbox), or a project member (any role reads; admins/owners change).
36
37| Route | Capability |
38|---|---|
39| `POST /projects/:project_id/zones` (Idempotency-Key) | `zone.zone.create` |
40| `GET /projects/:project_id/zones` | `zone.zone.list` |
41| `GET /zones/:zone_id` | `zone.zone.read` |
42| `PATCH /zones/:zone_id` | `zone.zone.manage` |
43| `DELETE /zones/:zone_id?recursive=true` | `zone.zone.delete` |
44| `GET /zones/:zone_id/usage` | `zone.usage.read` |
45| `GET /resources`, `/resources/:ovrn` | `zone.resource.read` (first-party) |
46| `POST /resources/control` | `zone.resource.control` (first-party) |
47
48Grants Zone holds: `media.zone.provision` and `media.access_key.manage` (audience `openvibe.media`),
49`network.project.member.read`, and `events.subscription.manage` (the `media.usage.recorded`
50subscription). SDK `openvibe-sdk/zone` is the client surface.
51
52## Database and migrations
53
54PostgreSQL `ov_zone` through `openvibe-sdk/db` (PgBouncer for the app, owner role for migrations):
55`0001` initial, `0002` usage, `0003` telemetry, `0004` account data, `0005`–`0007` sharing/defaults.
56Applied at boot; never edit a migration that has run. Telemetry is aggregated by route template,
57method and status class (never raw URL), with 14-day retention; `/shared`, static assets, health,
58readiness and `/metrics` are not counted.
59
60## Auth and security
61
62Network RS256 JWTs (JWKS) for apps/members; `/internal/events` answers only on loopback (nginx 404s
63it), requires a valid v2 signature (`ZONE_EVENTS_SECRET`) and refuses a request through a proxy. Every
64console change is a same-origin form; only a project's admins and owners change zones and keys. Media's
65S3 answers are `private, no-store` with CDN no-store, so no edge keeps them. Never log or store an
66access-key secret; no shared loopback key exists (tested by grep and at runtime). Account export/
67deletion covers every table with a person column or exempts it with a reason.
68
69## Deploy
70
71`sudo ovhost deploy zone`: unit `openvibe-zone.service` on `127.0.0.1:5050`, database `ov_zone`, env
72`/etc/openvibe/zone.env` (with `EVENTS_URL` and `ZONE_EVENTS_SECRET` for the usage subscription), nginx
73`deploy/nginx/openvibe.zone.conf` on openvibe.zone's own certificate; migrations run at boot. Rollback
74with `sudo ovhost rollback zone --to <sha>`.
75
76## Commands and tests
77
78```bash
79npm ci
80npm run dev # http://127.0.0.1:5050
81npm test # every test/*.test.js, temp PGlite + mock Network
82npm test -- login # only matching files
83npm run test:pg # the same on PostgreSQL
84```
85
86`test/run.js` uses `openvibe-shared/test-runner`; temp PGlite and an in-process Network mock, never the
87network. Coverage includes zone names/revisions/provisioning/deletion, usage rollups, app vs member
88authorization, the S3 surface/limits, nginx auth limits, the no-internal-key and secrets checks,
89session/redirect security, account data and the pages.
90
91## Conventions and pitfalls
92
93- CommonJS, 4-space indent, single quotes, semicolons; problem+json errors; escaped HTML.
94- **Zone is metadata only.** Do not add an object list, byte store, replica, key listing or a second
95 delete path — that is Media's, and a duplicate would drift. Operations call Media with Zone's own
96 grants.
97- **A zone name is never reused**, even after deletion; a rename does not change the S3 bucket identity.
98- **The secret is shown once.** A key response may carry it that one time; never persist, log or
99 re-render it. Media holds the encrypted key.
100- **Idempotency:** create carries `Idempotency-Key`; a retry must return the same zone, not a second one.
101- **Sandbox apps reach only the sandbox**, and only their project; a member check is Network's answer,
102 never a client claim.
103- **The usage is Media's:** keep the `media.usage.recorded` rollups and sum them; never estimate or
104 count a byte locally.
105- **No SQL migration may block a rollback**, and one with a person column must be covered by account
106 data or exempted in `server/account-data.js` with a reason.
107