openvibers/openvibe.media Commits

Emit placement provider events under the contracted media namespace (#19)

OP OpenVibers committed 3 Oct 2026 · 9ebdf6d2b5 · 4 files, +10 −10

checks run by GitHub CI (test, test-pg) · CI ✓ 3/3 · review SHIP by DeepSeek harness

https://github.com/OpenVibers/OpenVibe.Media/pull/19

Changes

ddiff --git a/docs/media-fabric.md b/docs/media-fabric.md
iindex 1086371..ac22502 100644
---- a/docs/media-fabric.md
++++ b/docs/media-fabric.md
@@@ -161,14 +161,14 @@ request/playback → telemetry → Events → {Hotness, Health, Cost} engines
(minutes–hours) ⊂ cost (hours–days). Each outer loop only constrains the inner (budgets bound placement; placement
bounds routes); every loop uses hysteresis and rate limits.
- **Events emitted:** `media.object.hot`, `media.replica.requested|ready|draining|evicted`, `media.variant.requested|ready`,
-- `media.region.hot`, `media.delivery.surge|degraded`, `provider.health.degraded`, `provider.capacity.warning`,
-- `provider.cost.threshold`.
++ `media.region.hot`, `media.delivery.surge|degraded`, `media.provider.health_degraded`, `media.provider.capacity_warning`,
++ `media.provider.cost_threshold`.
## 9. Cost truth
`media.cost_tiers` holds list prices; estimates drive decisions; **provider bills calibrate them**: R2 analytics
(GraphQL), B2 usage reports, Bunny statistics API are imported daily, compared with the estimate, and the gap is a
--metric. Budgets per class and provider with a forecast; `provider.cost.threshold` at 90 % shifts non-critical traffic.
++metric. Budgets per class and provider with a forecast; `media.provider.cost_threshold` at 90 % shifts non-critical traffic.
## 10. Build order
ddiff --git a/server/events.js b/server/events.js
iindex 8a4c973..50cddab 100644
---- a/server/events.js
++++ b/server/events.js
@@@ -46,7 +46,7 @@ const TYPES = {
const PLACEMENT_TYPES = new Set([
'media.replica.requested', 'media.replica.ready', 'media.replica.draining', 'media.replica.evicted',
'media.object.hot', 'media.region.hot', 'media.delivery.surge', 'media.delivery.degraded',
-- 'provider.health.degraded', 'provider.capacity.warning', 'provider.cost.threshold',
++ 'media.provider.health_degraded', 'media.provider.capacity_warning', 'media.provider.cost_threshold',
]);
// media.job.<transition>: progress is low priority, the outcome (and a proposal waiting for its owner) important.
ddiff --git a/server/placement/providers.js b/server/placement/providers.js
iindex 9491b09..8f9ee87 100644
---- a/server/placement/providers.js
++++ b/server/placement/providers.js
@@@ -120,7 +120,7 @@ function setCapabilities(name, caps) {
const prev = classesOf(old);
const next = classesOf(caps);
if (next.length < prev.length) {
-- void announceProviderEvent('provider.capacity.warning', name, { classes: next, previous_classes: prev });
++ void announceProviderEvent('media.provider.capacity_warning', name, { classes: next, previous_classes: prev });
}
}
state.capabilities[name] = caps;
@@@ -249,7 +249,7 @@ async function liveHealth(name) {
const wasHealthy = state.lastHealthCheck[name] != null && state.lastHealthError[name] == null;
state.lastHealthCheck[name] = Date.now();
state.lastHealthError[name] = err.message || String(err);
-- if (wasHealthy) void announceProviderEvent('provider.health.degraded', name, { healthy: false, last_error: state.lastHealthError[name] });
++ if (wasHealthy) void announceProviderEvent('media.provider.health_degraded', name, { healthy: false, last_error: state.lastHealthError[name] });
const ms = state.lastHealthCheck[name] - started;
observe(name, 'head', false, ms);
return { configured: true, healthy: false, lastCheck: state.lastHealthCheck[name], error: state.lastHealthError[name], latencyMs: ms };
ddiff --git a/test/placement-events.test.js b/test/placement-events.test.js
iindex 3813e5f..e316dfb 100644
---- a/test/placement-events.test.js
++++ b/test/placement-events.test.js
@@@ -146,15 +146,15 @@ const path = require('path');
assert.strictEqual((await providers.liveHealth('r2')).healthy, true);
healthy = false;
assert.strictEqual((await providers.liveHealth('r2')).healthy, false);
-- await waitFor(async () => (await rowsFor('r2')).some((e) => e.event_type === 'provider.health.degraded'));
++ await waitFor(async () => (await rowsFor('r2')).some((e) => e.event_type === 'media.provider.health_degraded'));
await providers.liveHealth('r2');
-- assert.strictEqual((await rowsFor('r2')).filter((e) => e.event_type === 'provider.health.degraded').length, 1);
++ assert.strictEqual((await rowsFor('r2')).filter((e) => e.event_type === 'media.provider.health_degraded').length, 1);
providers._setCapabilities('r2', { passed: true, capabilities: { range: true, range_206: true } });
providers._setCapabilities('r2', { passed: true, capabilities: { range: true, range_206: false } });
-- await waitFor(async () => (await rowsFor('r2')).some((e) => e.event_type === 'provider.capacity.warning'));
++ await waitFor(async () => (await rowsFor('r2')).some((e) => e.event_type === 'media.provider.capacity_warning'));
providers._setCapabilities('r2', { passed: true, capabilities: { range: true, range_206: false } });
rows = await rowsFor('r2');
-- assert.deepStrictEqual(rows.map((e) => e.event_type), ['provider.health.degraded', 'provider.capacity.warning']);
++ assert.deepStrictEqual(rows.map((e) => e.event_type), ['media.provider.health_degraded', 'media.provider.capacity_warning']);
assert.deepStrictEqual(rows.map((e) => e.subject), [{ type: 'provider', id: 'r2' }, { type: 'provider', id: 'r2' }]);
assert.deepStrictEqual(rows[1].payload, { app_id: null, provider: 'r2', classes: [], previous_classes: ['online-hot'] });
console.log('✅ provider transitions: one health degradation and one class-loss warning');