openvibers/openvibe.host Commits

W22 docs: C-58 and C-59 removed, C-73 step 1 in part; Games and Chat readiness now check their stores; inventory polls Games /api/ready

OP OpenVibers committed 23 Sept 2026 · cfd4401e4f · 3 files, +9 −9


Changes

ddiff --git a/docs/compatibility-register.md b/docs/compatibility-register.md
iindex 6fbcbce..78f5bdd 100644
---- a/docs/compatibility-register.md
++++ b/docs/compatibility-register.md
@@@ -103,8 +103,8 @@ The dates below assume each caller moves by **2026-10-09**. The key paths then g
| C-55 | **Network's outbound key calls**: Live `/internal/user-avatar`, Media `/internal/avatar-ingest`, Live `/internal/analytics-summary`, Live `/internal/ai/site-copy` (C-21), Live `/internal/user-role` (reads `INTERNAL_API_KEY \|\| OV_INTERNAL_KEY`), and admin analytics. | Network `server/profile/avatar.js:66,75`, `server/chrome/service.js:77,138`, `server/admin/routes.js:120,220-224`, `server/admin/analytics-routes.js:16,57` | Network | Move to service tokens with capabilities on the callee. | 2026-10-09 | none needed |
| C-56 | **Media `POST /internal/avatar-ingest`**: key only, loopback, ≥16-character key, otherwise 404. `VIEW_HASH_SECRET` falls back to `MEDIA_SECRET` and then `INTERNAL_API_KEY`. | Media `server/index.js:55-63`, `server/views/service.js:27-33` | Media | *proposed*: a service-token guard on avatar-ingest; set `VIEW_HASH_SECRET` explicitly so removing the key does not change view hashes. | 2026-10-23 | none needed |
| C-57 | **Tools satellites** `/api/internal/analytics` and `/analytics/bots`: key only, alias `OV_INTERNAL_KEY`. `apps/gateway/.env.example:30-31` describes it wrongly. | Tools `apps/_shared/internal-auth.js:11-17` | Tools | *proposed*: the gateway aggregates with a service token, or loopback without a key. | 2026-10-23 | none needed |
--| C-58 | **Network's cross-database read of Live.** On every boot, `syncLinkedLiveRoles` opens Live's database read-only, from `OPENVIBELIVE_DB_PATH`, then `/opt/openvibelive/data/live.db`, then `../live/data/live.db`. This breaks ADR-007 ("no service reads or writes another service's database"). None of the default paths is Live's production path (`/opt/openvibe.live/data/live.db`). Whether it runs in production: **unknown**. Check `/etc/openvibe/network.env` for `OPENVIBELIVE_DB_PATH`. | Network `server/db/database.js:28-99`, called at `:751` | Network | *proposed*: remove the function. Roles already reach Network through `/internal/user-role` and the identity map. | 2026-10-09 | none needed |
--| C-59 | **Hard-coded legacy secret** `X-Internal-Secret: openvibe-internal-2026`. Live's `/api/cosmetics/internal-unlock` accepts it from loopback. Live's `questApi` sends it to `LEGACY_QUEST_API = http://127.0.0.1:3200`, the pre-OpenVibe game. Games now runs on :8000. Whether anything listens on :3200: **unknown**. Check `ss -ltnp` on the host. | Live `server/monetization/cosmetics-routes.js:86-94`, `server/monetization/cosmetics.js:11,222-275` | Live (+ Games for inventory) | *proposed*: remove the literal secret and the :3200 client. If cosmetics still need the game inventory, Games exposes it behind a service token. | 2026-10-09 | none needed |
++| C-58 | **REMOVED 2026-09-23** (Network `6a1acd3`; `test/data-ownership.test.js` keeps it out). **Network's cross-database read of Live.** On every boot, `syncLinkedLiveRoles` opens Live's database read-only, from `OPENVIBELIVE_DB_PATH`, then `/opt/openvibelive/data/live.db`, then `../live/data/live.db`. This breaks ADR-007 ("no service reads or writes another service's database"). None of the default paths is Live's production path (`/opt/openvibe.live/data/live.db`). Whether it runs in production: **unknown**. Check `/etc/openvibe/network.env` for `OPENVIBELIVE_DB_PATH`. | Network `server/db/database.js:28-99`, called at `:751` | Network | *proposed*: remove the function. Roles already reach Network through `/internal/user-role` and the identity map. | 2026-10-09 | none needed |
++| C-59 | **REMOVED 2026-09-23** (Live `e505868`: nothing listened on :3200; `internal-unlock` takes only `INTERNAL_API_KEY`, the bridge is off unless `LEGACY_QUEST_API_URL` is set). **Hard-coded legacy secret** `X-Internal-Secret: openvibe-internal-2026`. Live's `/api/cosmetics/internal-unlock` accepts it from loopback. Live's `questApi` sends it to `LEGACY_QUEST_API = http://127.0.0.1:3200`, the pre-OpenVibe game. Games now runs on :8000. Whether anything listens on :3200: **unknown**. Check `ss -ltnp` on the host. | Live `server/monetization/cosmetics-routes.js:86-94`, `server/monetization/cosmetics.js:11,222-275` | Live (+ Games for inventory) | *proposed*: remove the literal secret and the :3200 client. If cosmetics still need the game inventory, Games exposes it behind a service token. | 2026-10-09 | none needed |
## Events delivery signatures
@@@ -120,7 +120,7 @@ The dates below assume each caller moves by **2026-10-09**. The key paths then g
| C-70 | **Live `MEDIA_API_KEY`**, the per-app bearer key that Live sends to Media, with `X-OV-User-Id`. **Live has no service-token path to Media.** Media already accepts principal tokens for audience `openvibe.media` (Media `server/auth.js:221-304`). Community, Tools, Games, Wiki and Blog are token-only tenants (`:162-173`). | Live `server/media-client.js:17-51`; Media `server/auth.js:353-396` | Live + Media | *proposed*: Live's media-client moves to a service token; then remove the `live` app key hash from `apps`. **Media has no key-vs-token counter.** Add one before the 14-day wait. | 2026-11-06 | The key keeps working. |
| C-71 | **`MEDIA_APP_KEYS` short form** and `MEDIA_APPS_SEED` key seeding. | Media `.env.example:37-38`, `server/auth.js:370-392` | Media | With C-70 and C-42: when no tenant uses a key. | 2026-11-06 | none needed |
| C-72 | **ADR-021 backups that still hold IPs**: `/opt/openvibe.live/data/backups/analytics-pre-adr021-2026-09-23.db`, `/opt/openvibe.tools/backups/analytics-2026-09-23`, Network `data/backups/network.db.pre-adr021-20260923T1634` (operator notes, not in a repo). | host | Live, Tools, Network | *documented* (operator notes): delete after 2026-10-07. | 2026-10-07 | They are the only way back to pre-ADR-021 analytics. |
--| C-73 | **Live's legacy `vods`/`clips`/`pastes` tables**. They were frozen for the Media cutover, and the canonical copies are now in Media and Community. **There is no tripwire; only comments** (`server/db/database.js:3223-3224`). One write path remains: `PUT /api/admin/pastes/:id/nsfw` (`server/admin/routes.js:427-434`). Exported write helpers with no callers remain: `updatePasteAi`, `updateVodHealth`, `repairVodDuration`, `updatePaste`, `deletePaste`, `likePaste`. Live still **reads** them for live pages: `server/ai/chat-ai-routes.js:337`, `server/admin/routes.js:91-92`, `server/arena/arena-service.js:436`, `server/streaming/routes.js:1547`, and about 70 queries in `database.js`. These reads serve pre-cutover data. | Live (above); `docs/vods-and-clips.md:3-5` | Live | **No removal plan in the repo.** *Proposed*: (1) remove the nsfw write and the dead helpers, and add a tripwire like `assertLiveLedger`, by 2026-10-09; (2) move the four readers to Media or Community; (3) back up and drop the tables after 30 days with no reader. | 2026-11-06 | The tables are the only local copy of pre-Media rows. Keep the backup. |
++| C-73 | *Step 1 in part, 2026-09-23: the nsfw write route is gone (Live `e505868`); dead helpers and the tripwire remain.* **Live's legacy `vods`/`clips`/`pastes` tables**. They were frozen for the Media cutover, and the canonical copies are now in Media and Community. **There is no tripwire; only comments** (`server/db/database.js:3223-3224`). One write path remains: `PUT /api/admin/pastes/:id/nsfw` (`server/admin/routes.js:427-434`). Exported write helpers with no callers remain: `updatePasteAi`, `updateVodHealth`, `repairVodDuration`, `updatePaste`, `deletePaste`, `likePaste`. Live still **reads** them for live pages: `server/ai/chat-ai-routes.js:337`, `server/admin/routes.js:91-92`, `server/arena/arena-service.js:436`, `server/streaming/routes.js:1547`, and about 70 queries in `database.js`. These reads serve pre-cutover data. | Live (above); `docs/vods-and-clips.md:3-5` | Live | **No removal plan in the repo.** *Proposed*: (1) remove the nsfw write and the dead helpers, and add a tripwire like `assertLiveLedger`, by 2026-10-09; (2) move the four readers to Media or Community; (3) back up and drop the tables after 30 days with no reader. | 2026-11-06 | The tables are the only local copy of pre-Media rows. Keep the backup. |
| C-74 | **Pre-Media local paths** `VOD_PATH`, `CLIPS_PATH`, `COLD_STORAGE_PATH`, `THUMBNAILS_PATH` are still read by Live's config. | Live `server/config.js:251-257` | Live | *proposed*: no code reads the directories. **Unknown** whether any does. Grep for each config key. | 2026-11-06 | none needed |
| C-75 | **Media dual write**: the old tables are written first, then synced into `media_objects`. A sync failure only logs. Old routes also stay: `/v/:id` basename fallback, `/api/thumbnails/:name` with app `live` hard-coded, `/f/screenshots/:name`, and `/tos` → `/terms`. Webhooks to Live are published alongside Events. | Media `docs/object-model.md:3-7,88-112`, `server/db/database.js:100-124`, `server/public/routes.js:176-190,346-384`, `server/events.js:1-18` | Media | *documented*: "The object layer is additive; old routes keep working throughout" (ADR-006 §Rollback). Webhooks stay "until every consumer reads events" (`server/events.js`). *Proposed*: Live consumes `media.*` events instead of `/internal/media-webhook`, then remove the webhooks. | 2026-12-18 | Old routes and tables. |
| C-76 | **Live legacy (in-place) deploy layout**, "current production" (Live `docs/deploy.md:31-32`). A rollback resets the checkout but not `node_modules`. `--rollback` is refused in this layout. | Live `deploy/scripts/deploy.sh:347,394-419`, `deploy/systemd/openvibe-live.service` | Live | *documented*: the one-time `deploy/scripts/migrate-to-releases.sh` (one restart). | 2026-10-09 | `git reset` to the old commit. |
ddiff --git a/docs/storage-state.md b/docs/storage-state.md
iindex c74a839..88210db 100644
---- a/docs/storage-state.md
++++ b/docs/storage-state.md
@@@ -34,9 +34,9 @@ Sources:
## Findings
1. **Readiness that does not check the store.**
-- - **Games** has no `/api/ready`. `/healthz` returns `{ ok: true, tick }` without touching `world.db`
++ - *(Fixed 2026-09-23, Games `f11e21c`: `/api/ready` checks `world.db` and the tick; host-only.)* **Games** had no `/api/ready`. `/healthz` returns `{ ok: true, tick }` without touching `world.db`
(`apps/server/src/net/httpServer.ts:596-600`).
-- - **Chat** `/ready` is 200 as soon as `ctx.stats.lastSyncAt` is set (`server/app.js:77-86`). That
++ - *(Fixed 2026-09-23, Chat `45f2b1f`: required `db` read of `chat_messages`, optional `live_sync` degraded after 60 s without a clean pass.)* **Chat** `/ready` was 200 as soon as `ctx.stats.lastSyncAt` was set (`server/app.js:77-86`). That
timestamp is set even when every Live sync step fails, because `tick()` and `sync()` catch each
step's error and set it anyway (`server/live-context.js:665-683`). The only database access is the
`mirror.pending` count (`SELECT COUNT(*) FROM live_mirror_outbox`, `server/bridge/live-mirror.js:34`,
@@@ -56,7 +56,7 @@ Sources:
- Media `/healthz`, which is liveness only, although Media's `/api/ready` checks the database and
storage;
- Tools gateway `/api/health`;
-- - Games `/healthz`.
++ - Games `/healthz` (the inventory now uses `/api/ready`, 2026-09-23; Network, Media and Tools too).
The inventory should point at `/api/ready` where one exists. Games has none.
4. **Two services have no ovhost inventory entry, so no `ovhost backup` and no drill**: AI (`ai.db`) and
@@@ -95,10 +95,10 @@ log win.
| **Live** | `/opt/openvibe.live/data/live.db` (legacy in-place layout, "current production", Live `docs/deploy.md:31`); `DB_PATH`, default `./data/live.db` (`server/config.js:228`). Also `data/rs-companion.db`, and `data/analytics.db`, whose path is hard-coded with no env (`server/index.js:361-363`). | Yes: streams, channels, follows, Live accounts, restreams, **money** (`BILLING_AUTHORITY` unset = `live`), AI state (`vod_ai_state`/`clip_ai_state`), analytics. **Not canonical:** the 12 chat mirror tables (Chat is, since 02:03 UTC) and the frozen `vods`/`clips`/`pastes` (Media and Community are). | SQLite, the reviewed state. No PostgreSQL plan. Money moves to Billing, not to PostgreSQL. | `/api/ready` (`server/index.js:748-756`, `server/web/observability.js:79-106`). Required: `boot`, `db` (`SELECT 1` on `live.db`). Optional: `sfu`, `media` (`MEDIA_URL/healthz`, cached 30 s), `network_key` (PEM loaded, no call). | **Yes** for `live.db`; not `analytics.db` or `rs-companion.db` | `ovhost backup` covers `live.db` and `rs-companion.db`. `analytics.db` is left out on purpose (`host.example.json` `_databasesNote`). Drill **unsupported**: `analytics.db` is opened relative to the checkout, RTMP always binds, and restreams, relays and AI jobs resume from the copy. |
| **Media** | `/opt/openvibe.media/data/media.db`; `DB_PATH`, default `./data/media.db` (`server/config.js:24`). Local `VOD_PATH`, `CLIPS_PATH`, `PASTES_PATH`, `THUMBNAILS_PATH`, `FILES_PATH`, `OBJECTS_PATH` (`config.js:27-58`), plus B2 and R2 object storage. | Yes: media objects, VODs, clips, files, thumbnails, avatars. Paste writes are frozen (Community is canonical). Media still serves paste screenshot bytes. | SQLite, the reviewed state. No PostgreSQL plan. | `/api/ready` (`server/observability.js:72-107`). Required: `db` (`SELECT COUNT(*) FROM apps`), and `storage_{vods,clips,pastes,thumbnails,files,objects}` (writes and unlinks a probe). Optional: `network_jwks`, `remote_b2`/`remote_r2` (HeadBucket, cached 60 s), `events_outbox` (>1000 pending). Also reports `recordings_in_progress`. | **Yes** (database and disk; object storage optional) | `ovhost backup` covers `media.db`. Drill **unsupported**: webhooks to Live come from the `apps` table, and ffmpeg jobs run against production paths. ovhost's deploy readiness polls `/healthz` (finding 3). |
| **Tools** | The gateway has no database. Each satellite has `apps/<app>/data/analytics.db` (relative to `/opt/openvibe.tools`). img, audio and docs also have `data/jobs.db` (`apps/_shared/jobs/index.js:77`), `data/uploads` and `data/output`; yt has `data/downloads`. Job results go to Media when `TOOLS_JOB_RESULTS=media` (default `local`, `apps/_shared/jobs/index.js:32-36`). | Yes: jobs (img, audio, docs) and per-app analytics | SQLite, the reviewed state. One file per process, so ADR-007's "concurrent writers across processes" trigger does not apply. | `/api/ready` on every unit (`apps/_shared/observe.js:71-162`). Gateway: `catalog` required; `network_key`, `service_directory`, `community`, and each `satellite_<name>` optional (`apps/gateway/server/index.js:60-72`). img, docs, audio: `jobs_db` (`SELECT COUNT(*) FROM tool_jobs`), `job_runtime` and data directories required. yt: `downloads_dir`. food: `maps` upstream. maps, text: none required (`analytics_db` optional). | **Partly**: `jobs.db` yes on its satellites; analytics optional by design; the gateway reports satellites as optional (finding 2) | `ovhost backup` covers the seven `analytics.db` files only; **`jobs.db` is missing** (finding 5). Drill **unsupported**: eight units, relative `analytics.db` paths, job runtimes re-queue at start. |
--| **Games** | `/opt/openvibe.games/data/world.db`; `DB_PATH`, default `data/world.db` (`apps/server/src/config.ts:79`). No `.env.example`. | Yes: world, accounts, inventory | SQLite, the reviewed state. No PostgreSQL plan. | **No `/api/ready`.** `/healthz` returns `{ ok: true, tick }` (`apps/server/src/net/httpServer.ts:596-600`). | **No** | `ovhost backup` covers `world.db`. Drill **unsupported** (runtime side effects not reviewed). Games is `managed: false` in the inventory. |
++| **Games** | `/opt/openvibe.games/data/world.db`; `DB_PATH`, default `data/world.db` (`apps/server/src/config.ts:79`). No `.env.example`. | Yes: world, accounts, inventory | SQLite, the reviewed state. No PostgreSQL plan. | `/api/ready` (since `f11e21c`): required `db` (`meta.schema_version` in `world.db`) and `tick` (last step < 5 s ago); 404 on the public vhosts. `/healthz` unchanged. | **Yes** | `ovhost backup` covers `world.db`. Drill **unsupported** (runtime side effects not reviewed). Games is `managed: false` in the inventory. |
| **Community** | `/var/lib/openvibe-community/community.db` (`StateDirectory`, `deploy/systemd/openvibe-community.service:26-27`). `/opt/openvibe.community/data/community.db` is the legacy location, and the inventory keeps whichever of the two exists. `COMMUNITY_DB_PATH`, default `./data/community.db` (`server/config.js:56`). | Yes: pastes (`PASTES_AUTHORITY=community` since 2026-09-22), comments, spaces, threads, Pulse | SQLite, the reviewed state. | `/api/ready` (`server/observability.js:29-66`). Required: `db` (`SELECT COUNT(*) FROM sqlite_master WHERE type='table'`, fails on 0). Optional: `network_jwks`, `media` (community mode) or `live` (live mode). Reports `pastes_authority`. | **Yes** | Drill passed 03:44 (manual) and 05:22. |
| **Events** | `/var/lib/openvibe-events/events.db` (`deploy/systemd/openvibe-events.service:30-31`); `EVENTS_DB_PATH`, default `./data/events.db` (`server/config.js:67`). | Yes: the durable event store, subscriptions, deliveries, DLQ | SQLite, the reviewed state. `README.md:163`: "the plan's PostgreSQL + Redis fanout is a later step". No dated plan. | `/api/ready` (`server/app.js:41-66`). Required: `db` (`store.ping()` = `SELECT 1`), `network_jwks`, `delivery_worker` (unless `EVENTS_WORKER=off`). Optional: `dlq` (degraded at more than `EVENTS_DLQ_DEGRADED_AT`, default 100). | **Yes** | Drill passed 05:22. |
--| **Chat** | `/var/lib/openvibe-chat/chat.db` (`deploy/systemd/openvibe-chat.service:38-39`); `CHAT_DB_PATH`, default `./data/chat.db` (`server/config.js:34`). It also reads Live's `/opt/openvibe.live/data/sounds` (`:43`). | Yes, since 2026-09-23 02:03 UTC: the 12 chat tables (`table_authority = 'chat'`). Six staged tables are still Live's. | SQLite, the reviewed state. | `/ready` (loopback, `server/app.js:77-86`, not openvibe-shared). **503 only while `lastSyncAt` is unset.** It reports `live.failures`/`last_error`, `mirror.pending` and `events.enabled`, and ignores them for the status. | **No** (finding 1) | Drill passed 05:22. |
++| **Chat** | `/var/lib/openvibe-chat/chat.db` (`deploy/systemd/openvibe-chat.service:38-39`); `CHAT_DB_PATH`, default `./data/chat.db` (`server/config.js:34`). It also reads Live's `/opt/openvibe.live/data/sounds` (`:43`). | Yes, since 2026-09-23 02:03 UTC: the 12 chat tables (`table_authority = 'chat'`). Six staged tables are still Live's. | SQLite, the reviewed state. | `/ready` (loopback). Since `45f2b1f`: required `db` (`SELECT MAX(id) FROM chat_messages`, 503 on failure); optional `live_sync` (degraded when no clean Live sync pass for `LIVE_SYNC_STALE_MS`, 60 s, or one step is late). `mirror.pending` is still reported only. | **Yes** | Drill passed 05:22. |
| **OpenRe.Stream** | `/var/lib/openre/openre.db` (all four units); default `./data/openre.db`. | Stream definitions, sessions and destinations for switched slots. **No slot is switched**, so Live is still the ingest authority for every stream. | SQLite, the reviewed state. `README.md:66` names PostgreSQL only as a future ADR-007 trigger. | `/api/ready` (`server/app.js:31-51`, own handler). Required: `db` (`SELECT 1`), `key` (Network public key). Reported but not required: workers, coordinator lease, store, events. Not checked: RTMP reachability, Media, Events. | **Yes** | **Not in the ovhost inventory: no `ovhost backup`, no drill** (finding 4). |
| **Billing** | `/var/lib/openvibe-billing/billing.db` (`deploy/systemd/openvibe-billing.service:23-25`, `StateDirectoryMode=0700`); `BILLING_DB_PATH`, default `./data/billing.db` (`server/config.js:62`). The journal is append-only (triggers refuse UPDATE and DELETE, `README.md:45-50`). | **Not yet.** It is a shadow: `STATUS.json` `"authoritative": false`. Live holds the money until the cutover. | SQLite, the reviewed state. ADR-007's SERIALIZABLE and row-lock rule applies when money moves to PostgreSQL. No plan yet. | `/api/ready` (`server/app.js:54-60`, own handler). `SELECT 1 FROM settings WHERE id = 1` and Network public key loaded, otherwise 503 `service.not_ready`. `/api/health` reports frozen state and providers. | **Yes** | Drill passed 05:22. |
| **Tips** | `/var/lib/openvibe-tips/tips.db` (`deploy/systemd/openvibe-tips.service:25`); `TIPS_DB_PATH` (`server/config.js:31`). | Yes: creator tip profiles, interactions, goals | SQLite, the reviewed state. | `/api/ready` (`server/app.js:74-79`, own handler): `SELECT 1 FROM settings WHERE id = 1` and key loaded. | **Yes** | Drill passed 05:23. |
ddiff --git a/host.example.json b/host.example.json
iindex 8efe602..1f0c269 100644
---- a/host.example.json
++++ b/host.example.json
@@@ -200,7 +200,7 @@
"units": ["openvibe-games.service"],
"envFile": "/etc/openvibe/games.env",
"port": 8000,
-- "ready": { "url": "http://127.0.0.1:8000/healthz", "timeoutSeconds": 60 },
++ "ready": { "url": "http://127.0.0.1:8000/api/ready", "timeoutSeconds": 60 },
"databases": [{ "name": "world", "path": "/opt/openvibe.games/data/world.db" }],
"drill": {
"supported": false,