openvibers/openvibe.network AGENTS.md
AGENTS.md
| 1 | # AGENTS.md — OpenVibe.Network |
| 2 | |
| 3 | ## Project Overview |
| 4 | |
| 5 | OpenVibe.Network is the identity and account service of the OpenVibe network and its token issuer: accounts, sessions, |
| 6 | anonymous identities, canonical subjects (`usr_…`/`gst_…`), OAuth2/OIDC, refresh tokens, service principals and their |
| 7 | grants, developer projects/apps/quotas, notifications and email, themes and per-service overrides, user modules, |
| 8 | platform blocks, the follow graph, presence, the Dock's state, creator analytics, the OpenCoins wallet, the machines |
| 9 | hub, the Locker key directory, the service registry, the status page and the network changelog proxy. |
| 10 | |
| 11 | - **Domain:** `openvibe.network` · **Port:** 4000 · **Service id:** `network` · **Env prefix:** `NETWORK`/`OV_` |
| 12 | - **Runtime:** Node 22, Express 4, PostgreSQL 18 through `openvibe-sdk/db` (PGlite in development/tests), Valkey 9, |
| 13 | RS256 signing key (`JWT_PRIVATE_KEY` or `data/keys`), email via Resend, optional Discord/GitHub. |
| 14 | - **Read first:** [README.md](README.md), [docs/developer-projects.md](docs/developer-projects.md), |
| 15 | [docs/sign-in.md](docs/sign-in.md), [docs/dock.md](docs/dock.md), [docs/retirement.md](docs/retirement.md), |
| 16 | [docs/security/](docs/security/), [.env.example](.env.example). |
| 17 | |
| 18 | **Network context:** Network is the issuer every other service trusts. It signs `svc:network` tokens for OpenVibe.Events, |
| 19 | OpenVibe.AI (site copy) and OpenVibe.Bot (machines). It does **not** own money (OpenVibe.Billing), a service's data |
| 20 | (the user-module namespace belongs to its owner), the contracts it serves (OpenVibe.Contracts) or the Frame's code |
| 21 | (OpenVibe.Shared). Services call its internal routes only with a capability-scoped service token; the old |
| 22 | deployment-wide internal key was retired on 2026-09-29. |
| 23 | |
| 24 | ## Commands |
| 25 | |
| 26 | ```bash |
| 27 | npm ci # install (contracts/sdk/shared pinned by tag) |
| 28 | npm run dev # NODE_ENV=development node server/index.js, PGlite under data/pglite |
| 29 | npm start # node server/index.js |
| 30 | npm test # every test/*.test.js on temp databases, no network |
| 31 | npm run test:pg # the same through PostgreSQL + PgBouncer |
| 32 | npm test -- oauth # only test files whose name contains the word |
| 33 | npm run reset-db # node server/reset-db.js (development) |
| 34 | npm run grant-admin # node server/grant-admin.js |
| 35 | node --check <file.js> # syntax check (no linter configured) |
| 36 | ``` |
| 37 | |
| 38 | No build step for the server. Public pages are plain HTML/JS under [public/](public/) (`index.html`, `login.html`, |
| 39 | `my.html`, `admin.html`, `wallet.html`, `pay.html`, assets and the brand). Production refuses to boot without |
| 40 | PostgreSQL settings. |
| 41 | |
| 42 | ## Architecture at a Glance |
| 43 | |
| 44 | - **Entry:** [server/index.js](server/index.js) — Express app, CORS, migrations, service/route init and periodic tasks; |
| 45 | `openvibe-network.socket` keeps connections queued across restarts. |
| 46 | - **Config:** [server/config.js](server/config.js) — port 4000, JWT/DB/OAuth/connected services; production |
| 47 | `/etc/openvibe/network.env`. Only variable names appear in code/docs. |
| 48 | - **Auth/OAuth/OIDC:** [server/auth/](server/auth/) — `routes.js` (register, login, profile, anon, multi-account, |
| 49 | follows), `oauth-routes.js` (authorize, token), `oidc.js` (discovery, id_token, userinfo), `refresh-tokens.js` |
| 50 | (SHA-256, rotating families), `session.js`, `fedcm.js`, `passkeys.js`, `mfa.js`, `qr-signin.js`, `sso-*.js`, |
| 51 | `revocation.js`, `reauth.js`, `staff-claims.js`. |
| 52 | - **Identity/grants:** [server/identity/](server/identity/) — `principals.js` (seeded grants), `internal-routes.js`, |
| 53 | `account-data.js`, `emails.js`, `follows.js`, `blocks.js`, `adult.js`, `friends.js`, `grants-admin.js`, |
| 54 | `account-aliases.js`. |
| 55 | - **Developer platform:** [server/developer/](server/developer/) — projects, members, apps, credentials, grants, |
| 56 | quotas, usage, audit; [docs/developer-projects.md](docs/developer-projects.md). |
| 57 | - **Notifications:** [server/notifications/](server/notifications/) + [server/push/](server/push/) — CRUD, |
| 58 | preferences, quiet hours/digests, email queue, cross-service push, action center; [server/actions/](server/actions/). |
| 59 | - **Themes/modules/dock/presence:** [server/themes/](server/themes/); user modules in |
| 60 | [server/identity/modules.js](server/identity/modules.js) and [server/identity/module-events.js](server/identity/module-events.js); |
| 61 | the Dock in [server/dock/](server/dock/), presence in [server/presence/](server/presence/), the home/menu feeds in |
| 62 | [server/home/](server/home/), [server/home-screen/](server/home-screen/), [server/menu-feeds/](server/menu-feeds/). |
| 63 | - **Wallet/money:** [server/coins/](server/coins/), [server/wallet/](server/wallet/), [server/money/](server/money/) — |
| 64 | balance, history, and the idempotent `internal/coins/*` credit/debit/transfer. |
| 65 | - **Machines/Locker/Connect:** [server/registry/machines.js](server/registry/machines.js), |
| 66 | [server/bot/](server/bot/), [server/link/](server/link/), [server/locker/](server/locker/), |
| 67 | [server/connect/](server/connect/), [server/consent/](server/consent/), [server/qr-signin/](server/qr-signin/). |
| 68 | - **Analytics/profile/status/registry:** [server/analytics/](server/analytics/), [server/profile/public.js](server/profile/public.js), |
| 69 | [server/status/](server/status/), [server/registry/](server/registry/), [server/seo/](server/seo/), |
| 70 | [server/operator/](server/operator/), [server/history/](server/history/), [server/levels/](server/levels/). |
| 71 | - **Admin/setup:** [server/admin/routes.js](server/admin/routes.js), [server/admin/setup.js](server/admin/setup.js), |
| 72 | [server/admin/ai-config-proxy.js](server/admin/ai-config-proxy.js); public [public/admin.html](public/admin.html). |
| 73 | - **Database:** [server/db/](server/db/) and [migrations/](migrations/) (73 migrations; every table in the README's |
| 74 | Database Tables section is real): `users`, `oauth_clients`, `oauth_codes`, `refresh_tokens`, `anon_users`, |
| 75 | `user_sessions`, `themes`, `user_theme_prefs`, `site_settings`, `audit_log`, `notifications`, |
| 76 | `notification_preferences`, `follows`, `wallets`, `coin_transactions`, `dev_*`, `analytics_*`, `user_modules*`, |
| 77 | `network_event_outbox`, `user_blocks`, `user_follows`, `locker_keys*`, etc. |
| 78 | - **Deploy:** [deploy/systemd/openvibe-network.service](deploy/systemd/openvibe-network.service) (+ socket/drop-in), |
| 79 | [deploy/scripts/deploy.sh](deploy/scripts/deploy.sh) runs `ovhost deploy network --install-units`; `sudo |
| 80 | /opt/openvibe.network/deploy/scripts/deploy.sh` (fast-forward, install on lockfile change, restart, `/api/ready` |
| 81 | poll, automatic rollback). Database `ov_network`; nginx in [deploy/nginx/](deploy/nginx/). |
| 82 | |
| 83 | ## Ownership and Boundaries |
| 84 | |
| 85 | Network owns accounts, sessions, anonymous identities, canonical subjects and aliases, the OAuth clients/codes, |
| 86 | refresh-token families, signing keys/JWKS, service principals and grants, mod principals, developer projects/apps/ |
| 87 | credentials/grants/quotas/usage/audit, notifications/email, themes/preferences, user modules and their revisions, |
| 88 | platform blocks, the follow graph, creator analytics, presence, Dock state, the OpenCoins wallet/ledger, the machines |
| 89 | hub, the Locker key directory (public keys and wrapped backups, opaque), the registry/status/changelog proxy and the |
| 90 | public profile. It does **not** own money (Billing), streams/channel points (Live), any service's own rows, the |
| 91 | contracts (Contracts) or the Frame (Shared). |
| 92 | |
| 93 | ## Auth and Capabilities |
| 94 | |
| 95 | - Network is the issuer: RS256 tokens signed with its key, JWKS published, `token_valid_after` cutoffs so services |
| 96 | refuse older tokens. Refresh tokens are stored as SHA-256, rotate as a new generation of one `family_id`, and a |
| 97 | reused token (after a 10-second two-tab grace) revokes the family and audits `oauth_refresh_reuse`. |
| 98 | - OIDC: discovery at `/.well-known/openid-configuration` and `/.well-known/oauth-authorization-server`; scope |
| 99 | `openid` adds an RS256 `id_token` (aud = client, nonce echoed, `sub` = Network id, `subject_id` = `usr_…`); |
| 100 | `/oauth/userinfo` returns the same. |
| 101 | - Capabilities (audience `openvibe.network`): `identity.subject.resolve`, `mods.grant.manage`, |
| 102 | `network.account.deletion.confirm`, `network.account.export.contribute`, `network.analytics.creator.read`, |
| 103 | `network.blocks.read`, `network.coins.credit|debit|transfer`, `network.follows.read|write`, |
| 104 | `network.presence.read`, `network.integration.github.read`, `network.modules.read|write`, |
| 105 | `network.notifications.push`, `network.operator.alert`, `network.project.manage|read`, `network.secret.use`, |
| 106 | `network.staff.read`, `network.status.incident`; the hub's `network.machines.read|manage`. Grants other services |
| 107 | hold are seeded in [server/identity/principals.js](server/identity/principals.js). |
| 108 | - CORS: only discovery and the registry answer any origin (`Access-Control-Allow-Origin: *`, preflight included); |
| 109 | every other route keeps the first-party allow-list ([server/public-cors.js](server/public-cors.js)). |
| 110 | - Staff roles are claim-based (`admin`, `global_mod`, …); the admin panel/API gates on them. Never trust a role from a |
| 111 | request body; never accept a typed token where a session is required. |
| 112 | - Redirects after sign-in (`/login?return=`, `/sso/fanout?next=`) never run `javascript:` or leave an owned host; |
| 113 | OAuth redirects must be one of the client's registered URIs. |
| 114 | - Errors are structured; internal routes take a capability-scoped service token only. The shared internal key is |
| 115 | retired — do not reintroduce it. |
| 116 | |
| 117 | ## API and Pages |
| 118 | |
| 119 | Pages: `/` (landing), `/login`, `/my` (account), `/@<username>` (public profile), `/wallet`, `/pay`, `/admin` |
| 120 | (+ `#setup`, `#glossary`), `/admin/review`, `/status`, `/connect`, and the shared Frame/legal pages. |
| 121 | API: `/api/auth/*`, `/oauth/*`, `/.well-known/*`, `/api/v1/{subjects,profiles,registry,projects,machines,presence, |
| 122 | coins,themes,modules,blocks,follows,notifications,dock,agent-keys,...}`, `/internal/*` (capability tokens), |
| 123 | `/contracts/*.json`, `/shared/*`, `/api/health`, `/api/ready`, `/release.json`, `/metrics`. |
| 124 | |
| 125 | ## Conventions |
| 126 | |
| 127 | - CommonJS, 4-space indent, single quotes, semicolons; no linter/formatter. Async routes through |
| 128 | [server/async-routes.js](server/async-routes.js). |
| 129 | - New schema = a new `migrations/NNNN_name.sql`; never edit one that has run. Services' rows are keyed by canonical |
| 130 | `usr_…`; an alias resolves to the canonical subject. |
| 131 | - Session/auth changes are security changes: add a test in the matching security suite, not only the happy path. |
| 132 | - Public responses carry explicit fields; never return a password hash, a refresh token, a client secret or another |
| 133 | person's email. A hidden profile shows only name and picture and is not indexed. |
| 134 | - Analytics keep no personal data beyond a day's salted hash; each day's visitor salts are deleted after the rollup. |
| 135 | - Notification events and the outbox are written in the change's transaction; a replay is one row. |
| 136 | - CSP/first-paint: public pages are server-served HTML; no Font Awesome; phone first; "Calm reduces, only Off |
| 137 | removes". |
| 138 | - Provider secrets come from the environment first and are never printed; only names appear in docs. |
| 139 | - Admin/setup changes: the owner settings proxy writes a key once (fresh sign-in, the service's vault, audit, alert) |
| 140 | and never shows it again. |
| 141 | |
| 142 | ## Key Pitfalls |
| 143 | |
| 144 | - **Refresh-token reuse revokes the family.** Keep the 10-second grace and the hashed storage; never log or return a |
| 145 | refresh token. A raw pre-change row is hashed on first use. |
| 146 | - **Redirects and CORS:** a post-login target must be owned and http(s); a wildcard belongs only to discovery/registry. |
| 147 | Cross-site cookie writes must be refused; a Bearer is not a session. |
| 148 | - **Subjects:** `GET /api/auth/me` returns only the caller's own `subject_id`; an account without one gets it minted |
| 149 | on first call. Other services ask via `identity.subject.resolve`. |
| 150 | - **Account export/deletion (ADR-033):** every row type is in the export, and deletion sweeps every namespace, |
| 151 | including Locker keys, presence, Dock, modules, blocks, follows, analytics and outbox receipts; a redelivery changes |
| 152 | nothing. Account deletion needs re-authentication. |
| 153 | - **Coins:** the wallet ledger is idempotent by key; OpenCoins are loyalty points, never money or Vibes. Never debit |
| 154 | without an idempotency key. |
| 155 | - **Analytics privacy:** a raw event may be kept ≤30 days; a visitor is a day-salted hash only; no IP/user-agent in a |
| 156 | rollup. |
| 157 | - **Machines:** pairing and choosing roles require the person's own session; a token acting for the person may only |
| 158 | read/rename/revoke. Bot is the registry underneath. |
| 159 | - **Deploy blockers:** an older release starts with empty `frame_cache`/`frame_hits` (renamed from `chrome_*`), and |
| 160 | does not know `tool_domains` mirror rows. Record the rollback target with that in mind. |
| 161 | - **Socket activation:** requests wait for the new process; do not replace the socket unit with a plain listen. |
| 162 | |
| 163 | ## Testing |
| 164 | |
| 165 | `npm test` runs the files in [test/](test/) on temp databases with no network. They cover OAuth/PKCE, OIDC discovery, |
| 166 | refresh rotation/reuse, SSO/FedCM, subjects/principals/grants, export/deletion, developer projects/apps/usage, user |
| 167 | modules, blocks, follows, analytics privacy, the registry/exposure, notifications/events, status/incidents, operator |
| 168 | alerts, actor limits, machines, presence, Dock, Locker keys, Connect, consent, QR sign-in, levels, the admin panel and |
| 169 | the security suites (`security-session`, `security-redirects`, `security-secrets`, `security-private`, |
| 170 | `security-idor`, `security-ssrf`), plus the deploy wrapper. `npm run test:pg` for PostgreSQL. Run the narrowest file |
| 171 | first, then the suite; always `node --check` changed files. |
| 172 | |
| 173 | ## Documentation |
| 174 | |
| 175 | - [README.md](README.md) — purpose, what it does, account ID, owns/does-not-own, capabilities, architecture, setup, |
| 176 | deploying, TLS, notifications, Dock, email, provider/project secrets, anonymous users, user modules, blocks, |
| 177 | follows, multi-account, OAuth2, observability, analytics, client libraries, admin panel, connected services, |
| 178 | database tables, acceptance, security, sharing. |
| 179 | - [docs/developer-projects.md](docs/developer-projects.md), [docs/sign-in.md](docs/sign-in.md), [docs/dock.md](docs/dock.md), |
| 180 | [docs/retirement.md](docs/retirement.md), [docs/security/authenticator.md](docs/security/authenticator.md), |
| 181 | [docs/security/passkeys.md](docs/security/passkeys.md), [docs/slo.md](docs/slo.md), |
| 182 | [docs/platform-plan.md](docs/platform-plan.md), [docs/capabilities-proposal/](docs/capabilities-proposal/). |
| 183 | - [SECURITY.md](SECURITY.md), [.env.example](.env.example). |
| 184 |