openvibers/openvibe.media Commits
Point fabric doc at openvibe.media and mark pastes moved in object docs (#17)
Changes
@@@ -14,7 +14,7 @@ presentation; they ask Media for an object and a delivery URL and never know whe**Online (the Media Fabric).** Every object a viewer can ask for has an immediately retrievable copy:```--viewer → media.openvibe.network → delivery router++viewer → openvibe.media → delivery router├─ OpenVibe edge (the Media host's NVMe cache; later edge nodes) capacity-bounded, prepaid bandwidth├─ global CDN (Bunny in front of B2; Cloudflare only in front of R2)└─ hot object tier: R2 Standard@@@ -3,7 +3,8 @@Roadmap Wave 4 turns Media from predecessor-shaped VOD/clip/file tables into one canonical objectplatform. Every stored blob is a **media object** with an id `med_<ULID>`. The inherited`vods`, `clips`, `files` and screenshot `pastes` rows stay as they are, and each one is now a typed--projection over an object through its `object_id` column. Every existing URL and v1 response is++projection over an object through its `object_id` column. Pastes moved to OpenVibe.Community on++2026-09-22: Media keeps the frozen `pastes` rows and their screenshot/avatar objects, read-only. Every existing URL and v1 response isunchanged. New code talks to objects through the [v2 API](#object-api-v2).Code: `server/objects/` (`model.js`, `routes.js`, `backfill.js`, `reconcile.js`, `invariant.js`,@@@ -53,6 +54,10 @@ the transitional `media.media-ref@1` legacy form in `legacy_ref`, which is uniqu| avatar (screenshot paste with `metadata.kind = 'avatar'`) | avatar | `legacy:<app>:avatar:<slug>` || `vods`/`clips.thumbnail_url` (`/t/<name>`) | thumbnail | `legacy:<app>:thumbnail:<name>` |++Paste rows are frozen since pastes moved to OpenVibe.Community (2026-09-22): no new screenshot or++avatar objects are created through Media's paste API, and the existing ones cannot be deleted++(`DELETE` answers 409 `media.object.legacy_managed`).++Every v2 route that takes `:id` accepts either form. Native v2 objects have `legacy_ref = NULL`.### How a row maps to its object@@@ -519,7 +524,7 @@ While any hold applies:- Quarantine cleanup and the boot junk sweep skip the object.- Finalize never deletes a held recording that turned out empty (no file, or zero bytes): it keeps the row and the file and settles it as failed (`missing_file` / `zero_byte`, quarantined, `vod.failed`).- `deleteVodObjects` keeps the bytes.-- - Paste screenshot removal keeps the file.++ - Paste screenshot removal no longer exists (the paste API is read-only since pastes moved to OpenVibe.Community).- A `BEFORE DELETE` trigger on each projected table, plus a trigger on `media_objects.lifecycle_status`, refuses the change, so paths nobody has hooked are still covered. The triggers are the `_v2` ones (clip-aware); an older database has its first-version guards replaced at start.- **No tier move.** `moveToCold`, `moveToHot`, `promoteToR2` and `demoteFromR2` return `{ ok: false, held: true }` (the admin moves answer it, R2 moves are logged as `refused`), and the sweep counts these as `skippedHeld` instead of errors. A held native object is never promoted to or demoted from the R2 cache either (logged as `refused`; a hold placed while its bytes were being copied wins, and the unrecorded copy is removed). A hold freezes an object's placement: `moveToHot` is refused too, because restoring flips the row to local and drops an R2 copy. A clip cut from a held VOD in B2/R2 is cut from the cloud copy instead of fetching the VOD home. Row updates that only record where the bytes already are (the sweep finding a local file gone while B2 has the copy, the legacy-tier migration) still happen: they move nothing.
@@ -14,7 +14,7 @@ presentation; they ask Media for an object and a delivery URL and never know whe
@@ -14,7 +14,7 @@ presentation; they ask Media for an object and a delivery URL and never know whe
**Online (the Media Fabric).** Every object a viewer can ask for has an immediately retrievable copy:
**Online (the Media Fabric).** Every object a viewer can ask for has an immediately retrievable copy:
```
```
-viewer → media.openvibe.network → delivery router
+viewer → openvibe.media → delivery router
├─ OpenVibe edge (the Media host's NVMe cache; later edge nodes) capacity-bounded, prepaid bandwidth
├─ OpenVibe edge (the Media host's NVMe cache; later edge nodes) capacity-bounded, prepaid bandwidth
├─ global CDN (Bunny in front of B2; Cloudflare only in front of R2)
├─ global CDN (Bunny in front of B2; Cloudflare only in front of R2)
└─ hot object tier: R2 Standard
└─ hot object tier: R2 Standard
@@ -3,7 +3,8 @@
@@ -3,7 +3,8 @@
Roadmap Wave 4 turns Media from predecessor-shaped VOD/clip/file tables into one canonical object
Roadmap Wave 4 turns Media from predecessor-shaped VOD/clip/file tables into one canonical object
platform. Every stored blob is a **media object** with an id `med_<ULID>`. The inherited
platform. Every stored blob is a **media object** with an id `med_<ULID>`. The inherited
`vods`, `clips`, `files` and screenshot `pastes` rows stay as they are, and each one is now a typed
`vods`, `clips`, `files` and screenshot `pastes` rows stay as they are, and each one is now a typed
-projection over an object through its `object_id` column. Every existing URL and v1 response is
+projection over an object through its `object_id` column. Pastes moved to OpenVibe.Community on
+2026-09-22: Media keeps the frozen `pastes` rows and their screenshot/avatar objects, read-only. Every existing URL and v1 response is
unchanged. New code talks to objects through the [v2 API](#object-api-v2).
unchanged. New code talks to objects through the [v2 API](#object-api-v2).
Code: `server/objects/` (`model.js`, `routes.js`, `backfill.js`, `reconcile.js`, `invariant.js`,
Code: `server/objects/` (`model.js`, `routes.js`, `backfill.js`, `reconcile.js`, `invariant.js`,
@@ -53,6 +54,10 @@ the transitional `media.media-ref@1` legacy form in `legacy_ref`, which is uniqu
@@ -53,6 +54,10 @@ the transitional `media.media-ref@1` legacy form in `legacy_ref`, which is uniqu
| avatar (screenshot paste with `metadata.kind = 'avatar'`) | avatar | `legacy:<app>:avatar:<slug>` |
| avatar (screenshot paste with `metadata.kind = 'avatar'`) | avatar | `legacy:<app>:avatar:<slug>` |
| `vods`/`clips.thumbnail_url` (`/t/<name>`) | thumbnail | `legacy:<app>:thumbnail:<name>` |
| `vods`/`clips.thumbnail_url` (`/t/<name>`) | thumbnail | `legacy:<app>:thumbnail:<name>` |
+Paste rows are frozen since pastes moved to OpenVibe.Community (2026-09-22): no new screenshot or
+avatar objects are created through Media's paste API, and the existing ones cannot be deleted
+(`DELETE` answers 409 `media.object.legacy_managed`).
+
Every v2 route that takes `:id` accepts either form. Native v2 objects have `legacy_ref = NULL`.
Every v2 route that takes `:id` accepts either form. Native v2 objects have `legacy_ref = NULL`.
### How a row maps to its object
### How a row maps to its object
@@ -519,7 +524,7 @@ While any hold applies:
@@ -519,7 +524,7 @@ While any hold applies:
- Quarantine cleanup and the boot junk sweep skip the object.
- Quarantine cleanup and the boot junk sweep skip the object.
- Finalize never deletes a held recording that turned out empty (no file, or zero bytes): it keeps the row and the file and settles it as failed (`missing_file` / `zero_byte`, quarantined, `vod.failed`).
- Finalize never deletes a held recording that turned out empty (no file, or zero bytes): it keeps the row and the file and settles it as failed (`missing_file` / `zero_byte`, quarantined, `vod.failed`).
- `deleteVodObjects` keeps the bytes.
- `deleteVodObjects` keeps the bytes.
- - Paste screenshot removal keeps the file.
+ - Paste screenshot removal no longer exists (the paste API is read-only since pastes moved to OpenVibe.Community).
- A `BEFORE DELETE` trigger on each projected table, plus a trigger on `media_objects.lifecycle_status`, refuses the change, so paths nobody has hooked are still covered. The triggers are the `_v2` ones (clip-aware); an older database has its first-version guards replaced at start.
- A `BEFORE DELETE` trigger on each projected table, plus a trigger on `media_objects.lifecycle_status`, refuses the change, so paths nobody has hooked are still covered. The triggers are the `_v2` ones (clip-aware); an older database has its first-version guards replaced at start.
- **No tier move.** `moveToCold`, `moveToHot`, `promoteToR2` and `demoteFromR2` return `{ ok: false, held: true }` (the admin moves answer it, R2 moves are logged as `refused`), and the sweep counts these as `skippedHeld` instead of errors. A held native object is never promoted to or demoted from the R2 cache either (logged as `refused`; a hold placed while its bytes were being copied wins, and the unrecorded copy is removed). A hold freezes an object's placement: `moveToHot` is refused too, because restoring flips the row to local and drops an R2 copy. A clip cut from a held VOD in B2/R2 is cut from the cloud copy instead of fetching the VOD home. Row updates that only record where the bytes already are (the sweep finding a local file gone while B2 has the copy, the legacy-tier migration) still happen: they move nothing.
- **No tier move.** `moveToCold`, `moveToHot`, `promoteToR2` and `demoteFromR2` return `{ ok: false, held: true }` (the admin moves answer it, R2 moves are logged as `refused`), and the sweep counts these as `skippedHeld` instead of errors. A held native object is never promoted to or demoted from the R2 cache either (logged as `refused`; a hold placed while its bytes were being copied wins, and the unrecorded copy is removed). A hold freezes an object's placement: `moveToHot` is refused too, because restoring flips the row to local and drops an R2 copy. A clip cut from a held VOD in B2/R2 is cut from the cloud copy instead of fetching the VOD home. Row updates that only record where the bytes already are (the sweep finding a local file gone while B2 has the copy, the legacy-tier migration) still happen: they move nothing.