What shipped on OpenVibe.Media
Every change deployed to OpenVibe, newest first. Each line is a commit from the OpenVibers repositories, linked to the change itself. When enough have gathered, or a large feature lands, they are written up as Patch notes on openvibe.blog. JSON: /api/v1/changelog.
2026-09-24
9de5cb9Channel lookups through Live's canonical lineage resolver (D20): the dev-data API's @username selector asks OpenVibe.Live /internal/lineage/resolve with Media's service token (live.lineage.resolve) for the channel and its Live user id…
984e7adSECURITY.md: how to report a vulnerability ([email protected], 7-day reply, scope, supported versions)
7114433R2 tier decisions are recorded and explainable: every promotion to and demotion from the R2 popularity cache gets a media_tier_decisions row with the inputs it saw (view_count, last_accessed_at, provider, recording, hold, size…
5099c97thumbnail.regenerate refuses a recording with nothing to take a frame from. The five failed jobs on production (all at 2026-09-23 21:36:41, created by app:live, generate_failed) were Live asking for thumbnails of VODs 3044, 5537, 5540…
039dc48Events media.object.deleted and media.object.visibility_changed through the outbox, so consumers (Search, Live, Community) learn that an object went away or stopped being public (Media emitted neither). Payloads are minimal…
a90d0edCrawler basics at the openvibe.media origin: /robots.txt (openvibe-shared seo.robotsTxt: AI and search crawlers named; /api/, /auth/, /internal/, /o/, /metrics, /release-metrics and the /live/ dev-data endpoints disallowed; the sitemap)…
cb17f29RTMP pull SSRF: POST /vods/:id/ingest/rtmp makes ffmpeg connect to the URL it is given, and any rtmp:// or rtmps:// URL was accepted, so an app key (or a leaked one) could point Media's ffmpeg at any host and port it can reach. The…
e5e6f09object.hash job: sha256 for local copies in bounded batches, recorded on the object (content_hash, with metadata.hash_basis = the file and size it read) and on its local location (checksum, verified_at). Only 14 of 2,839 ready objects had…
47b4349trust proxy is loopback only (was true): req.ip is the X-Forwarded-For that the local nginx set when a request came through it, and the socket address for anyone else, so a caller reaching port 4100 directly (HOST defaults to 0.0.0.0) can…
20a602evod.duration.reconcile: stored VOD durations are compared with a measurement of the real file in bounded batches, offloaded VODs included, and clearly wrong values are repaired (the health scanner only repaired durations <= 0 and skipped…
24f1962vod.finalize job: a recording whose finalize failed or never ran is retried on media_jobs with backoff, instead of a one-shot boot sweep whose failure was swallowed and a stat failure that only cleared is_recording. The orphan sweep (in…
cc84bf1VOD durations are measured, never estimated: finalize stores what the file says and vods.duration_source says where it came from. probe = ffprobe's container duration of the finished file; remux = the last packet time of a stream-copy pass…
2026-09-23
0724119objects-verify test: read the re-upload from the run that first checked the object, not from media_verifications, which keeps only the latest verdict; when batch rotation re-verified the object in a later run (same-second verified_at…
6c2d384Opening the database no longer fails a clip being cut: getDb() ran the restart recovery ('processing' clips → failed) every time any process opened media.db, so a backfill or operator script run on the host while the service cut a clip…
ce6a02agitleaks: allow the owner-subject test's made-up INTERNAL_API_KEY, matched on path and value
31c2899Owner subjects: every media object that names an app-local owner (owner_app + owner_user_id) gets the owner's canonical Network subject in owner_subject, so Live's lineage resolver and other consumers read owner.subject instead of…
0c27f0bopenvibe-shared v1.5.1 (Track R release manifest), openvibe-contracts v0.33.0, openvibe-sdk v0.5.0
6e74eb0Restore drills: MEDIA_DRILL=1 makes Media a read-only instance on a restored copy of media.db, so `ovhost drill media` can boot one next to production. It refuses to start unless DB_PATH is set and outside the checkout and…
ee39be1Clips say who made them: POST /clips keeps auto_generated and the description when the app itself asks (an app-key call with no X-OV-User-Id), and never when the call acts for one of the app's users. Both fields were dropped, so every AI…
ad2217bJob events carry a projection, not the job: media.job.<transition> payloads are queue.jobEvent (id, app_id, object_id, type, status, attempts, max_attempts, error_code, cancel_requested, has_result, run_after, decided_at, created_at…
5d44df5Docs: the single-part PUT's refusals include the multipart size, content type and open-session answers
0ffbde5Multipart complete is idempotent: completing a session that already completed (a retry after its answer was lost) returns the ready object instead of 409, as the single-part complete does; the SDK objects client (openvibe-sdk 0.5.0)…
d006749Wave 4: media job system, multipart and presigned uploads, R2 eviction drill
9de4b25/p/:slug/screenshot: a slug Media doesn't hold (pastes made in Community since the move) goes to Community like /p/:slug and /raw; a private one answers the same way. Hero-moment thumbnails linked here and showed broken images
454dc09CI: run the shared security workflow (gitleaks secret scan + dependency audit, Track Q)
cbf8410Media's Events outbox row is written in the state change's transaction: webhooks.announce() runs the change (VOD ready/quarantine/delete, clip ready/failed, object ready + invariant row) and events.record() in ONE SQLite transaction, then…
22ba4aaDocs: STATUS.json and README match production (2026-09-23)
e42af9eScheduled copy verification: every 10 min verify the 50 least recently verified ready objects (local stat + sha256 up to 64 MB, B2/R2 HEAD + size), record verdicts on media_locations, media_verifications and media_verify_runs, restore a…
dfb2172Private items answer exactly like missing ones: /v and /c returned 403 "This media is private", which confirmed that a private recording (or, through the basename form of /v, a file name) exists; now the same 404 and body as an unknown id…
2601b0esystemd: TimeoutStopSec 30 → 80 so a stop during a recording lets the recorder's 60 s trailer grace (and the 70 s forced exit) finish instead of SIGKILL cutting the file
4b99e02nginx: client address headers only from $remote_addr (realip). X-Forwarded-For was appended to and CF-Connecting-IP passed through from the client, so a request reaching the origin without Cloudflare (DNS-only host or the bare IP) chose…
20189e9Developer-project tenants: no inline HTML/SVG from /f, race-free files quota, retained deletes count
1c8f67dopenvibe-contracts v0.28.0
bfc9e1bDeveloper-project tenants (Wave 20, ADR-014): app tokens get a Media tenant per project
d3f81b9Token-only tenants for wiki and blog (10 GB each)
3007461Token-only tenants for tools (20 GB, job results) and games (10 GB, map-editor assets)
e5525bdTruthful GET /api/ready and loopback-only GET /metrics (openvibe-shared v1.3.0)
927d451GET /release.json (ADR-016); openvibe-shared v1.2.0
1122f52Media outcomes as durable events: vod/clip ready|failed, object uploaded and storage alerts also go to OpenVibe.Events through an outbox (openvibe-sdk 0.2.2); webhooks unchanged; openvibe-contracts v0.8.0
76f3a44openvibe-shared is the pinned OpenVibe.Shared v1.0.0 release (vendor/ is gone); openvibe-contracts v0.7.0
cced10fCanonical object model (Wave 4): media_objects with locations, relationships, variants, jobs, retention holds and the public-size invariant; vods/clips/files/screenshot pastes become projections via object_id
2026-09-22
ddfefcfPaste cutover switches: PASTES_FROZEN_APPS answers 410 to paste writes for apps that moved to Community; PASTES_MOVED_TO 301s /p/:slug and its raw text there; screenshot bytes stay served here
747d7dbopenvibe-contracts v0.5.1
2db74cfService-principal tokens on the tenant API; token-only 'community' tenant; paste export bundle for Community's importer
13fcdfeCI: npm test on Node 22 on every push to main and every PR
2026-09-21
2026-09-18
28c1009Avatar ingest: pinned lookup answers both the single and the list form
145b45aAvatar ingestion: fetch a user-supplied link safely (public addresses only, pinned connection, size and redirect caps), re-encode with sharp, store as an unlisted avatar paste; internal-only route
4214448vendor sync: shared navbar (drawer, chips, sections)