openvibers/openvibe.zone AGENTS.md
AGENTS.md
AGENTS.md — OpenVibe.Zone
Project overview
OpenVibe.Zone (https://openvibe.zone) provides private S3-compatible storage zones for OpenVibe
projects: one named object zone per project environment (sandbox or production), reached by any S3 tool
at https://s3.openvibe.media (path style; https://s3.openvibe.network is the same surface behind
Cloudflare). The design is ADR-031. Zone owns the zone record and control API; Media owns everything
inside a zone.
- Entry:
server/index.js; appserver/app.js; port5050, service idzone. - API:
server/http/api.js(/api/v1),events.js(/internal/events, v2-signed,
loopback-only),pages.js(console + docs),discovery.js,principal.js,caller-limits.js. - Domain:
server/zone/—zones.js(records, names, revisions, provisioning, deletion, the job),
usage.js(Media's hourly rollups, kept and summed),clients.js(Media provision/report/keys and
Network roles/projects),media-limits.js. - Pages:
server/render/(layout.js,html.js,markdown.js,short.js); CSS inpublic/css.
Console changes are same-origin forms; no SPA. - Secrets: an access key's secret is shown once and never stored here (Media keeps it encrypted).
Owns, does not own
Owns the zone record: its name (never reused), environment, state, description, per-zone quota; the
control API and console; its metered usage as Media reports it (zone_usage); the resource index/control
views. Tables in ov_zone (migrations below).
Does not own: Media owns the objects, bytes, replicas, S3/native data APIs, access keys, quota
enforcement, usage counting and the one deletion path. Zone never lists keys, never holds bytes and
never counts. Identity/projects are Network's; the price comparison is Host's.
Capabilities and API
Base https://openvibe.zone/api/v1 (and https://api.openvibe.zone/api/v1). A caller is either a
developer app of the project with the route's capability for audience openvibe.zone (a sandbox app
reaches only the sandbox), or a project member (any role reads; admins/owners change).
| Route | Capability |
|---|---|
| POST /projects/:project_id/zones (Idempotency-Key) | zone.zone.create |
| GET /projects/:project_id/zones | zone.zone.list |
| GET /zones/:zone_id | zone.zone.read |
| PATCH /zones/:zone_id | zone.zone.manage |
| DELETE /zones/:zone_id?recursive=true | zone.zone.delete |
| GET /zones/:zone_id/usage | zone.usage.read |
| GET /resources, /resources/:ovrn | zone.resource.read (first-party) |
| POST /resources/control | zone.resource.control (first-party) |
Grants Zone holds: media.zone.provision and media.access_key.manage (audience openvibe.media),
network.project.member.read, and events.subscription.manage (the media.usage.recorded
subscription). SDK openvibe-sdk/zone is the client surface.
Database and migrations
PostgreSQL ov_zone through openvibe-sdk/db (PgBouncer for the app, owner role for migrations):
0001 initial, 0002 usage, 0003 telemetry, 0004 account data, 0005–0007 sharing/defaults.
Applied at boot; never edit a migration that has run. Telemetry is aggregated by route template,
method and status class (never raw URL), with 14-day retention; /shared, static assets, health,
readiness and /metrics are not counted.
Auth and security
Network RS256 JWTs (JWKS) for apps/members; /internal/events answers only on loopback (nginx 404s
it), requires a valid v2 signature (ZONE_EVENTS_SECRET) and refuses a request through a proxy. Every
console change is a same-origin form; only a project's admins and owners change zones and keys. Media's
S3 answers are private, no-store with CDN no-store, so no edge keeps them. Never log or store an
access-key secret; no shared loopback key exists (tested by grep and at runtime). Account export/
deletion covers every table with a person column or exempts it with a reason.
Deploy
sudo ovhost deploy zone: unit openvibe-zone.service on 127.0.0.1:5050, database ov_zone, env
/etc/openvibe/zone.env (with EVENTS_URL and ZONE_EVENTS_SECRET for the usage subscription), nginx
deploy/nginx/openvibe.zone.conf on openvibe.zone's own certificate; migrations run at boot. Rollback
with sudo ovhost rollback zone --to <sha>.
Commands and tests
npm ci
npm run dev # http://127.0.0.1:5050
npm test # every test/*.test.js, temp PGlite + mock Network
npm test -- login # only matching files
npm run test:pg # the same on PostgreSQL
test/run.js uses openvibe-shared/test-runner; temp PGlite and an in-process Network mock, never the
network. Coverage includes zone names/revisions/provisioning/deletion, usage rollups, app vs member
authorization, the S3 surface/limits, nginx auth limits, the no-internal-key and secrets checks,
session/redirect security, account data and the pages.
Conventions and pitfalls
- CommonJS, 4-space indent, single quotes, semicolons; problem+json errors; escaped HTML.
- Zone is metadata only. Do not add an object list, byte store, replica, key listing or a second
delete path — that is Media's, and a duplicate would drift. Operations call Media with Zone's own
grants. - A zone name is never reused, even after deletion; a rename does not change the S3 bucket identity.
- The secret is shown once. A key response may carry it that one time; never persist, log or
re-render it. Media holds the encrypted key. - Idempotency: create carries
Idempotency-Key; a retry must return the same zone, not a second one. - Sandbox apps reach only the sandbox, and only their project; a member check is Network's answer,
never a client claim. - The usage is Media's: keep the
media.usage.recordedrollups and sum them; never estimate or
count a byte locally. - No SQL migration may block a rollback, and one with a person column must be covered by account
data or exempted inserver/account-data.jswith a reason.