openvibers/openvibe.services AGENTS.md
AGENTS.md
| 1 | # AGENTS.md — OpenVibe.Services |
| 2 | |
| 3 | ## Project overview |
| 4 | |
| 5 | OpenVibe.Services is the network's developer platform and resource control plane, served at |
| 6 | `https://openvibe.services`. It is one console and API over OpenVibe.Network's projects, apps, |
| 7 | credentials, grants and quotas; it also owns release metadata, the authority registry and the merged |
| 8 | resource index, and (step 10) control operations routed to the authority that owns a resource. |
| 9 | |
| 10 | - **Entry:** `server/index.js` (`node server/index.js`); Express app in `server/app.js`. |
| 11 | - **Port / service id:** `127.0.0.1:4930`, id `services`; unit `openvibe-services.service`. |
| 12 | - **Portal:** `server/http/portal.js` (`/projects`, `/projects/:project/resources`, `/secrets`, |
| 13 | `/usage`, `/export`, `/apps/:app`), `server/http/mod.js`, `server/http/tools.js`. |
| 14 | - **API and machine routes:** `server/http/api.js` (`/api/v1`), `server/http/openapi.js`, |
| 15 | `server/http/network-mcp.js` (`/mcp`), `server/http/discovery.js` (robots/sitemap/llms). |
| 16 | - **Generated docs:** `server/docs/`, `server/http/docs.js`, `server/http/ai-docs.js`, |
| 17 | `server/http/money-docs.js` — generated from the pinned contracts, capability catalog, SDK |
| 18 | `.d.ts` files and ADRs at boot; never hand-written where it can drift. |
| 19 | - **Resource index / control:** `server/resources.js`, `server/authorities/index.js`, |
| 20 | `server/registry/`, `server/domain/`; SDK `openvibe-sdk/resources` does the fan-out and merge. |
| 21 | - **Pages:** server-rendered through `openvibe-shared/shell`; the only own scripts are the optional |
| 22 | in-browser webhook verifier and a copy button. No SPA, no build step. |
| 23 | |
| 24 | ## Owns, does not own |
| 25 | |
| 26 | Owns: release metadata keyed to Network app ids (validated `services.app-manifest@1` and |
| 27 | `mods.mod-manifest@1`; releases `draft → published → deprecated → revoked` and an append-only log); |
| 28 | the merged resource index (`services.resource.read`, read live from every authority, never stored as |
| 29 | truth); trust tiers (metadata only); playground run logs; the events `services.app.published`, |
| 30 | `services.app.deprecated`, `services.app.revoked` and `services.moderation.action`; the generated |
| 31 | reference and the console pages. |
| 32 | |
| 33 | Does not own: projects, members, apps, credentials, grants, allowances, quotas or audit — those are |
| 34 | OpenVibe.Network (`/api/v1/projects`), and Services never copies them. Secrets are Network's: |
| 35 | Services renders a secret only once, never stores, logs or re-displays it. Identity, billing and |
| 36 | moderation stay with their services. |
| 37 | |
| 38 | ## Who calls it, what it calls |
| 39 | |
| 40 | Developers and first-party services use the console and the public API; SDK |
| 41 | `openvibe-sdk/projects` and `openvibe-sdk/resources` are the client surface, and the MCP server at |
| 42 | `/mcp` exposes the network's read-only tools. Services calls: |
| 43 | |
| 44 | - **Network** — SSO (`services`, PKCE S256), JWKS, `/api/v1/projects` with the person's own access |
| 45 | token, the registry, client-credentials tokens, and `/internal/confirmations` for sensitive |
| 46 | control operations. |
| 47 | - **Events** — the outbox relay publishes `services.app.*` with Services' service token; playgrounds |
| 48 | publish with the app's own token; the project archive reads app events with an export token. |
| 49 | - **Media** — playground uploads with the app's token; the project archive lists objects and |
| 50 | namespaces with an export token. |
| 51 | - **Authorities** — the registry is built at boot from the pinned `openvibe-contracts` service |
| 52 | manifests (every service whose manifest lists `<id>.resource.read`); the loopback origin comes from |
| 53 | the manifest, `SERVICES_<ID>_URL` overrides it. No polling loop: each request reads the |
| 54 | authorities. |
| 55 | |
| 56 | The authority registry is not hand-maintained: turning a capability on in a service's manifest puts |
| 57 | it in the registry on the next boot. |
| 58 | |
| 59 | ## Database and migrations |
| 60 | |
| 61 | PostgreSQL `ov_services` (ADR-035) via `openvibe-sdk/db`: `DATABASE_URL` (PgBouncer) for the app, |
| 62 | `DATABASE_DIRECT_URL` for migrations. `server/db.js` applies `migrations/NNNN_*.sql` at boot under an |
| 63 | advisory lock, then serves with the pooled role; a held migration is logged and does not crash the |
| 64 | service. Never edit a migration that has run; a new table/column is an expand migration and removal |
| 65 | waits for a contract migration. Valkey is optional and holds only per-actor limit counters. |
| 66 | `telemetry_samples` is this service's own table, pruned after 14 days. |
| 67 | |
| 68 | ## Auth |
| 69 | |
| 70 | Network RS256 JWTs (JWKS) plus the SSO session cookie. Service tokens carry `openvibe.services` |
| 71 | audience and the route's capability (`services.release.read`, `services.release.manage`, |
| 72 | `services.resource.read`, `services.resource.control`); a person's user token is used only for the |
| 73 | person's own projects and the pages. The index answers a first-party service token, or one project |
| 74 | at a time for a person who owns or belongs to it — never unscoped, and a non-member project is the |
| 75 | same 404 Network uses. `/api/v1/authorities` is first-party loopback data. Sensitive control |
| 76 | operations require Network's confirmation; until the grant exists they fail with |
| 77 | `confirmations.unavailable` and nothing is applied. No path sends or accepts a shared loopback key. |
| 78 | |
| 79 | ## Deploy |
| 80 | |
| 81 | `sudo ovhost deploy services` (git-checkout: fetch, fast-forward `/opt/openvibe.services`, install on |
| 82 | a lockfile change, restart, wait for `/api/ready`; rollback `sudo ovhost rollback services --to <sha>`). |
| 83 | Unit `deploy/systemd/openvibe-services.service`, env `/etc/openvibe/services.env`, nginx |
| 84 | `deploy/nginx/openvibe.services.conf`, database `ov_services`. The release migrates at boot. A |
| 85 | rollback across the console migration serves the old index and the console pages answer 404 until |
| 86 | the next deploy. |
| 87 | |
| 88 | ## Commands and tests |
| 89 | |
| 90 | ```bash |
| 91 | npm install |
| 92 | npm run dev # http://localhost:4930 |
| 93 | npm test # PGlite, in-process stub Network/Events/Media |
| 94 | npm run test:pg # the same on the PostgreSQL containers |
| 95 | npm test -- index scaffold # only matching files |
| 96 | npx openvibe-contracts-check --service services --src server |
| 97 | ``` |
| 98 | |
| 99 | `test/run.js` uses `openvibe-shared/test-runner` (180 s, serial); each file gets a temp database and |
| 100 | stubs, never the network. The suite covers the crawl artifacts and their `lastmod` source, the |
| 101 | ADR-013 trust-tier migration, secrets never stored or re-displayed, honest Network errors, the scope |
| 102 | editor, generated docs matching the pins, v2-only webhook verification, playground grants, usage and |
| 103 | resource pages, project export/delete, the resource index auth matrix and cursors, readiness/metrics, |
| 104 | and the home page's size budgets (`test/perf-budget.test.js`). |
| 105 | |
| 106 | ## Conventions |
| 107 | |
| 108 | - CommonJS, 4-space indent, single quotes, semicolons. Server-rendered HTML; escape every value |
| 109 | (names, problem details, OVRNs) and keep pages usable without JavaScript. |
| 110 | - RFC 9457 problem+json for API errors (`openvibe-contracts`); render Network's status/code/detail |
| 111 | and request id rather than inventing a friendlier error. |
| 112 | - Guard routes with `openvibe-contracts` `requireCapability`; do not hand-roll a grant check. |
| 113 | - Anything generated (docs, OpenAPI, manifests, sitemap/llms) is rendered from the pinned packages |
| 114 | and `STATUS.json`, with the versions shown; never patch the output by hand. |
| 115 | - A release is validated against the pinned contracts before it is stored; publish/deprecate/revoke |
| 116 | are state transitions with an event, not direct row edits. |
| 117 | |
| 118 | ## Key pitfalls |
| 119 | |
| 120 | - **Never persist or re-display a secret.** Network returns a client secret once; Services passes it |
| 121 | through and drops it. The export archive must contain no secret — tests assert both. |
| 122 | - **A person's resource read is never unscoped.** `?project=` is required for a user token, Network |
| 123 | answers membership, and another project's rows must not appear even if an authority ignores |
| 124 | `?owner=`. |
| 125 | - **Trust tiers are metadata**, not a grant check: changing a tier never widens access. |
| 126 | - **The merged index is not stored as truth.** Read the authorities per request; a failed authority |
| 127 | is named in `stale`, never silently dropped or fatal. |
| 128 | - **Control operations need the Network confirmation** and the authority's own capability; a gated |
| 129 | action without the grant fails closed. |
| 130 | - **Trust the pinned versions:** docs, manifest validation and compatibility pages all come from |
| 131 | `openvibe-contracts`/`openvibe-sdk`; bump the pins instead of copying their content. |
| 132 |