openvibers/openvibe.host Commits

Compatibility register: every entry re-checked against today's code (31 closed, 18 open) (#65)

OP OpenVibers committed 11 Oct 2026 · 9108c6cce4 · 1 file, +46 −47

Plan §5, the deletion ledger's last item: the compatibility register should end up empty. It still described the estate of 2026-09-23. Every entry was re-checked against today's `main` of every repository (the code, not CHANGELOGs or comments that record a removal), and each row now opens with its status and evidence.

**Closed (31):** C-02–C-04, C-10, C-11, C-21–C-23, C-50–C-61, C-70, C-72, C-73, C-74, C-76, C-77, C-80–C-84. Among them:
- the chat read mirror and bridge outbox;
- the Live/Media paste authority switches;
- AI's namespace fallbacks;
- every `X-Internal-Key` reader;
- v1-only delivery signatures;
- Live's Media app key and the pre-Media paths;
- the in-place deploy layout;
- the P2P fallback;
- Shared's old analytics tracker;
- the Sites placeholders;
- the Live media webhook and the deploy-notice bridge.

C-72 keeps its earlier claim (host backups deleted), which source cannot verify.

**Open (18), each with the file:line that still implements it:**
- C-01 `CHAT_AUTHORITY`; C-05 the legacy `token` cookie; C-06 the REST send route and `?scope=stream`;
- C-12 the old paste redirects; C-13 Tools' paste forwarding; C-14 numeric service thread ids; C-15 `legacy_id_map`;
- C-20 `AI_SERVICE`; C-24 legacy MediaRef ids;
- C-30–C-32 the money switch, freeze and provider webhooks;
- C-40–C-42 the ingest authority, the second RTMP port and Media key auth;
- C-71 Media app-key seeding; C-75 Media's legacy-row pairing; C-85 `GOLIVE_NOTIFY`.

The summary table counts both. Docs only.

Changes

ddiff --git a/docs/compatibility-register.md b/docs/compatibility-register.md
iindex 823ce41..169c322 100644
---- a/docs/compatibility-register.md
++++ b/docs/compatibility-register.md
@@@ -25,66 +25,65 @@ Contracts has no deprecated contracts and no aliases (`OpenVibe.Contracts/compat
## Summary
--| Status on 2026-09-23 | Entries |
--|---|---|
--| Cut over; the shim is now only a rollback lever | C-01–C-03 (chat), C-10–C-13 (pastes), C-20–C-21 (AI) |
--| Prepared; the old path is still authoritative | C-30–C-31 (Billing), C-40–C-42 (OpenRestream) |
--| Running in parallel, waiting on telemetry | C-60–C-61 (Events v1 signatures) |
--| Legacy secret or credential still accepted | C-42, C-70, C-71 (Media app keys) |
--| Legacy data kept for rollback | C-73 (frozen Live tables); C-72 closed 2026-10-10 |
--| No removal condition in any repo before this register (proposed here) | C-06, C-14, C-24, C-70, C-73, C-74, C-77, C-80 |
--| Closed; never built, nothing to migrate | C-82 (Realtime, ADR-005; archived 2026-09-25) |
++| Status as checked 2026-10-11 | Count | Entries |
++|---|---:|---|
++| Closed | 31 | C-02, C-03, C-04, C-10, C-11, C-21, C-22, C-23, C-50, C-51, C-52, C-53, C-54, C-55, C-56, C-57, C-58, C-59, C-60, C-61, C-70, C-72, C-73, C-74, C-76, C-77, C-80, C-81, C-82, C-83, C-84 |
++| Open | 18 | C-01, C-05, C-06, C-12, C-13, C-14, C-15, C-20, C-24, C-30, C-31, C-32, C-40, C-41, C-42, C-71, C-75, C-85 |
++
++C-72 retains its earlier host-backup closure claim; source checkouts cannot verify deleted host backups. Number gaps have no entries in this register.
## Chat (Wave 6: cut over 2026-09-23 02:03 UTC)
| # | What | Where | Owner | Removal condition | Target | Rollback lever until then |
|---|---|---|---|---|---|---|
--| C-01 | **`CHAT_AUTHORITY`**, Live's switch. Unset: Live runs chat itself. `chat`: `/api/chat`, `/api/dm`, `/api/tts`, `/api/sounds` answer 503, `/ws/chat` is refused, and Live's remaining chat writers forward to Chat. Production: `chat`. | Live `server/chat/chat-authority.js:19-21`, `server/chat/chat-remote.js`, `server/index.js:644-691` | Live | *documented*: the Live read mirror "retires in a later wave" (ADR-010 §Migration consequences; Chat `docs/cutover.md:5-6`). *Proposed*: every Live reader of the mirror tables reads Chat's API instead, and C-02/C-03 have both drained with no rollback for 30 days. | 2026-11-06 | Chat `docs/cutover.md:169-184`: remove the nginx include, drain the mirror, unset `CHAT_AUTHORITY`, restart Live. |
--| C-02 | **Live chat read mirror.** Chat pushes 12 tables into Live's database: `chat_messages`, `dm_*` (5 tables), `tts_voice_overrides`, `channel_sounds`, `relay_users`, `hidden_relay_users`, `pending_ip_messages`, `stream_first_chats`, `moderation_actions`. Chat's `LIVE_MIRROR=1` (default 0) drives it through `live_mirror_outbox`. It arrives at Live's `POST /internal/chat-effects/mirror`, capability `live.chat_mirror.write`. | Chat `server/config.js:54`, `server/bridge/live-mirror.js`, `server/db/database.js:25-38`; Live `server/chat/live-context-routes.js:599-659` | Chat (sender), Live (tables) | Same as C-01. Then set `LIVE_MIRROR=0`, drop the mirror tables from Live after a backup, and remove the route. | 2026-11-06 | The mirror is what makes C-01's rollback lossless. `scripts/mirror-flush.js` drains it if Chat is down (Chat `docs/cutover.md:175-179`). |
--| C-03 | **`chat_bridge_outbox`**: Live's forwarded writes to Chat. It is replayed on boot. With `CHAT_AUTHORITY` unset, `drainToLocal()` applies unacknowledged rows to Live's own tables. | Live `server/chat/chat-remote.js:93-122, 237-248, 351-372`, `server/index.js:1074-1075` | Live | *proposed*: Live's remaining chat writers call Chat directly, and the outbox has been empty for 14 days. | 2026-11-06 | `drainToLocal()` on rollback. The code warns that a write Chat applied but never acknowledged gets applied twice (`chat-remote.js:354`). |
--| C-04 | **Staged tables still owned by Live** (`table_authority = 'live'`): `channel_moderators`, `channel_moderation_settings`, `emotes`, `user_tags`, `chat_ai_summaries`, `chat_timeline_events`. | Chat `server/db/database.js:39-47,116-118`; Chat `docs/cutover.md:43`; ADR-010 :17-19 | Chat + Live | *documented*: they move "when their Live writers move" (ADR-010). | 2026-12-18 | They are not moved yet, so Live stays their writer. |
--| C-05 | **Legacy chat auth**: the `token` cookie ("legacy openvibelive") next to `ov_token`, plus a `?token=` query fallback on the WebSocket. | Chat `server/auth/auth.js:43-47,62-63,86-87` | Chat | *proposed*: the Live SPA sends only `ov_token`. Needs a counter of cookie and query use, which does not exist yet. | 2026-10-23 | Accepting both keeps old tabs working. |
--| C-06 | **REST send fallback** `POST /api/chat/send`, and the legacy `?scope=stream`. | Chat `server/chat/routes.js:7,227,490` | Chat | *proposed*: no client sends them for 14 days. Unknown whether any does: check Chat's access log. | 2026-11-06 | none needed |
++| C-01 | **Open (2026-10-11):** `CHAT_AUTHORITY` still gates Chat in Live `server/chat/chat-authority.js:23-24`; the local chat server is gone. **`CHAT_AUTHORITY`**, Live's switch. Unset: Live runs chat itself. `chat`: `/api/chat`, `/api/dm`, `/api/tts`, `/api/sounds` answer 503, `/ws/chat` is refused, and Live's remaining chat writers forward to Chat. Production: `chat`. | Live `server/chat/chat-authority.js:19-21`, `server/chat/chat-remote.js`, `server/index.js:644-691` | Live | *documented*: the Live read mirror "retires in a later wave" (ADR-010 §Migration consequences; Chat `docs/cutover.md:5-6`). *Proposed*: every Live reader of the mirror tables reads Chat's API instead, and C-02/C-03 have both drained with no rollback for 30 days. | 2026-11-06 | Chat `docs/cutover.md:169-184`: remove the nginx include, drain the mirror, unset `CHAT_AUTHORITY`, restart Live. |
++| C-02 | **Closed (verified 2026-10-11):** Chat has no `LIVE_MIRROR` sender and Live `server/chat/chat-reads.js:3-9` reads Chat directly after its mirror tables were dropped. **Live chat read mirror.** Chat pushes 12 tables into Live's database: `chat_messages`, `dm_*` (5 tables), `tts_voice_overrides`, `channel_sounds`, `relay_users`, `hidden_relay_users`, `pending_ip_messages`, `stream_first_chats`, `moderation_actions`. Chat's `LIVE_MIRROR=1` (default 0) drives it through `live_mirror_outbox`. It arrives at Live's `POST /internal/chat-effects/mirror`, capability `live.chat_mirror.write`. | Chat `server/config.js:54`, `server/bridge/live-mirror.js`, `server/db/database.js:25-38`; Live `server/chat/live-context-routes.js:599-659` | Chat (sender), Live (tables) | Same as C-01. Then set `LIVE_MIRROR=0`, drop the mirror tables from Live after a backup, and remove the route. | 2026-11-06 | The mirror is what makes C-01's rollback lossless. `scripts/mirror-flush.js` drains it if Chat is down (Chat `docs/cutover.md:175-179`). |
++| C-03 | **Closed (verified 2026-10-11):** Live `server/chat/chat-delivery.js:5-8` says the ordered bridge and outbox are gone; `migrations/0002_live.sql:13` excludes `chat_bridge_outbox`. **`chat_bridge_outbox`**: Live's forwarded writes to Chat. It is replayed on boot. With `CHAT_AUTHORITY` unset, `drainToLocal()` applies unacknowledged rows to Live's own tables. | Live `server/chat/chat-remote.js:93-122, 237-248, 351-372`, `server/index.js:1074-1075` | Live | *proposed*: Live's remaining chat writers call Chat directly, and the outbox has been empty for 14 days. | 2026-11-06 | `drainToLocal()` on rollback. The code warns that a write Chat applied but never acknowledged gets applied twice (`chat-remote.js:354`). |
++| C-04 | **Closed (verified 2026-10-11):** Chat writes the formerly staged `channel_moderators` in `server/db/database.js:1011` and `chat_ai_summaries` at :1219; Live no longer owns them. **Staged tables still owned by Live** (`table_authority = 'live'`): `channel_moderators`, `channel_moderation_settings`, `emotes`, `user_tags`, `chat_ai_summaries`, `chat_timeline_events`. | Chat `server/db/database.js:39-47,116-118`; Chat `docs/cutover.md:43`; ADR-010 :17-19 | Chat + Live | *documented*: they move "when their Live writers move" (ADR-010). | 2026-12-18 | They are not moved yet, so Live stays their writer. |
++| C-05 | **Open (2026-10-11):** Chat still accepts the legacy `token` cookie in `server/auth/auth.js:51,68`; WebSocket `?token=` authentication is gone at :89-93. **Legacy chat auth**: the `token` cookie ("legacy openvibelive") next to `ov_token`, plus a `?token=` query fallback on the WebSocket. | Chat `server/auth/auth.js:43-47,62-63,86-87` | Chat | *proposed*: the Live SPA sends only `ov_token`. Needs a counter of cookie and query use, which does not exist yet. | 2026-10-23 | Accepting both keeps old tabs working. |
++| C-06 | **Open (2026-10-11):** Chat still handles `POST /api/chat/send` at `server/chat/routes.js:283` and `?scope=stream` at :554-566. **REST send fallback** `POST /api/chat/send`, and the legacy `?scope=stream`. | Chat `server/chat/routes.js:7,227,490` | Chat | *proposed*: no client sends them for 14 days. Unknown whether any does: check Chat's access log. | 2026-11-06 | none needed |
## Pastes (Wave 5: Community is the authority since 2026-09-22 ~23:35 UTC)
| # | What | Where | Owner | Removal condition | Target | Rollback lever until then |
|---|---|---|---|---|---|---|
--| C-10 | **Community `PASTES_AUTHORITY`**. The code default is `live`, which proxies to Live and Media. Production is `community` (set in `/etc/openvibe/community.env`). | Community `server/config.js:51-53`, `server/app.js:127-151`, `README.md:16-21` | Community | *documented*: the old Media paste code and Live's Media paste router "stay until usage telemetry says they're dead" (operator notes, Wave 5). *Proposed*: Media's 410 on paste writes (C-12) and Live's Media path (C-11) are both unused for 30 days. Then make `community` the only mode. | 2026-11-06 | ADR-011 §Rollback: set `PASTES_AUTHORITY=live` in Community and Live, remove Media's two flags, restart. Export Community-side writes first. **No Community→Media export tool exists.** |
--| C-11 | **Live `PASTES_AUTHORITY=community`** plus the Media paste router it replaces. Unset sends `/api/pastes` to Media. The value is read on every request. `PASTES_ON_COMMUNITY=1` 301s `/p/:slug` and `/pastes` to Community. `PASTES_AUTHORITY` and `OV_COMMUNITY_INTERNAL_URL` are missing from `.env.example`; `PASTES_ON_COMMUNITY=0` is at `.env.example:130-132`. | Live `server/pastes-client.js:5-18`, `server/media-proxy/pastes.js:250-319`, `server/index.js:597-612` | Live | Same as C-10. | 2026-11-06 | Unset `PASTES_AUTHORITY` (no restart needed). `PASTES_ON_COMMUNITY=0` needs a restart, and browsers may have cached the 301s. |
--| C-12 | **Media paste writes frozen**: `PASTES_FROZEN_APPS=live` makes every paste write answer 410 `pastes.moved`. `PASTES_MOVED_TO=https://openvibe.community` 301s `/p/:slug` and `/p/:slug/raw`. Screenshot bytes are still served by Media. | Media `server/pastes/routes.js:47-55`, `server/public/routes.js:446-494` | Media | *proposed*: the 410 path is unused for 30 days. The 301s stay as permanent redirects, because old links exist. Delete the paste write code, keep the redirect. | 2026-11-06 (write code); redirects kept | Remove both flags (ADR-011 §Rollback). |
--| C-13 | **Tools paste forwarding**: `/api/pastes/*` goes to Community (`OV_COMMUNITY_INTERNAL_URL`) with the visitor's `ov_token` and no service token. `pastes.openvibe.tools` redirects to Community. **There is no flag.** Commit `fd5d318` (2026-09-22) replaced the Live proxy, and only a revert rolls it back. | Tools `apps/gateway/server/index.js:183-207, 296-309` | Tools | *proposed*: keep the forwarding as the product path, and remove the stale comments that say Media backs pastes (`apps/gateway/server/index.js:8,12`, `apps/gateway/README.md:17-59`). `LIVE_URL` is dead apart from the startup banner (`apps/gateway/server/config.js:35-39`). Remove it. | 2026-10-09 | `git revert fd5d318` in Tools, then deploy. |
--| C-14 | **Community numeric thread ids for services.** A `/^\d{1,15}$/` id resolves only for service tokens. `threadIdFor` hands services the sequential id and browsers the `cth_` access id. The documented service callers are Blog, Wiki, Deals, Reviews and Live. | Community `server/comments/service.js:63-69,104-110`, `server/comments/api.js:28-40`, `README.md:174-178`; tests `test/comments.test.js:271-340` | Community | **No removal condition in the repo.** The code treats it as the intended design. *Proposed*: every service caller stores and sends `cth_` ids; then refuse numeric ids. This needs a per-principal counter of numeric lookups, which does not exist yet. | 2026-12-18 | Numeric ids keep existing service rows working. |
--| C-15 | **Community legacy id columns**: `pastes.legacy_media_id`, `legacy_user_id`, `paste_comments.legacy_media_id`, and `legacy_id_map`. The map also serves old JWTs that have no `subject_id`. The staff censor route also falls back to a numeric id. | Community `server/db.js:49-50,84,118-125`, `server/identity/network.js:90-103`, `server/pastes/service.js:593-596` | Community | The columns are import provenance and stay. *Proposed*: the JWT fallback goes once Network tokens older than their maximum lifetime cannot exist (every token carries `subject_id` since Wave 1, ADR-001). | 2026-10-23 (JWT fallback) | none needed |
++| C-10 | **Closed (verified 2026-10-11):** Community `server/app.js:216-218` mounts its native paste API; `PASTES_AUTHORITY` no longer selects a Live proxy. **Community `PASTES_AUTHORITY`**. The code default is `live`, which proxies to Live and Media. Production is `community` (set in `/etc/openvibe/community.env`). | Community `server/config.js:51-53`, `server/app.js:127-151`, `README.md:16-21` | Community | *documented*: the old Media paste code and Live's Media paste router "stay until usage telemetry says they're dead" (operator notes, Wave 5). *Proposed*: Media's 410 on paste writes (C-12) and Live's Media path (C-11) are both unused for 30 days. Then make `community` the only mode. | 2026-11-06 | ADR-011 §Rollback: set `PASTES_AUTHORITY=live` in Community and Live, remove Media's two flags, restart. Export Community-side writes first. **No Community→Media export tool exists.** |
++| C-11 | **Closed (verified 2026-10-11):** Live `server/media-proxy/pastes.js:2-5` forwards to Community without a `PASTES_AUTHORITY` or Media branch. **Live `PASTES_AUTHORITY=community`** plus the Media paste router it replaces. Unset sends `/api/pastes` to Media. The value is read on every request. `PASTES_ON_COMMUNITY=1` 301s `/p/:slug` and `/pastes` to Community. `PASTES_AUTHORITY` and `OV_COMMUNITY_INTERNAL_URL` are missing from `.env.example`; `PASTES_ON_COMMUNITY=0` is at `.env.example:130-132`. | Live `server/pastes-client.js:5-18`, `server/media-proxy/pastes.js:250-319`, `server/index.js:597-612` | Live | Same as C-10. | 2026-11-06 | Unset `PASTES_AUTHORITY` (no restart needed). `PASTES_ON_COMMUNITY=0` needs a restart, and browsers may have cached the 301s. |
++| C-12 | **Open (2026-10-11):** Media paste writes are gone, but `PASTES_MOVED_TO` still drives `/p/:slug` and `/p/:slug/raw` redirects in `server/config.js:46` and `server/public/routes.js:537-542`. **Media paste writes frozen**: `PASTES_FROZEN_APPS=live` makes every paste write answer 410 `pastes.moved`. `PASTES_MOVED_TO=https://openvibe.community` 301s `/p/:slug` and `/p/:slug/raw`. Screenshot bytes are still served by Media. | Media `server/pastes/routes.js:47-55`, `server/public/routes.js:446-494` | Media | *proposed*: the 410 path is unused for 30 days. The 301s stay as permanent redirects, because old links exist. Delete the paste write code, keep the redirect. | 2026-11-06 (write code); redirects kept | Remove both flags (ADR-011 §Rollback). |
++| C-13 | **Open (2026-10-11):** Tools still forwards `/api/pastes` to Community in `apps/gateway/server/index.js:307-313`. **Tools paste forwarding**: `/api/pastes/*` goes to Community (`OV_COMMUNITY_INTERNAL_URL`) with the visitor's `ov_token` and no service token. `pastes.openvibe.tools` redirects to Community. **There is no flag.** Commit `fd5d318` (2026-09-22) replaced the Live proxy, and only a revert rolls it back. | Tools `apps/gateway/server/index.js:183-207, 296-309` | Tools | *proposed*: keep the forwarding as the product path, and remove the stale comments that say Media backs pastes (`apps/gateway/server/index.js:8,12`, `apps/gateway/README.md:17-59`). `LIVE_URL` is dead apart from the startup banner (`apps/gateway/server/config.js:35-39`). Remove it. | 2026-10-09 | `git revert fd5d318` in Tools, then deploy. |
++| C-14 | **Open (2026-10-11):** Community still returns numeric service thread IDs and resolves them in `server/comments/service.js:83,129`. **Community numeric thread ids for services.** A `/^\d{1,15}$/` id resolves only for service tokens. `threadIdFor` hands services the sequential id and browsers the `cth_` access id. The documented service callers are Blog, Wiki, Deals, Reviews and Live. | Community `server/comments/service.js:63-69,104-110`, `server/comments/api.js:28-40`, `README.md:174-178`; tests `test/comments.test.js:271-340` | Community | **No removal condition in the repo.** The code treats it as the intended design. *Proposed*: every service caller stores and sends `cth_` ids; then refuse numeric ids. This needs a per-principal counter of numeric lookups, which does not exist yet. | 2026-12-18 | Numeric ids keep existing service rows working. |
++| C-15 | **Open (2026-10-11):** Community dropped the legacy paste columns in `migrations/0002_drop_legacy_paste_columns.sql:8-10`, but `legacy_id_map` remains in `server/pastes/store.js:413-418`. **Community legacy id columns**: `pastes.legacy_media_id`, `legacy_user_id`, `paste_comments.legacy_media_id`, and `legacy_id_map`. The map also serves old JWTs that have no `subject_id`. The staff censor route also falls back to a numeric id. | Community `server/db.js:49-50,84,118-125`, `server/identity/network.js:90-103`, `server/pastes/service.js:593-596` | Community | The columns are import provenance and stay. *Proposed*: the JWT fallback goes once Network tokens older than their maximum lifetime cannot exist (every token carries `subject_id` since Wave 1, ADR-001). | 2026-10-23 (JWT fallback) | none needed |
## AI (Wave 13: `AI_SERVICE=remote` in production since 2026-09-23 ~15:40 UTC)
| # | What | Where | Owner | Removal condition | Target | Rollback lever until then |
|---|---|---|---|---|---|---|
--| C-20 | **`AI_SERVICE`**, Live's switch. `remote` runs `llm.complete`, translation, paste/frame, overview, media and recap calls as OpenVibe.AI workflows. Unset uses Live's own provider key. | Live `server/ai/ai-service.js:39-40`, `server/ai/llm.js:299-313` | Live | *documented*: keep the shared key in Live's settings "until the cutover has held for a while" (AI `docs/migration.md:101-103`). *Proposed*: 30 days of remote runs with no `[AI service]` warnings in Live's journal. Then remove the local shared-key path. Streamer BYO providers stay (AI `docs/migration.md:105-115`). | 2026-10-23 | Unset `AI_SERVICE` and restart Live (ADR-015 §Rollback). |
--| C-21 | **`POST /internal/ai/site-copy`** on Live, key-only (see C-50). Network calls `network.site_copy` directly since `422f8e9`, and falls back to Live's endpoint with the internal key when AI answers nothing. With `AI_SERVICE=remote`, Live forwards the call to AI. | Live `server/internal/routes.js:24-61`; Network `server/chrome/service.js:56-57,136-141` | Network (caller), Live (route) | *proposed*: remove Network's fallback, then Live's route after 14 days with no call (the procedure in Network `docs/retirement.md`). Live has no counter of its own. Network's `principal_usage` counts only Network's routes. | 2026-10-23 | Network's fallback call. |
--| C-22 | **`AI_NS_FALLBACK`** (default `live=live.*\|network.site_copy`): a service token with no `ns` gets these namespaces. Its stated reason, that Live's grant names no namespaces (`README.md:76-77`), stopped being true with Network `988330b`, which now grants `live.*` and `network.site_copy` (`server/identity/principals.js:102-103`). | AI `server/config.js:27-44`, `server/auth.js:96-103` | AI | *proposed*: every AI grant names its namespaces. Then set `AI_NS_FALLBACK=none` and remove the code. | 2026-10-09 | Set it back to the default string. |
--| C-23 | **`AI_NS_REQUIRED`** (default `true`). `false` restores the old open rule. It is documented as a "rollback lever only". | AI `server/config.js:77-79`, `.env.example:24-25`, `README.md:78-79` | AI | *proposed*: removed together with C-22, once fail-closed has held for 30 days. | 2026-10-23 | Set `AI_NS_REQUIRED=false`. |
--| C-24 | **Legacy MediaRef ids** `legacy:<svc>:<kind>:<id>` are still accepted. | AI `server/fetcher.js:17,160-163`, `server/workflows/common-schemas.js:7` | AI | *proposed*: every caller sends Media object ids (ADR-006). Unknown whether any caller still sends legacy refs: check AI `runs` inputs. | 2026-11-06 | none needed |
++| C-20 | **Open (2026-10-11):** Live removed local provider calls, but `AI_SERVICE=off` still gates AI workflows in `server/ai/ai-service.js:40`. **`AI_SERVICE`**, Live's switch. `remote` runs `llm.complete`, translation, paste/frame, overview, media and recap calls as OpenVibe.AI workflows. Unset uses Live's own provider key. | Live `server/ai/ai-service.js:39-40`, `server/ai/llm.js:299-313` | Live | *documented*: keep the shared key in Live's settings "until the cutover has held for a while" (AI `docs/migration.md:101-103`). *Proposed*: 30 days of remote runs with no `[AI service]` warnings in Live's journal. Then remove the local shared-key path. Streamer BYO providers stay (AI `docs/migration.md:105-115`). | 2026-10-23 | Unset `AI_SERVICE` and restart Live (ADR-015 §Rollback). |
++| C-21 | **Closed (verified 2026-10-11):** Live `server/internal/routes.js:24` records the removed site-copy route; Network `server/frame/service.js:30` no longer calls it. **`POST /internal/ai/site-copy`** on Live, key-only (see C-50). Network calls `network.site_copy` directly since `422f8e9`, and falls back to Live's endpoint with the internal key when AI answers nothing. With `AI_SERVICE=remote`, Live forwards the call to AI. | Live `server/internal/routes.js:24-61`; Network `server/chrome/service.js:56-57,136-141` | Network (caller), Live (route) | *proposed*: remove Network's fallback, then Live's route after 14 days with no call (the procedure in Network `docs/retirement.md`). Live has no counter of its own. Network's `principal_usage` counts only Network's routes. | 2026-10-23 | Network's fallback call. |
++| C-22 | **Closed (verified 2026-10-11):** AI `server/auth.js:16-18` records retirement of the namespace fallback; no `AI_NS_FALLBACK` reader remains. **`AI_NS_FALLBACK`** (default `live=live.*\|network.site_copy`): a service token with no `ns` gets these namespaces. Its stated reason, that Live's grant names no namespaces (`README.md:76-77`), stopped being true with Network `988330b`, which now grants `live.*` and `network.site_copy` (`server/identity/principals.js:102-103`). | AI `server/config.js:27-44`, `server/auth.js:96-103` | AI | *proposed*: every AI grant names its namespaces. Then set `AI_NS_FALLBACK=none` and remove the code. | 2026-10-09 | Set it back to the default string. |
++| C-23 | **Closed (verified 2026-10-11):** AI `server/auth.js:16-18` records retirement of the open-rule lever; no `AI_NS_REQUIRED` reader remains. **`AI_NS_REQUIRED`** (default `true`). `false` restores the old open rule. It is documented as a "rollback lever only". | AI `server/config.js:77-79`, `.env.example:24-25`, `README.md:78-79` | AI | *proposed*: removed together with C-22, once fail-closed has held for 30 days. | 2026-10-23 | Set `AI_NS_REQUIRED=false`. |
++| C-24 | **Open (2026-10-11):** AI still accepts `legacy:<svc>:<kind>:<id>` in `server/fetcher.js:223` and `server/workflows/common-schemas.js:7`. **Legacy MediaRef ids** `legacy:<svc>:<kind>:<id>` are still accepted. | AI `server/fetcher.js:17,160-163`, `server/workflows/common-schemas.js:7` | AI | *proposed*: every caller sends Media object ids (ADR-006). Unknown whether any caller still sends legacy refs: check AI `runs` inputs. | 2026-11-06 | none needed |
## Money (Billing prepared, running in shadow; Live is the authority)
| # | What | Where | Owner | Removal condition | Target | Rollback lever until then |
|---|---|---|---|---|---|---|
--| C-30 | **`BILLING_AUTHORITY=live`** (the default). Live holds the money: `openvibe_bucks_*`, `transactions`, `payment_orders`, `subscriptions`. `billing` makes Billing the ledger, and a tripwire (`assertLiveLedger`) makes Live's columns read-only. Any other value refuses every money write. There is no runtime shadow mode. "Shadow" means Billing's repeated shadow imports (Billing `README.md:203-204`). | Live `server/monetization/money-authority.js:26-49`, `server/db/database.js:5406-5555`; Billing `docs/live-cutover.md` | Billing + Live | *documented*: Billing `docs/live-cutover.md` steps 0-10. **Blocked on the owner**: step 4 (re-pointing the PowerChat webhook in the PowerChat dashboard). *Proposed*: after the cutover holds for 30 days, remove Live's money write paths and leave the columns read-only. | Cutover: when the owner re-points (target 2026-10-07). Removal: cutover + 30 days. | Before step 6: re-point PowerChat back to Live. Steps 6-9: unset `BILLING_AUTHORITY` and restore `billing-pre-cutover.db`. After step 9: **no tool exists** (Billing `docs/live-cutover.md:177-200`). |
--| C-31 | **`money_writes_frozen`** (site setting, owner-only, both modes), with `/api/admin/money/freeze`. | Live `server/monetization/money-authority.js:54-111`, `server/admin/routes.js:492-517` | Live | A permanent incident control, not a shim. It stays. | kept | It is itself the lever. |
--| C-32 | **Live's provider webhooks** `/api/powerchat/webhook` and `/api/payments/webhook/*`. They answer 410 under `billing`. | Live `server/monetization/payments-routes.js:15-25` | Live | *proposed*: removed with C-30's write paths. A 410 makes PowerChat disable the endpoint (Billing `docs/live-cutover.md:170-175`), so remove them only after PowerChat points at Billing. | cutover + 30 days | Re-point the webhook back to Live. |
++| C-30 | **Open (2026-10-11):** Live still reads `BILLING_AUTHORITY` and guards ledger writes in `server/monetization/money-authority.js:27-38`. **`BILLING_AUTHORITY=live`** (the default). Live holds the money: `openvibe_bucks_*`, `transactions`, `payment_orders`, `subscriptions`. `billing` makes Billing the ledger, and a tripwire (`assertLiveLedger`) makes Live's columns read-only. Any other value refuses every money write. There is no runtime shadow mode. "Shadow" means Billing's repeated shadow imports (Billing `README.md:203-204`). | Live `server/monetization/money-authority.js:26-49`, `server/db/database.js:5406-5555`; Billing `docs/live-cutover.md` | Billing + Live | *documented*: Billing `docs/live-cutover.md` steps 0-10. **Blocked on the owner**: step 4 (re-pointing the PowerChat webhook in the PowerChat dashboard). *Proposed*: after the cutover holds for 30 days, remove Live's money write paths and leave the columns read-only. | Cutover: when the owner re-points (target 2026-10-07). Removal: cutover + 30 days. | Before step 6: re-point PowerChat back to Live. Steps 6-9: unset `BILLING_AUTHORITY` and restore `billing-pre-cutover.db`. After step 9: **no tool exists** (Billing `docs/live-cutover.md:177-200`). |
++| C-31 | **Open (2026-10-11):** Live still reads `money_writes_frozen` in `server/monetization/money-authority.js:56-73`. **`money_writes_frozen`** (site setting, owner-only, both modes), with `/api/admin/money/freeze`. | Live `server/monetization/money-authority.js:54-111`, `server/admin/routes.js:492-517` | Live | A permanent incident control, not a shim. It stays. | kept | It is itself the lever. |
++| C-32 | **Open (2026-10-11):** Live retains provider webhook handlers under Billing authority in `server/monetization/payments-routes.js:14-25` and `server/integrations/powerchat-routes.js:443`. **Live's provider webhooks** `/api/powerchat/webhook` and `/api/payments/webhook/*`. They answer 410 under `billing`. | Live `server/monetization/payments-routes.js:15-25` | Live | *proposed*: removed with C-30's write paths. A 410 makes PowerChat disable the endpoint (Billing `docs/live-cutover.md:170-175`), so remove them only after PowerChat points at Billing. | cutover + 30 days | Re-point the webhook back to Live. |
## Stream ingest (OpenRestream deployed; no slot switched)
| # | What | Where | Owner | Removal condition | Target | Rollback lever until then |
|---|---|---|---|---|---|---|
--| C-40 | **`managed_streams.ingest_authority`** (default `live`) and `OPENRE_URL`. The authority is `openre` only when the column says so and `OPENRE_URL` and `OV_OAUTH_CLIENT_SECRET` are set. Only RTMP moves. | Live `server/openre/schema.js:5-7`, `server/openre/authority.js:18-54,103-146`, `server/openre/routes.js:35-47` | Live + OpenRestream | *documented*: "Live's ingest code stays until the last protocol has run on OpenRestream for two weeks without a regression" (ADR-009 §Rollback). WHIP, JSMPEG and SFU are not ported (OpenRestream `README.md:207-209`). **Blocked on the owner**: the DNS-only record `ingest.openre.stream` and 1936/tcp. | First RTMP slot: DNS + 7 days. Live's RTMP ingest: last slot + 14 days. Whole switch: 2027-03-31 (depends on three unported transports). | Per slot: `PUT …/ingest-authority {"authority":"live"}`, then the broadcaster regenerates the key. All slots: unset `OPENRE_URL` and restart Live (OpenRestream `README.md:205`). |
--| C-41 | **OpenRestream port 1936** (`OPENRE_RTMP_PORT`). Port 1935 stays Live's until Live stops listening. Then `OPENRE_RTMP_EXTRA_PORTS=1935`. | OpenRestream `server/config.js:24,72`, `README.md:139-145` | OpenRestream | *documented*: after Live's RTMP ingest retires (C-40). 1936 stays permanently, because handed-out URLs use it. | with C-40 | none needed |
--| C-42 | **OpenRestream `MEDIA_API_KEY`**, `OPENRE_MEDIA_AUTH=key` (default). This is an interim shared Media tenant key. The target is `service`. The **capability bridge** allows a capability unknown to contracts on the grant alone. | OpenRestream `server/config.js:138-139`, `server/media-client.js:31-36`, `server/auth/index.js:16-19,79-84`, `README.md:103,125` | OpenRestream + Media | *documented*: Media names capabilities on its VOD routes, and the grant `[openre, media.object.upload, openvibe.media]` exists. The bridge becomes a no-op once contracts define the ids (contracts v0.16.0 is already pinned, `package.json:26`). | 2026-10-23 | Set `OPENRE_MEDIA_AUTH=key`. |
++| C-40 | **Open (2026-10-11):** Live still switches `managed_streams.ingest_authority` in `server/openre/authority.js:29,131-160`. **`managed_streams.ingest_authority`** (default `live`) and `OPENRE_URL`. The authority is `openre` only when the column says so and `OPENRE_URL` and `OV_OAUTH_CLIENT_SECRET` are set. Only RTMP moves. | Live `server/openre/schema.js:5-7`, `server/openre/authority.js:18-54,103-146`, `server/openre/routes.js:35-47` | Live + OpenRestream | *documented*: "Live's ingest code stays until the last protocol has run on OpenRestream for two weeks without a regression" (ADR-009 §Rollback). WHIP, JSMPEG and SFU are not ported (OpenRestream `README.md:207-209`). **Blocked on the owner**: the DNS-only record `ingest.openre.stream` and 1936/tcp. | First RTMP slot: DNS + 7 days. Live's RTMP ingest: last slot + 14 days. Whole switch: 2027-03-31 (depends on three unported transports). | Per slot: `PUT …/ingest-authority {"authority":"live"}`, then the broadcaster regenerates the key. All slots: unset `OPENRE_URL` and restart Live (OpenRestream `README.md:205`). |
++| C-41 | **Open (2026-10-11):** OpenRestream still defaults `OPENRE_RTMP_PORT` to 1936 and reads `OPENRE_RTMP_EXTRA_PORTS` in `server/config.js:24,82`. **OpenRestream port 1936** (`OPENRE_RTMP_PORT`). Port 1935 stays Live's until Live stops listening. Then `OPENRE_RTMP_EXTRA_PORTS=1935`. | OpenRestream `server/config.js:24,72`, `README.md:139-145` | OpenRestream | *documented*: after Live's RTMP ingest retires (C-40). 1936 stays permanently, because handed-out URLs use it. | with C-40 | none needed |
++| C-42 | **Open (2026-10-11):** OpenRestream still defaults `OPENRE_MEDIA_AUTH` to key and reads `MEDIA_API_KEY` in `server/config.js:215-216`; capability bridge remains in `server/auth/index.js:45`. **OpenRestream `MEDIA_API_KEY`**, `OPENRE_MEDIA_AUTH=key` (default). This is an interim shared Media tenant key. The target is `service`. The **capability bridge** allows a capability unknown to contracts on the grant alone. | OpenRestream `server/config.js:138-139`, `server/media-client.js:31-36`, `server/auth/index.js:16-19,79-84`, `README.md:103,125` | OpenRestream + Media | *documented*: Media names capabilities on its VOD routes, and the grant `[openre, media.object.upload, openvibe.media]` exists. The bridge becomes a no-op once contracts define the ids (contracts v0.16.0 is already pinned, `package.json:26`). | 2026-10-23 | Set `OPENRE_MEDIA_AUTH=key`. |
## The shared internal key (`INTERNAL_API_KEY` / `X-Internal-Key`): retired 2026-09-29
++**Closed (verified 2026-10-11): C-50–C-59.** No code reader of `INTERNAL_API_KEY` or `X-Internal-Key` remains in the described services; Live `server/internal/routes.js:13`, Media `server/service-guard.js:9`, and Tools `apps/_shared/internal-token.js:4` describe token-only gates. The original ten entries were recorded together in this section.
++
C-50 to C-59 are closed, in one sweep (plan T2: convert, then delete; no telemetry window). Every internal call in the
estate now carries a service token with the one capability it performs (Contracts v0.79.0 added the last eight:
`network.avatar.write`, `network.registry.read`, `network.coins.read`, `live.avatar.write`, `live.url_registry.refresh`,
@@@ -107,32 +106,32 @@ estate now carries a service token with the one capability it performs (Contract
| # | What | Where | Owner | Removal condition | Target | Rollback lever until then |
|---|---|---|---|---|---|---|
--| C-60 | **v1 signature** `X-OpenVibe-Signature` (HMAC of the body, no timestamp) is sent on every delivery next to v2 (`X-OpenVibe-Timestamp`, `X-OpenVibe-Signature-V2`, ±300 s). **There is no env switch to stop v1.** Production: Events sends v2. | Events `server/worker.js:56,66-68`, `lib/client.js:36-64` | Events | *documented*: "v1 stays on the wire until every consumer requires v2, and only then may it be removed" (ADR-004; Events `docs/replay-window-rollout.md` step 5, "optional"). Every consumer has committed `requireV2`/`verifyDeliveryV2`: Live `ae07553`, Search `5a7e59b`, News `1802e7d`, Deals `e72aeec`, Trade `9a55644`, Reviews `3151c05`, Tips `435f32b`, VIP `dc98781`, Examples `6ea9cfd`. Which of these are deployed: **unknown**. Check each `/opt/openvibe.<svc>` HEAD. Condition: all deployed, plus one clean day for Tips and VIP. | 2026-10-07 | Consumers unset `requireV2`. Events keeps sending v1 until then (ADR-004). |
--| C-61 | **Codes webhook tester** checks and generates v1 only. The docs page already describes v2. | Codes `server/domain/webhooks.js`; Events `docs/replay-window-rollout.md:58` | Codes | Tester inspects `X-OpenVibe-Signature-V2` (rollout doc follow-up). Needed before C-60. | 2026-10-07 | none needed |
++| C-60 | **Closed (verified 2026-10-11):** Events `server/worker.js:75-88` sends only `X-OpenVibe-Signature-V2`; the v1 signer remains a library helper, not a delivery header. **v1 signature** `X-OpenVibe-Signature` (HMAC of the body, no timestamp) is sent on every delivery next to v2 (`X-OpenVibe-Timestamp`, `X-OpenVibe-Signature-V2`, ±300 s). **There is no env switch to stop v1.** Production: Events sends v2. | Events `server/worker.js:56,66-68`, `lib/client.js:36-64` | Events | *documented*: "v1 stays on the wire until every consumer requires v2, and only then may it be removed" (ADR-004; Events `docs/replay-window-rollout.md` step 5, "optional"). Every consumer has committed `requireV2`/`verifyDeliveryV2`: Live `ae07553`, Search `5a7e59b`, News `1802e7d`, Deals `e72aeec`, Trade `9a55644`, Reviews `3151c05`, Tips `435f32b`, VIP `dc98781`, Examples `6ea9cfd`. Which of these are deployed: **unknown**. Check each `/opt/openvibe.<svc>` HEAD. Condition: all deployed, plus one clean day for Tips and VIP. | 2026-10-07 | Consumers unset `requireV2`. Events keeps sending v1 until then (ADR-004). |
++| C-61 | **Closed (verified 2026-10-11):** Codes has no webhook tester; Services `server/domain/webhooks.js:46-70` validates v2 and `server/http/tools.js:103` describes the retired v1 header. **Codes webhook tester** checks and generates v1 only. The docs page already describes v2. | Codes `server/domain/webhooks.js`; Events `docs/replay-window-rollout.md:58` | Codes | Tester inspects `X-OpenVibe-Signature-V2` (rollout doc follow-up). Needed before C-60. | 2026-10-07 | none needed |
## Media, Live storage and legacy data
| # | What | Where | Owner | Removal condition | Target | Rollback lever until then |
|---|---|---|---|---|---|---|
--| 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-70 | **Closed (verified 2026-10-11):** Live `server/media-client.js:63-68` sends a Network service token to Media and has no `MEDIA_API_KEY` reader. **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 | **Open (2026-10-11):** Media still parses `MEDIA_APPS_SEED` and deprecated `MEDIA_APP_KEYS` in `server/auth.js:425-443`. **`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 | **Closed 2026-10-10:** the ADR-021 backups that still held IPs were deleted: `/opt/openvibe.live/data/backups/analytics-pre-adr021-2026-09-23.db` (1.7 GB), `/opt/openvibe.tools/backups/analytics-2026-09-23` (76 MB) and Network's `data/backups/network.db.pre-adr021-20260923T1634` (731 MB), each with its -wal/-shm. No other copy holds them: the oldest ovhost backup on the host is 2026-09-27, and the first off-site run (2026-09-23 18:47 UTC) came after the scrub. | host | Live, Tools, Network | *closed* | — | Pre-ADR-021 analytics are gone for good, as ADR-021 intends. |
--| C-73 | *Steps 1-2 DONE 2026-09-23: no Live code reads or writes the frozen tables (Live `9b80d2b`, `9b3be33`; the test refuses both, with an empty allow-list); deployed, 13 public endpoints keep their shape and counts. Step 3 (drop) not before 2026-10-23, procedure in Live `docs/vods-and-clips.md`.* **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 | **retired 2026-09-24 05:51 UTC**: `migrate-to-releases.sh` ran on the host (one restart, no streams live); Live runs `/opt/openvibe.live/current` → `releases/<time>-<sha>` and deploys from there; the inventory reads Live's release as root (`owner: root`, `runAs: ubuntu`); the Live restore drill passed after the switch. | 2026-10-09 | `git reset` to the old commit. |
--| C-77 | **`ALLOW_P2P_FALLBACK`** (default `false`), an "emergency rollback" to legacy P2P relays, which can expose viewer IPs. | Live `server/config.js:87-93`, `SETUP.md:136` | Live | **No condition documented.** *Proposed*: remove the flag and the P2P relay path. | 2026-11-06 | Set the flag to `true` (not recommended). |
++| C-73 | **Closed (verified 2026-10-11):** Live `migrations/0002_live.sql:9-13` omits frozen `vods`, `clips` and `pastes` from its PostgreSQL schema; `server/db/database.js:2361-2365` has no reader. *Steps 1-2 DONE 2026-09-23: no Live code reads or writes the frozen tables (Live `9b80d2b`, `9b3be33`; the test refuses both, with an empty allow-list); deployed, 13 public endpoints keep their shape and counts. Step 3 (drop) not before 2026-10-23, procedure in Live `docs/vods-and-clips.md`.* **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 | **Closed (verified 2026-10-11):** Live `server/config.js:246-247` records removal of its pre-Media path settings; Media owns its own storage paths. **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 | **Open (2026-10-11):** Media still pairs old row writes with object projections in `server/db/database.js:82-84` and `server/objects/model.js:1-9`; old routes and `server/webhooks.js:84-96` remain. **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 | **Closed (verified 2026-10-11):** Live `deploy/scripts/deploy.sh:4-16` delegates to ovhost release layout and supports rollback. **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 | **retired 2026-09-24 05:51 UTC**: `migrate-to-releases.sh` ran on the host (one restart, no streams live); Live runs `/opt/openvibe.live/current` → `releases/<time>-<sha>` and deploys from there; the inventory reads Live's release as root (`owner: root`, `runAs: ubuntu`); the Live restore drill passed after the switch. | 2026-10-09 | `git reset` to the old commit. |
++| C-77 | **Closed (verified 2026-10-11):** No `ALLOW_P2P_FALLBACK` reader or P2P relay path remains under Live `server/`. **`ALLOW_P2P_FALLBACK`** (default `false`), an "emergency rollback" to legacy P2P relays, which can expose viewer IPs. | Live `server/config.js:87-93`, `SETUP.md:136` | Live | **No condition documented.** *Proposed*: remove the flag and the P2P relay path. | 2026-11-06 | Set the flag to `true` (not recommended). |
## Shared packages, sites and closed repositories
| # | What | Where | Owner | Removal condition | Target | Rollback lever until then |
|---|---|---|---|---|---|---|
--| C-80 | **`openvibe-shared/analytics.js`** (`AnalyticsTracker`) stores raw IP, `user_id`, city and full UA. **No deployed service uses it.** Live (`server/analytics/`, `0e07aec`), Network (`server/analytics/`) and Tools (`apps/_shared/analytics`, `533c9a3`) run their own ADR-021 trackers. No other repo requires it. The one remaining `require` is the stale local checkout `~/OpenVibers/OpenVibe.Live` (`e266060`, behind `origin/main`). It is still exported (`package.json:10`, `index.js:8,40-44`) with no deprecation marker. Stale docs: Shared `docs/migration-plan.md:29,252,493`; Live `SECURITY_AUDIT.md:98`. **RETIRED 2026-09-23**: the ADR-021 module is `openvibe-shared/analytics` v1.4.0 (with GPC/DNT); Live `c893c6c`, Tools `5d1b423` and Network `9bbd90f` use it, their local copies are deleted, verified on production (no ip/user_id/city; DNT requests not recorded). | OpenVibe.Shared `analytics.js` | Shared | **No condition documented** (ADR-021 does not mention it). *Proposed*: mark it deprecated in the next Shared minor, then remove it in the next major, since removing an export is breaking (ADR-008). | Deprecate 2026-10-09; remove in Shared v2.0.0, 2026-11-06 | Pin the previous Shared tag. |
--| C-81 | **OpenVibe.Sites placeholders**. There are 16, all `"stage": "placeholder"`: openvibe.chat, .news, .reviews, .tips, .vip, .trade, .host, .deals, .coupons, openre.stream, and auth., api., admin., themes., realtime. and ai.openvibe.network. The page says "coming soon · this page is a placeholder, nothing here is live yet". None claims to be a working product. **One defect:** repos outside `EXISTING_REPOS` are labelled "(charter only, no code yet)", which is false for Tips, VIP, News, Reviews, Trade, Deals, Coupons and Host. (realtime.openvibe.network no longer advertises a product: ADR-005 closed it and its placeholder has served a closed notice since 2026-09-23, C-82.) | Sites `sites.json`, `build.js:22,157-161,283`, `deploy/nginx/*.conf` | Sites (each service owns its launch) | *partly done 2026-09-24*: every placeholder's label comes from the product's STATUS.json/registry facts (Sites `facts.json`, `8be8071`), so a running product is "in development" with what its launch waits for, never "charter only"; realtime is a closed notice (ADR-005); openvibe.host describes Host as it is. Retire each placeholder when its product launches | Label: 2026-09-30; the rest: at each launch | Re-add the entry. |
--| C-82 | **OpenVibe.Realtime, never built and closed by ADR-005** (accepted 2026-09-22): realtime runs inside Events as SSE `GET /realtime/stream`. The repository never carried code — only `LICENSE`, `README.md`, `SECURITY.md`, `STATUS.json`, no `package.json`; its Contracts service manifest was removed in Contracts v0.78.0 (2026-09-29; v0.34.0 had made it `retired`), so realtime is not registered as a service — it registered no capability and no event, and the charter's planned `realtime.subscribe` was never registered. **Nothing was ever deployed from it, so nothing is left to migrate**: `STATUS.json` is `stage: closed`, `code: false`, `deployed: false`, `contracts: none` (updated 2026-09-28); the README no longer says "Decision pending"; the repository was marked archived on 2026-09-25 (`6a49221`, roadmap WS-F task 6) and stays as ADR-005's decision record; Events' README states ADR-005 put Realtime inside Events; and the domain `realtime.openvibe.network` serves a Sites closed notice since 2026-09-23 (C-81). | OpenVibe.Realtime; Contracts; Sites; Events | Contracts, Sites, Events | *met, nothing to remove* (2026-09-28): every listed leftover is resolved and the service has no runtime, data or consumer of its own. **No migration remains** — no rows to migrate, nothing to back up or cut over. | Done 2026-09-28 (archive 2026-09-25, closed notice 2026-09-23) | ADR-005: reopen only on measured load needing an independently scaled delivery process. |
--| C-83 | **Live `/internal/media-webhook`** next to the Events consumer `/internal/media-events` (media.vod.*, media.clip.*, media.storage.*). `MEDIA_EVENTS_AUTHORITY=webhook` \| `both` \| `events`; each outcome is applied once (inbox consumer `live-media-outcomes`, key object + event id; Media webhooks carry `event_id` since `cbf8410`). **`both` since 2026-09-23 20:00 UTC** (3 subscriptions created). | Live `server/media-proxy/webhook.js`, `outcomes.js`, `media-events.js`; Media `server/webhooks.js` | Live + Media | *documented* (Live `docs/architecture.md` "Media outcomes over Events"): `events` has run clean (every VOD/clip since the switch has its `vod_ai_state`/`clip_ai_state` row and a receipt, no DLQ entries for Live's subscriptions); then clear Live's `webhook_url` in Media `apps`, wait a release, delete the route, `MEDIA_WEBHOOK_SECRET` and the `webhook`/`both` modes. | `events` 2026-10-01; removal 2026-10-14 | `MEDIA_EVENTS_AUTHORITY=webhook`; `subscribe-media-events.js --disable` |
--| C-84 | **Live deploy notice over the Chat bridge** (`chat-remote.js` `deployNotice`) next to the `live.release.deployed` event (Live `e73a92d`). | Live `server/chat/deploy-notice.js`, `server/chat/chat-remote.js` | Chat + Live | *done 2026-09-23*: Chat subscribes to `live.release.deployed` (Chat `ed90461`, table `deploy_releases` keyed by head) and folds by `subject.id`; the 22:14 UTC deploy produced one card via both paths (`duplicate:release`), and since Live `5324f40` (22:18 UTC deploy: `first_via = events`, folded) the bridge hop runs only while Live's Events publishing is off | 2026-10-14 | Keep the bridge call |
--| C-85 | **Live's own go-live follower push** (`server/streaming/golive-notify.js`) next to Network's `live.stream.started` consumer (subscription since 2026-09-23 19:55 UTC; Live `GET /internal/followers`). A shared per-streamer rate limit in Network stops double notifications. | Live `golive-notify.js` + call sites in `routes.js`, `rtmp-server.js`, `whip-handler.js`, `openre/mirror.js`; Network `server/notifications/stream-live.js` | Live + Network | *documented* (Network commit 2a95548): Network's log shows `outcome: notified` for real go-lives; then delete Live's push and retire Network's `POST /internal/events/stream-live`. | 2026-10-07 | Unsubscribe Network from `live.stream.started` |
++| C-80 | **Closed (verified 2026-10-11):** Shared `package.json:9-13` exports `./analytics`, while the old `analytics.js` and `AnalyticsTracker` export are absent. **`openvibe-shared/analytics.js`** (`AnalyticsTracker`) stores raw IP, `user_id`, city and full UA. **No deployed service uses it.** Live (`server/analytics/`, `0e07aec`), Network (`server/analytics/`) and Tools (`apps/_shared/analytics`, `533c9a3`) run their own ADR-021 trackers. No other repo requires it. The one remaining `require` is the stale local checkout `~/OpenVibers/OpenVibe.Live` (`e266060`, behind `origin/main`). It is still exported (`package.json:10`, `index.js:8,40-44`) with no deprecation marker. Stale docs: Shared `docs/migration-plan.md:29,252,493`; Live `SECURITY_AUDIT.md:98`. **RETIRED 2026-09-23**: the ADR-021 module is `openvibe-shared/analytics` v1.4.0 (with GPC/DNT); Live `c893c6c`, Tools `5d1b423` and Network `9bbd90f` use it, their local copies are deleted, verified on production (no ip/user_id/city; DNT requests not recorded). | OpenVibe.Shared `analytics.js` | Shared | **No condition documented** (ADR-021 does not mention it). *Proposed*: mark it deprecated in the next Shared minor, then remove it in the next major, since removing an export is breaking (ADR-008). | Deprecate 2026-10-09; remove in Shared v2.0.0, 2026-11-06 | Pin the previous Shared tag. |
++| C-81 | **Closed (verified 2026-10-11):** Sites `sites.json` has no placeholder kind; `build.js:173-178` renders current notice or product entries. **OpenVibe.Sites placeholders**. There are 16, all `"stage": "placeholder"`: openvibe.chat, .news, .reviews, .tips, .vip, .trade, .host, .deals, .coupons, openre.stream, and auth., api., admin., themes., realtime. and ai.openvibe.network. The page says "coming soon · this page is a placeholder, nothing here is live yet". None claims to be a working product. **One defect:** repos outside `EXISTING_REPOS` are labelled "(charter only, no code yet)", which is false for Tips, VIP, News, Reviews, Trade, Deals, Coupons and Host. (realtime.openvibe.network no longer advertises a product: ADR-005 closed it and its placeholder has served a closed notice since 2026-09-23, C-82.) | Sites `sites.json`, `build.js:22,157-161,283`, `deploy/nginx/*.conf` | Sites (each service owns its launch) | *partly done 2026-09-24*: every placeholder's label comes from the product's STATUS.json/registry facts (Sites `facts.json`, `8be8071`), so a running product is "in development" with what its launch waits for, never "charter only"; realtime is a closed notice (ADR-005); openvibe.host describes Host as it is. Retire each placeholder when its product launches | Label: 2026-09-30; the rest: at each launch | Re-add the entry. |
++| C-82 | **Closed (verified 2026-10-11):** Realtime has no service runtime; Events serves `/realtime/stream` in `server/app.js:46-48`, and Sites keeps the closed notice at `sites.json:640-647`. **OpenVibe.Realtime, never built and closed by ADR-005** (accepted 2026-09-22): realtime runs inside Events as SSE `GET /realtime/stream`. The repository never carried code — only `LICENSE`, `README.md`, `SECURITY.md`, `STATUS.json`, no `package.json`; its Contracts service manifest was removed in Contracts v0.78.0 (2026-09-29; v0.34.0 had made it `retired`), so realtime is not registered as a service — it registered no capability and no event, and the charter's planned `realtime.subscribe` was never registered. **Nothing was ever deployed from it, so nothing is left to migrate**: `STATUS.json` is `stage: closed`, `code: false`, `deployed: false`, `contracts: none` (updated 2026-09-28); the README no longer says "Decision pending"; the repository was marked archived on 2026-09-25 (`6a49221`, roadmap WS-F task 6) and stays as ADR-005's decision record; Events' README states ADR-005 put Realtime inside Events; and the domain `realtime.openvibe.network` serves a Sites closed notice since 2026-09-23 (C-81). | OpenVibe.Realtime; Contracts; Sites; Events | Contracts, Sites, Events | *met, nothing to remove* (2026-09-28): every listed leftover is resolved and the service has no runtime, data or consumer of its own. **No migration remains** — no rows to migrate, nothing to back up or cut over. | Done 2026-09-28 (archive 2026-09-25, closed notice 2026-09-23) | ADR-005: reopen only on measured load needing an independently scaled delivery process. |
++| C-83 | **Closed (verified 2026-10-11):** Live `server/media-proxy/outcomes.js:3-9` uses Events and records removal of `/internal/media-webhook`; Media generic webhooks remain separately under C-75. **Live `/internal/media-webhook`** next to the Events consumer `/internal/media-events` (media.vod.*, media.clip.*, media.storage.*). `MEDIA_EVENTS_AUTHORITY=webhook` \| `both` \| `events`; each outcome is applied once (inbox consumer `live-media-outcomes`, key object + event id; Media webhooks carry `event_id` since `cbf8410`). **`both` since 2026-09-23 20:00 UTC** (3 subscriptions created). | Live `server/media-proxy/webhook.js`, `outcomes.js`, `media-events.js`; Media `server/webhooks.js` | Live + Media | *documented* (Live `docs/architecture.md` "Media outcomes over Events"): `events` has run clean (every VOD/clip since the switch has its `vod_ai_state`/`clip_ai_state` row and a receipt, no DLQ entries for Live's subscriptions); then clear Live's `webhook_url` in Media `apps`, wait a release, delete the route, `MEDIA_WEBHOOK_SECRET` and the `webhook`/`both` modes. | `events` 2026-10-01; removal 2026-10-14 | `MEDIA_EVENTS_AUTHORITY=webhook`; `subscribe-media-events.js --disable` |
++| C-84 | **Closed (verified 2026-10-11):** Live `server/chat/deploy-notice.js:5-10` publishes only `live.release.deployed`; the Chat bridge deploy hop is gone. **Live deploy notice over the Chat bridge** (`chat-remote.js` `deployNotice`) next to the `live.release.deployed` event (Live `e73a92d`). | Live `server/chat/deploy-notice.js`, `server/chat/chat-remote.js` | Chat + Live | *done 2026-09-23*: Chat subscribes to `live.release.deployed` (Chat `ed90461`, table `deploy_releases` keyed by head) and folds by `subject.id`; the 22:14 UTC deploy produced one card via both paths (`duplicate:release`), and since Live `5324f40` (22:18 UTC deploy: `first_via = events`, folded) the bridge hop runs only while Live's Events publishing is off | 2026-10-14 | Keep the bridge call |
++| C-85 | **Open (2026-10-11):** Live still calls Network `/internal/events/stream-live` in `server/streaming/golive-notify.js:80`; `GOLIVE_NOTIFY=events` gates that path at :31-39. **Live's own go-live follower push** (`server/streaming/golive-notify.js`) next to Network's `live.stream.started` consumer (subscription since 2026-09-23 19:55 UTC; Live `GET /internal/followers`). A shared per-streamer rate limit in Network stops double notifications. | Live `golive-notify.js` + call sites in `routes.js`, `rtmp-server.js`, `whip-handler.js`, `openre/mirror.js`; Network `server/notifications/stream-live.js` | Live + Network | *documented* (Network commit 2a95548): Network's log shows `outcome: notified` for real go-lives; then delete Live's push and retire Network's `POST /internal/events/stream-live`. | 2026-10-07 | Unsubscribe Network from `live.stream.started` |
## Kept by design (not shims)