openvibers/openvibe.network Commits

Plan T2, resource registry SLICES 3 and 4 together (#9)

OP OpenVibers committed 2 Oct 2026 · d6ed8c4d19 · 3 files, +58 −4

local checks ✓ 3/3 · CI ✓ 3/3 · review SHIP by DeepSeek harness

https://github.com/OpenVibers/OpenVibe.Network/pull/9

Changes

ddiff --git a/docs/t2-resource-registry.md b/docs/t2-resource-registry.md
iindex 71932cf0..8bb418e1 100644
---- a/docs/t2-resource-registry.md
++++ b/docs/t2-resource-registry.md
@@@ -189,6 +189,37 @@ code. The contract between the two is therefore exact:
- `capabilities[]` items are `namespace:name` strings (`node:http`, `events:gateway`, `object:r2`, …) — the contract's
own pattern; they are stored, not interpreted.
++### Worked example — `GET /api/v1/resources?kind=provider` → `plan()`
++
++Two provider offers in one region, both `health.status: 'up'` and priced per request at `$0.02` and `$0.03`:
++
++```json
++GET /api/v1/resources?kind=provider
++{ "offers": [
++ { "offer_id": "p-provider", "kind": "provider", "region": "us-central", "trust": "community",
++ "health": { "status": "up" }, "pricing": { "model": "per-request", "marginal_usd_per_unit": 0.02 } },
++ { "offer_id": "p-provider-b", "kind": "provider", "region": "us-central", "trust": "community",
++ "health": { "status": "up" }, "pricing": { "model": "per-request", "marginal_usd_per_unit": 0.03 } }
++ ] }
++```
++
++That `offers` array goes to `plan()` unchanged:
++
++```js
++const req = { kind: 'request', mobility: 'request', latency_class: 'interactive',
++ objective: 'cheapest', region: 'us-central', capabilities: [], units: 1 };
++const result = require('openvibe-sdk/placement').plan(req, response.offers, { now });
++// → { selected: 'p-provider', objective: 'cheapest',
++// reasons: ['objective cheapest'], candidates: [ … ], decided_at: '<now as ISO>' }
++```
++
++The fields that matter: `selected` is the cheaper eligible offer (`p-provider`, `$0.02` < `$0.03`); every offer is
++listed in `candidates` with `eligible: true` and its `estimated_cost_usd` (from `pricing.marginal_usd_per_unit`,
++since the public list has no `capacity` to price prepaid capacity from); and the whole result validates as
++`platform.placement-result@1`. `test/resource-registry.test.js` test 9 does exactly this — reporting a node offer
++at `$0.05` alongside the two providers and feeding the **unfiltered** list, with `region: 'us-central'` narrowing
++eligibility to the three — and asserts both the contract validity and the same winner.
++
## 6. Tests — `test/resource-registry.test.js`
Modelled on `test/nodes.test.js`: temp dir + `getDb()`, `oauth_clients.client_secret` set for `host` and `live`, a
@@@ -234,10 +265,10 @@ filter path — PGlite and a real PostgreSQL must agree on it — and `npm test`
2. **Public read + filters.** `list()`, `get()`, `routers().pub` in `server/registry/offers.js`,
`app.use('/api/v1/resources', pub)`, tests 5-7. Also the full internal read (`GET /internal/resources[/:id]`, the
report's guard), because the public read leaves capacity out (§4).
--3. **Discovery entry.** Add `resources: '/api/v1/resources'` to the `/api/v1/registry` index in
++3. **Discovery entry.** (done) Add `resources: '/api/v1/resources'` to the `/api/v1/registry` index in
`server/registry/ecosystem.js`. Nothing else.
--4. **Placement consumption proof.** §5 gains a worked example; test 9 added. No production-code change — this slice
-- exists to fail loudly if the response shape drifts from what `plan()` accepts.
++4. **Placement consumption proof.** (done) §5 gains a worked example; test 9 added. No production-code change — this
++ slice exists to fail loudly if the response shape drifts from what `plan()` accepts.
5. **Contracts bump + kind widening.** *Blocked on T1.* `package.json:30` pin bump, a
`node -e "validate('platform.storage-offer@1', …)"` smoke check, `migrations/0009_resource_offer_kinds.sql` widening
the `kind` CHECK to the published kinds, the one-line capability switch, a `KIND → CONTRACT` map used for per-kind
ddiff --git a/server/registry/ecosystem.js b/server/registry/ecosystem.js
iindex da4f203b..4510d2bb 100644
---- a/server/registry/ecosystem.js
++++ b/server/registry/ecosystem.js
@@@ -28,6 +28,8 @@
* library, repository, planned), each rule stated
* GET /api/v1/registry/featured public sites that are up, most used first (the navbar's
* usage ranking), with when that use was counted
++ * GET /api/v1/resources[?kind=&region=&trust=&status=&cell=&max_price_usd=]
++ * resource offers (server/registry/offers.js)
* GET /contracts/<domain>/<name>.v<N>.json the schema at its $id URL
*/
const express = require('express');
@@@ -324,7 +326,7 @@ function createEcosystemRegistry({ issuer, internalOverrides = {}, fetchImpl = g
const notFound = (res, code, detail) => contracts.http.sendProblem(res, 404, code, { detail });
r.get('/.well-known/openvibe', (_req, res) => cache(res, 300).json(descriptor()));
-- r.get('/api/v1/registry', (_req, res) => cache(res).json({ services: '/api/v1/registry/services', capabilities: '/api/v1/registry/capabilities', namespaces: '/api/v1/registry/namespaces', contracts: '/api/v1/registry/contracts', topics: '/api/v1/registry/topics', releases: '/api/v1/registry/releases', health: '/api/v1/registry/health', search: '/api/v1/registry/search?q=', domains: '/api/v1/registry/domains/:domain', categories: '/api/v1/registry/categories', featured: '/api/v1/registry/featured' }));
++ r.get('/api/v1/registry', (_req, res) => cache(res).json({ services: '/api/v1/registry/services', capabilities: '/api/v1/registry/capabilities', namespaces: '/api/v1/registry/namespaces', contracts: '/api/v1/registry/contracts', topics: '/api/v1/registry/topics', releases: '/api/v1/registry/releases', health: '/api/v1/registry/health', search: '/api/v1/registry/search?q=', domains: '/api/v1/registry/domains/:domain', categories: '/api/v1/registry/categories', featured: '/api/v1/registry/featured', resources: '/api/v1/resources' }));
r.get('/api/v1/registry/categories', (_req, res) => cache(res, 30).json({ categories: categories(), checked_at: lastPollAt ? new Date(lastPollAt).toISOString() : null }));
r.get('/api/v1/registry/categories/:id', (req, res) => {
const c = categories().find(x => x.id === String(req.params.id));
ddiff --git a/test/resource-registry.test.js b/test/resource-registry.test.js
iindex d16b2b05..cc2f7eea 100644
---- a/test/resource-registry.test.js
++++ b/test/resource-registry.test.js
@@@ -36,6 +36,7 @@ app.use('/oauth', require('../server/auth/oauth-routes'));
const r = offers.routers({ guard: principals.guard('network.node.report', { legacy: false }) });
app.use('/api/v1/resources', r.pub);
app.use('/internal/resources', r.internal);
++app.use(require('../server/registry/ecosystem').createEcosystemRegistry().router());
const server = http.createServer(app);
const offer = (id, extra = {}) => ({ offer_id: id, kind: 'node', node_id: id, provider: 'ovh', region: 'us-west', cell: 'wnam-1', trust: 'first-party', capabilities: ['node:http', 'events:gateway'], capacity: { cpu: { utilization: 0.2, available_cores: 6 } }, health: { status: 'up', checked_at: '2026-10-01T12:00:00Z' }, pricing: { model: 'prepaid', marginal_usd_per_unit: 0.01, unit: 'request' }, updated_at: '2026-10-01T12:00:00Z', ...extra });
@@@ -234,6 +235,26 @@ const assertLockstep = async () => {
assert.strictEqual(x.headers.get('cache-control'), 'no-store');
assert.strictEqual(x.headers.get('access-control-allow-origin'), '*');
assert.strictEqual(x.headers.get('timing-allow-origin'), '*');
++
++ // 9. Placement proof (slice 4): the public list feeds openvibe-sdk/placement unchanged. Three up offers in
++ // their own region — two providers and a node, different marginal prices; `cheapest` must pick the cheapest
++ // eligible one from a result that matches platform.placement-result@1.
++ x = await post({ source: 'proof', offers: [
++ offer('p-node', { region: 'us-central', trust: 'community', pricing: { model: 'per-request', marginal_usd_per_unit: 0.05, unit: 'request' } }),
++ offer('p-provider', { kind: 'provider', node_id: undefined, adapter: 'nats-v1', region: 'us-central', trust: 'community', pricing: { model: 'per-request', marginal_usd_per_unit: 0.02, unit: 'request' } }),
++ offer('p-provider-b', { kind: 'provider', node_id: undefined, adapter: 'http-v1', region: 'us-central', trust: 'community', pricing: { model: 'per-request', marginal_usd_per_unit: 0.03, unit: 'request' } }),
++ ] }, auth);
++ assert.strictEqual(x.status, 200, JSON.stringify(x.body));
++ const idx = await get('/api/v1/registry');
++ assert.strictEqual(idx.body.resources, '/api/v1/resources', 'the registry index advertises the resource list');
++ const pub = await get('/api/v1/resources');
++ assert.strictEqual(pub.status, 200);
++ const req = { kind: 'request', mobility: 'request', latency_class: 'interactive', objective: 'cheapest', region: 'us-central', capabilities: [], units: 1 };
++ const result = require('openvibe-sdk/placement').plan(req, pub.body.offers, { now });
++ const check = validate('platform.placement-result@1', result);
++ assert.ok(check.valid, JSON.stringify(check.errors));
++ assert.strictEqual(result.selected, 'p-provider', 'the cheaper eligible offer wins');
++ assert.deepStrictEqual(result.candidates.filter((c) => c.eligible).map((c) => c.id).sort(), ['p-node', 'p-provider', 'p-provider-b']);
console.log('resource-registry: all tests passed');
} finally {
server.close();