openvibers/openvibe.services Commits
AGENTS.md: the context an agent starts from (#290)
Changes
nnew file mode 100644@@@ -0,0 +1,131 @@++# AGENTS.md — OpenVibe.Services++++## Project overview++++OpenVibe.Services is the network's developer platform and resource control plane, served at++`https://openvibe.services`. It is one console and API over OpenVibe.Network's projects, apps,++credentials, grants and quotas; it also owns release metadata, the authority registry and the merged++resource index, and (step 10) control operations routed to the authority that owns a resource.++++- **Entry:** `server/index.js` (`node server/index.js`); Express app in `server/app.js`.++- **Port / service id:** `127.0.0.1:4930`, id `services`; unit `openvibe-services.service`.++- **Portal:** `server/http/portal.js` (`/projects`, `/projects/:project/resources`, `/secrets`,++ `/usage`, `/export`, `/apps/:app`), `server/http/mod.js`, `server/http/tools.js`.++- **API and machine routes:** `server/http/api.js` (`/api/v1`), `server/http/openapi.js`,++ `server/http/network-mcp.js` (`/mcp`), `server/http/discovery.js` (robots/sitemap/llms).++- **Generated docs:** `server/docs/`, `server/http/docs.js`, `server/http/ai-docs.js`,++ `server/http/money-docs.js` — generated from the pinned contracts, capability catalog, SDK++ `.d.ts` files and ADRs at boot; never hand-written where it can drift.++- **Resource index / control:** `server/resources.js`, `server/authorities/index.js`,++ `server/registry/`, `server/domain/`; SDK `openvibe-sdk/resources` does the fan-out and merge.++- **Pages:** server-rendered through `openvibe-shared/shell`; the only own scripts are the optional++ in-browser webhook verifier and a copy button. No SPA, no build step.++++## Owns, does not own++++Owns: release metadata keyed to Network app ids (validated `services.app-manifest@1` and++`mods.mod-manifest@1`; releases `draft → published → deprecated → revoked` and an append-only log);++the merged resource index (`services.resource.read`, read live from every authority, never stored as++truth); trust tiers (metadata only); playground run logs; the events `services.app.published`,++`services.app.deprecated`, `services.app.revoked` and `services.moderation.action`; the generated++reference and the console pages.++++Does not own: projects, members, apps, credentials, grants, allowances, quotas or audit — those are++OpenVibe.Network (`/api/v1/projects`), and Services never copies them. Secrets are Network's:++Services renders a secret only once, never stores, logs or re-displays it. Identity, billing and++moderation stay with their services.++++## Who calls it, what it calls++++Developers and first-party services use the console and the public API; SDK++`openvibe-sdk/projects` and `openvibe-sdk/resources` are the client surface, and the MCP server at++`/mcp` exposes the network's read-only tools. Services calls:++++- **Network** — SSO (`services`, PKCE S256), JWKS, `/api/v1/projects` with the person's own access++ token, the registry, client-credentials tokens, and `/internal/confirmations` for sensitive++ control operations.++- **Events** — the outbox relay publishes `services.app.*` with Services' service token; playgrounds++ publish with the app's own token; the project archive reads app events with an export token.++- **Media** — playground uploads with the app's token; the project archive lists objects and++ namespaces with an export token.++- **Authorities** — the registry is built at boot from the pinned `openvibe-contracts` service++ manifests (every service whose manifest lists `<id>.resource.read`); the loopback origin comes from++ the manifest, `SERVICES_<ID>_URL` overrides it. No polling loop: each request reads the++ authorities.++++The authority registry is not hand-maintained: turning a capability on in a service's manifest puts++it in the registry on the next boot.++++## Database and migrations++++PostgreSQL `ov_services` (ADR-035) via `openvibe-sdk/db`: `DATABASE_URL` (PgBouncer) for the app,++`DATABASE_DIRECT_URL` for migrations. `server/db.js` applies `migrations/NNNN_*.sql` at boot under an++advisory lock, then serves with the pooled role; a held migration is logged and does not crash the++service. Never edit a migration that has run; a new table/column is an expand migration and removal++waits for a contract migration. Valkey is optional and holds only per-actor limit counters.++`telemetry_samples` is this service's own table, pruned after 14 days.++++## Auth++++Network RS256 JWTs (JWKS) plus the SSO session cookie. Service tokens carry `openvibe.services`++audience and the route's capability (`services.release.read`, `services.release.manage`,++`services.resource.read`, `services.resource.control`); a person's user token is used only for the++person's own projects and the pages. The index answers a first-party service token, or one project++at a time for a person who owns or belongs to it — never unscoped, and a non-member project is the++same 404 Network uses. `/api/v1/authorities` is first-party loopback data. Sensitive control++operations require Network's confirmation; until the grant exists they fail with++`confirmations.unavailable` and nothing is applied. No path sends or accepts a shared loopback key.++++## Deploy++++`sudo ovhost deploy services` (git-checkout: fetch, fast-forward `/opt/openvibe.services`, install on++a lockfile change, restart, wait for `/api/ready`; rollback `sudo ovhost rollback services --to <sha>`).++Unit `deploy/systemd/openvibe-services.service`, env `/etc/openvibe/services.env`, nginx++`deploy/nginx/openvibe.services.conf`, database `ov_services`. The release migrates at boot. A++rollback across the console migration serves the old index and the console pages answer 404 until++the next deploy.++++## Commands and tests++++```bash++npm install++npm run dev # http://localhost:4930++npm test # PGlite, in-process stub Network/Events/Media++npm run test:pg # the same on the PostgreSQL containers++npm test -- index scaffold # only matching files++npx openvibe-contracts-check --service services --src server++```++++`test/run.js` uses `openvibe-shared/test-runner` (180 s, serial); each file gets a temp database and++stubs, never the network. The suite covers the crawl artifacts and their `lastmod` source, the++ADR-013 trust-tier migration, secrets never stored or re-displayed, honest Network errors, the scope++editor, generated docs matching the pins, v2-only webhook verification, playground grants, usage and++resource pages, project export/delete, the resource index auth matrix and cursors, readiness/metrics,++and the home page's size budgets (`test/perf-budget.test.js`).++++## Conventions++++- CommonJS, 4-space indent, single quotes, semicolons. Server-rendered HTML; escape every value++ (names, problem details, OVRNs) and keep pages usable without JavaScript.++- RFC 9457 problem+json for API errors (`openvibe-contracts`); render Network's status/code/detail++ and request id rather than inventing a friendlier error.++- Guard routes with `openvibe-contracts` `requireCapability`; do not hand-roll a grant check.++- Anything generated (docs, OpenAPI, manifests, sitemap/llms) is rendered from the pinned packages++ and `STATUS.json`, with the versions shown; never patch the output by hand.++- A release is validated against the pinned contracts before it is stored; publish/deprecate/revoke++ are state transitions with an event, not direct row edits.++++## Key pitfalls++++- **Never persist or re-display a secret.** Network returns a client secret once; Services passes it++ through and drops it. The export archive must contain no secret — tests assert both.++- **A person's resource read is never unscoped.** `?project=` is required for a user token, Network++ answers membership, and another project's rows must not appear even if an authority ignores++ `?owner=`.++- **Trust tiers are metadata**, not a grant check: changing a tier never widens access.++- **The merged index is not stored as truth.** Read the authorities per request; a failed authority++ is named in `stale`, never silently dropped or fatal.++- **Control operations need the Network confirmation** and the authority's own capability; a gated++ action without the grant fails closed.++- **Trust the pinned versions:** docs, manifest validation and compatibility pages all come from++ `openvibe-contracts`/`openvibe-sdk`; bump the pins instead of copying their content.nnew file mode 100644@@@ -0,0 +1 @@
new file mode 100644
new file mode 100644
@@ -0,0 +1,131 @@
@@ -0,0 +1,131 @@
+# AGENTS.md — OpenVibe.Services
+
+## Project overview
+
+OpenVibe.Services is the network's developer platform and resource control plane, served at
+`https://openvibe.services`. It is one console and API over OpenVibe.Network's projects, apps,
+credentials, grants and quotas; it also owns release metadata, the authority registry and the merged
+resource index, and (step 10) control operations routed to the authority that owns a resource.
+
+- **Entry:** `server/index.js` (`node server/index.js`); Express app in `server/app.js`.
+- **Port / service id:** `127.0.0.1:4930`, id `services`; unit `openvibe-services.service`.
+- **Portal:** `server/http/portal.js` (`/projects`, `/projects/:project/resources`, `/secrets`,
+ `/usage`, `/export`, `/apps/:app`), `server/http/mod.js`, `server/http/tools.js`.
+- **API and machine routes:** `server/http/api.js` (`/api/v1`), `server/http/openapi.js`,
+ `server/http/network-mcp.js` (`/mcp`), `server/http/discovery.js` (robots/sitemap/llms).
+- **Generated docs:** `server/docs/`, `server/http/docs.js`, `server/http/ai-docs.js`,
+ `server/http/money-docs.js` — generated from the pinned contracts, capability catalog, SDK
+ `.d.ts` files and ADRs at boot; never hand-written where it can drift.
+- **Resource index / control:** `server/resources.js`, `server/authorities/index.js`,
+ `server/registry/`, `server/domain/`; SDK `openvibe-sdk/resources` does the fan-out and merge.
+- **Pages:** server-rendered through `openvibe-shared/shell`; the only own scripts are the optional
+ in-browser webhook verifier and a copy button. No SPA, no build step.
+
+## Owns, does not own
+
+Owns: release metadata keyed to Network app ids (validated `services.app-manifest@1` and
+`mods.mod-manifest@1`; releases `draft → published → deprecated → revoked` and an append-only log);
+the merged resource index (`services.resource.read`, read live from every authority, never stored as
+truth); trust tiers (metadata only); playground run logs; the events `services.app.published`,
+`services.app.deprecated`, `services.app.revoked` and `services.moderation.action`; the generated
+reference and the console pages.
+
+Does not own: projects, members, apps, credentials, grants, allowances, quotas or audit — those are
+OpenVibe.Network (`/api/v1/projects`), and Services never copies them. Secrets are Network's:
+Services renders a secret only once, never stores, logs or re-displays it. Identity, billing and
+moderation stay with their services.
+
+## Who calls it, what it calls
+
+Developers and first-party services use the console and the public API; SDK
+`openvibe-sdk/projects` and `openvibe-sdk/resources` are the client surface, and the MCP server at
+`/mcp` exposes the network's read-only tools. Services calls:
+
+- **Network** — SSO (`services`, PKCE S256), JWKS, `/api/v1/projects` with the person's own access
+ token, the registry, client-credentials tokens, and `/internal/confirmations` for sensitive
+ control operations.
+- **Events** — the outbox relay publishes `services.app.*` with Services' service token; playgrounds
+ publish with the app's own token; the project archive reads app events with an export token.
+- **Media** — playground uploads with the app's token; the project archive lists objects and
+ namespaces with an export token.
+- **Authorities** — the registry is built at boot from the pinned `openvibe-contracts` service
+ manifests (every service whose manifest lists `<id>.resource.read`); the loopback origin comes from
+ the manifest, `SERVICES_<ID>_URL` overrides it. No polling loop: each request reads the
+ authorities.
+
+The authority registry is not hand-maintained: turning a capability on in a service's manifest puts
+it in the registry on the next boot.
+
+## Database and migrations
+
+PostgreSQL `ov_services` (ADR-035) via `openvibe-sdk/db`: `DATABASE_URL` (PgBouncer) for the app,
+`DATABASE_DIRECT_URL` for migrations. `server/db.js` applies `migrations/NNNN_*.sql` at boot under an
+advisory lock, then serves with the pooled role; a held migration is logged and does not crash the
+service. Never edit a migration that has run; a new table/column is an expand migration and removal
+waits for a contract migration. Valkey is optional and holds only per-actor limit counters.
+`telemetry_samples` is this service's own table, pruned after 14 days.
+
+## Auth
+
+Network RS256 JWTs (JWKS) plus the SSO session cookie. Service tokens carry `openvibe.services`
+audience and the route's capability (`services.release.read`, `services.release.manage`,
+`services.resource.read`, `services.resource.control`); a person's user token is used only for the
+person's own projects and the pages. The index answers a first-party service token, or one project
+at a time for a person who owns or belongs to it — never unscoped, and a non-member project is the
+same 404 Network uses. `/api/v1/authorities` is first-party loopback data. Sensitive control
+operations require Network's confirmation; until the grant exists they fail with
+`confirmations.unavailable` and nothing is applied. No path sends or accepts a shared loopback key.
+
+## Deploy
+
+`sudo ovhost deploy services` (git-checkout: fetch, fast-forward `/opt/openvibe.services`, install on
+a lockfile change, restart, wait for `/api/ready`; rollback `sudo ovhost rollback services --to <sha>`).
+Unit `deploy/systemd/openvibe-services.service`, env `/etc/openvibe/services.env`, nginx
+`deploy/nginx/openvibe.services.conf`, database `ov_services`. The release migrates at boot. A
+rollback across the console migration serves the old index and the console pages answer 404 until
+the next deploy.
+
+## Commands and tests
+
+```bash
+npm install
+npm run dev # http://localhost:4930
+npm test # PGlite, in-process stub Network/Events/Media
+npm run test:pg # the same on the PostgreSQL containers
+npm test -- index scaffold # only matching files
+npx openvibe-contracts-check --service services --src server
+```
+
+`test/run.js` uses `openvibe-shared/test-runner` (180 s, serial); each file gets a temp database and
+stubs, never the network. The suite covers the crawl artifacts and their `lastmod` source, the
+ADR-013 trust-tier migration, secrets never stored or re-displayed, honest Network errors, the scope
+editor, generated docs matching the pins, v2-only webhook verification, playground grants, usage and
+resource pages, project export/delete, the resource index auth matrix and cursors, readiness/metrics,
+and the home page's size budgets (`test/perf-budget.test.js`).
+
+## Conventions
+
+- CommonJS, 4-space indent, single quotes, semicolons. Server-rendered HTML; escape every value
+ (names, problem details, OVRNs) and keep pages usable without JavaScript.
+- RFC 9457 problem+json for API errors (`openvibe-contracts`); render Network's status/code/detail
+ and request id rather than inventing a friendlier error.
+- Guard routes with `openvibe-contracts` `requireCapability`; do not hand-roll a grant check.
+- Anything generated (docs, OpenAPI, manifests, sitemap/llms) is rendered from the pinned packages
+ and `STATUS.json`, with the versions shown; never patch the output by hand.
+- A release is validated against the pinned contracts before it is stored; publish/deprecate/revoke
+ are state transitions with an event, not direct row edits.
+
+## Key pitfalls
+
+- **Never persist or re-display a secret.** Network returns a client secret once; Services passes it
+ through and drops it. The export archive must contain no secret — tests assert both.
+- **A person's resource read is never unscoped.** `?project=` is required for a user token, Network
+ answers membership, and another project's rows must not appear even if an authority ignores
+ `?owner=`.
+- **Trust tiers are metadata**, not a grant check: changing a tier never widens access.
+- **The merged index is not stored as truth.** Read the authorities per request; a failed authority
+ is named in `stale`, never silently dropped or fatal.
+- **Control operations need the Network confirmation** and the authority's own capability; a gated
+ action without the grant fails closed.
+- **Trust the pinned versions:** docs, manifest validation and compatibility pages all come from
+ `openvibe-contracts`/`openvibe-sdk`; bump the pins instead of copying their content.
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@