openvibers/openvibe.media Commits
Readiness: virtual clips are not objects with no good copy (their bytes are their VOD's; five held /api/ready at degraded) (#138)
Changes
@@@ -3,7 +3,7 @@** A ready object has NO GOOD COPY when none of its locations is `present` (verified there) or* `pending` (not verified yet, so possibly fine). That covers objects whose every copy was found-- * missing/corrupt, and ready objects with no location row at all.++ * missing/corrupt, and ready objects with no location row at all, except virtual clips (a window over their VOD).** Pure SQL over a small `q` interface — { all(sql, params), get(sql, params) } — so the service* (server/db/database.js), the metrics/readiness checks and the read-only report script@@@ -12,8 +12,12 @@*/'use strict';++// A virtual clip (clip-jobs makeVirtual: a window over its VOD's timeline) has no copy of its own by design: its bytes++// are its VOD's, whose object is checked on its own. The same test as verify-job's selection; five of them held++// /api/ready at "degraded" (2026-10-17).const NO_GOOD_COPY_WHERE = `o.lifecycle_status = 'ready'-- AND NOT EXISTS (SELECT 1 FROM media_locations l WHERE l.object_id = o.id AND l.state IN ('present', 'pending'))`;++ AND NOT EXISTS (SELECT 1 FROM media_locations l WHERE l.object_id = o.id AND l.state IN ('present', 'pending'))++ AND NOT (o.kind = 'clip' AND (CASE WHEN json_valid(o.metadata) THEN json_extract(o.metadata, '$.virtual') END) IS NOT NULL)`;async function countNoGoodCopy(q, { appId = null } = {}) {const r = await q.get(`SELECT COUNT(*) AS n FROM media_objects o WHERE ${NO_GOOD_COPY_WHERE}${appId ? ' AND o.app_id = ?' : ''}`, appId ? [appId] : []);@@@ -157,7 +157,10 @@ const crypto = require('crypto');console.log('✅ never deletes: files, objects, locations and legacy rows all remain');// ── Count, /metrics and /api/ready ──-- assert.strictEqual(await copyReport.countNoGoodCopy(db), 3, 'c (remote corrupt), d (local gone), h (no locations)');++ // A virtual clip (clip-jobs makeVirtual) has no copy of its own by design: its bytes are its VOD's.++ await model.createObject({ app_id: 'live', kind: 'clip', lifecycle_status: 'ready', size_bytes: 0,++ owner_user_id: 7, owner_subject: 'usr_01JAB2C3D4E5F6G7H8J9K0MNPQ', mime_type: 'video/mp4', metadata: { virtual: true } });++ assert.strictEqual(await copyReport.countNoGoodCopy(db), 3, 'c (remote corrupt), d (local gone), h (no locations); not the virtual clip');const st = await job.status();assert.strictEqual(st.no_good_copy, 3);assert.strictEqual(st.verified_objects, READY);
@@ -3,7 +3,7 @@
@@ -3,7 +3,7 @@
*
*
* A ready object has NO GOOD COPY when none of its locations is `present` (verified there) or
* A ready object has NO GOOD COPY when none of its locations is `present` (verified there) or
* `pending` (not verified yet, so possibly fine). That covers objects whose every copy was found
* `pending` (not verified yet, so possibly fine). That covers objects whose every copy was found
- * missing/corrupt, and ready objects with no location row at all.
+ * missing/corrupt, and ready objects with no location row at all, except virtual clips (a window over their VOD).
*
*
* Pure SQL over a small `q` interface — { all(sql, params), get(sql, params) } — so the service
* Pure SQL over a small `q` interface — { all(sql, params), get(sql, params) } — so the service
* (server/db/database.js), the metrics/readiness checks and the read-only report script
* (server/db/database.js), the metrics/readiness checks and the read-only report script
@@ -12,8 +12,12 @@
@@ -12,8 +12,12 @@
*/
*/
'use strict';
'use strict';
+// A virtual clip (clip-jobs makeVirtual: a window over its VOD's timeline) has no copy of its own by design: its bytes
+// are its VOD's, whose object is checked on its own. The same test as verify-job's selection; five of them held
+// /api/ready at "degraded" (2026-10-17).
const NO_GOOD_COPY_WHERE = `o.lifecycle_status = 'ready'
const NO_GOOD_COPY_WHERE = `o.lifecycle_status = 'ready'
- AND NOT EXISTS (SELECT 1 FROM media_locations l WHERE l.object_id = o.id AND l.state IN ('present', 'pending'))`;
+ AND NOT EXISTS (SELECT 1 FROM media_locations l WHERE l.object_id = o.id AND l.state IN ('present', 'pending'))
+ AND NOT (o.kind = 'clip' AND (CASE WHEN json_valid(o.metadata) THEN json_extract(o.metadata, '$.virtual') END) IS NOT NULL)`;
async function countNoGoodCopy(q, { appId = null } = {}) {
async function countNoGoodCopy(q, { appId = null } = {}) {
const r = await q.get(`SELECT COUNT(*) AS n FROM media_objects o WHERE ${NO_GOOD_COPY_WHERE}${appId ? ' AND o.app_id = ?' : ''}`, appId ? [appId] : []);
const r = await q.get(`SELECT COUNT(*) AS n FROM media_objects o WHERE ${NO_GOOD_COPY_WHERE}${appId ? ' AND o.app_id = ?' : ''}`, appId ? [appId] : []);
@@ -157,7 +157,10 @@ const crypto = require('crypto');
@@ -157,7 +157,10 @@ const crypto = require('crypto');
console.log('✅ never deletes: files, objects, locations and legacy rows all remain');
console.log('✅ never deletes: files, objects, locations and legacy rows all remain');
// ── Count, /metrics and /api/ready ──
// ── Count, /metrics and /api/ready ──
- assert.strictEqual(await copyReport.countNoGoodCopy(db), 3, 'c (remote corrupt), d (local gone), h (no locations)');
+ // A virtual clip (clip-jobs makeVirtual) has no copy of its own by design: its bytes are its VOD's.
+ await model.createObject({ app_id: 'live', kind: 'clip', lifecycle_status: 'ready', size_bytes: 0,
+ owner_user_id: 7, owner_subject: 'usr_01JAB2C3D4E5F6G7H8J9K0MNPQ', mime_type: 'video/mp4', metadata: { virtual: true } });
+ assert.strictEqual(await copyReport.countNoGoodCopy(db), 3, 'c (remote corrupt), d (local gone), h (no locations); not the virtual clip');
const st = await job.status();
const st = await job.status();
assert.strictEqual(st.no_good_copy, 3);
assert.strictEqual(st.no_good_copy, 3);
assert.strictEqual(st.verified_objects, READY);
assert.strictEqual(st.verified_objects, READY);