openvibers/openvibe.codes Commits

Publish harness offers as Fabric resource offers of kind harness (#9)

OP OpenVibers committed 3 Oct 2026 · b612319c7b · 7 files, +79 −39

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

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

Changes

ddiff --git a/README.md b/README.md
iindex ee31289..71c68b6 100644
---- a/README.md
++++ b/README.md
@@@ -48,7 +48,7 @@ Everything is server-rendered and usable without JavaScript. The only scripts of
- **OpenVibe.Events** — the outbox relay publishes `codes.app.*` with Codes' service token (`events.event.publish`); the Events playground calls it with the app's token (`events.app.publish`); the project archive pulls the project's app events with a Network export token (`events.app.read`).
- **OpenVibe.Media** — the Media playground uploads with the app's token into the project's namespace; the project archive lists the project's objects, namespaces and download URLs with a Network export token (`media.object.list`, `media.object.read`).
- **PostgreSQL 18 and Valkey 9** (OpenVibe.Host `roles/data/`, ADR-035): every read and write is async through `openvibe-sdk/db`; Valkey holds the per-actor limit counters (optional).
--- **openvibe-contracts** v0.84.0, **openvibe-sdk v0.26.0**, **openvibe-shared v2.4.0** (pinned tag tarballs; the docs show the tag and the package version, and say so when they differ).
++- **openvibe-contracts** v0.86.0, **openvibe-sdk v0.26.0**, **openvibe-shared v2.4.0** (pinned tag tarballs; the docs show the tag and the package version, and say so when they differ).
No path in Codes sends or accepts a shared loopback key (tested by grep and at runtime).
@@@ -69,7 +69,7 @@ In Network (`server/identity/principals.js` / `server/db/database.js`, as for co
- OAuth client `codes`, name `OpenVibe.Codes`, redirect URI `https://openvibe.codes/auth/callback`.
- Service grant `['codes', 'events.event.publish', 'openvibe.events', []]` (the outbox relay).
--The Codes service manifest, `codes.release.manage|read` and `codes.app-manifest@1` are released in openvibe-contracts (tag v0.27.0; this repository pins v0.84.0); the CI contracts check is blocking.
++The Codes service manifest, `codes.release.manage|read` and `codes.app-manifest@1` are released in openvibe-contracts (tag v0.27.0; this repository pins v0.86.0); the CI contracts check is blocking.
For the playgrounds to succeed end to end (not Codes' code; configuration elsewhere): Network `DEV_SANDBOX_AUDIENCES` including `openvibe.media` and `openvibe.events`, a staff-set allowance containing `media.object.upload` and `events.app.publish` for the project, a Media tenant keyed by the project id, and OpenVibe.Events serving `events.app.publish` for app tokens. On 2026-09-23 these were in place on production: a sandbox app uploaded to Media (tenant `prj_…-sandbox`) and published and read app events through public endpoints. That run used curl, not the Codes playgrounds, and it is not yet a committed, repeatable check.
ddiff --git a/STATUS.json b/STATUS.json
iindex d1070ba..0957311 100644
---- a/STATUS.json
++++ b/STATUS.json
@@@ -7,9 +7,9 @@
"runtime": "node 22, express 4, PostgreSQL 18 (openvibe-sdk/db, async) + Valkey 9",
"port": 4900,
"domain": "openvibe.codes",
-- "contracts": "openvibe-contracts v0.84.0",
++ "contracts": "openvibe-contracts v0.86.0",
"packages": [
-- "openvibe-contracts v0.84.0",
++ "openvibe-contracts v0.86.0",
"openvibe-sdk v0.26.0",
"openvibe-shared v2.4.0"
],
@@@ -30,7 +30,7 @@
"Policy pages rendered from the published ADRs; licensing read from package metadata; governance pages (code of conduct, contributing, contributor ladder, moderation policy) rendered from the repository's Markdown",
"Release API guarded by openvibe-contracts requireCapability (codes.release.manage, codes.release.read)",
"Readiness (/api/ready), /release.json and loopback-only /metrics",
-- "Coding-harness registry: a validated catalog of harnesses and their agents (capabilities, address, price and limits), a task placement adapter over openvibe-sdk/placement, and a public read API and read-only docs page (GET /api/v1/harnesses, POST /api/v1/harnesses/route, /docs/harnesses)"
++ "Coding-harness registry: a validated catalog of harnesses and their agents (capabilities, address, price and limits), a task placement adapter over openvibe-sdk/placement, and a public read API and read-only docs page (GET /api/v1/harnesses, POST /api/v1/harnesses/route, /docs/harnesses); each harness × agent pair is routed as a platform.resource-offer@1 of kind harness with task:<kind> and harness:<flag> capabilities and a platform.harness-offer@1 detail"
],
"notYet": [
"The playgrounds have never been used on production (0 playground runs); the one production end-to-end run used curl, and it is not a committed, repeatable check",
ddiff --git a/package-lock.json b/package-lock.json
iindex f2e621f..a188f4c 100644
---- a/package-lock.json
++++ b/package-lock.json
@@@ -16,7 +16,7 @@
"express-rate-limit": "^7.5.0",
"helmet": "^8.1.0",
"iovalkey": "^0.4.0",
-- "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.84.0",
++ "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.86.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.26.0",
"openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.4.0",
"pg": "^8.23.0"
@@@ -1157,9 +1157,8 @@
}
},
"node_modules/openvibe-contracts": {
-- "version": "0.84.0",
-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.84.0",
-- "integrity": "sha512-wnmQbzffd2xXSGk3Zr1JxZYIL4oSlnJTo8lGpUApBPoDKeQ3U4VCLJgt3cZpbtkbl5NH1qNjBDXkwx+uASwoyA==",
++ "version": "0.86.0",
++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.86.0",
"license": "AGPL-3.0-only",
"dependencies": {
"ajv": "^8.17.1",
ddiff --git a/package.json b/package.json
iindex 5ac363d..67e165d 100644
---- a/package.json
++++ b/package.json
@@@ -28,7 +28,7 @@
"express-rate-limit": "^7.5.0",
"helmet": "^8.1.0",
"iovalkey": "^0.4.0",
-- "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.84.0",
++ "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.86.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.26.0",
"openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.4.0",
"pg": "^8.23.0"
ddiff --git a/server/domain/harness-placement.js b/server/domain/harness-placement.js
iindex aecf048..d3f3328 100644
---- a/server/domain/harness-placement.js
++++ b/server/domain/harness-placement.js
@@@ -7,33 +7,42 @@
const contracts = require('openvibe-contracts');
const { plan } = require('openvibe-sdk/placement');
--// Every coding harness offers edit and review until task capabilities reach harness-offer@1.
--const BASE_CAPABILITIES = ['edit', 'review'];
--const HARNESS_CAPABILITIES = ['host_access', 'mcp', 'long_autonomy', 'resume'];
++// A catalog row without task_capabilities takes edit and review.
++const DEFAULT_TASKS = ['edit', 'review'];
++// harness-offer@1 capability flag → the resource-offer@1 capability it adds when true.
++const HARNESS_FLAGS = { host_access: 'harness:host-access', mcp: 'harness:mcp', long_autonomy: 'harness:long-autonomy', resume: 'harness:resume' };
const TASKS = Object.freeze({
-- edit: ['edit'],
-- review: ['review'],
-- long: ['edit', 'long_autonomy'],
-- resume: ['resume'],
++ edit: ['task:edit'],
++ review: ['task:review'],
++ long: ['task:edit', 'harness:long-autonomy'],
++ resume: ['harness:resume'],
});
++/** One platform.resource-offer@1 of kind harness; its detail is the catalog row as platform.harness-offer@1. */
function toOffer(harness, agent, opts = {}) {
-- const caps = harness.capabilities || {};
-- const limits = harness.limits || {};
++ const { agents, ...row } = harness;
++ const caps = row.capabilities || {};
++ const limits = row.limits || {};
++ const tasks = row.task_capabilities || DEFAULT_TASKS;
++ const maxContextTokens = (agent.context_limits && agent.context_limits.input_tokens) ?? limits.max_context_tokens;
const health = typeof opts.health === 'string' ? { status: opts.health } : opts.health || { status: 'up' };
const offer = {
offer_id: `${harness.id}:${agent.id || agent.model}`,
-- kind: 'provider',
++ kind: 'harness',
provider: agent.provider || harness.provider,
region: 'global',
trust: opts.trust || 'external',
-- capabilities: [...BASE_CAPABILITIES, ...HARNESS_CAPABILITIES.filter((name) => caps[name] === true)],
++ capabilities: [...tasks.map((task) => `task:${task}`), ...Object.keys(HARNESS_FLAGS).filter((name) => caps[name] === true).map((name) => HARNESS_FLAGS[name])],
capacity: { workers: { harness: limits.max_concurrent_runs } },
health,
pricing: { model: 'per-operation', unit: 'token', marginal_usd_per_unit: agent.price_per_1k_tokens.fresh_usd / 1000 },
-- // Not an SDK Offer field: the planner ignores it; kept for callers sizing a run.
-- max_context_tokens: (agent.context_limits && agent.context_limits.input_tokens) ?? limits.max_context_tokens,
++ updated_at: new Date(opts.now ?? Date.now()).toISOString(),
++ detail: {
++ ...row,
++ limits: maxContextTokens == null ? limits : { ...limits, max_context_tokens: maxContextTokens },
++ task_capabilities: [...tasks],
++ },
};
if (opts.latencyMs != null) offer.latency_ms = { run_p95: opts.latencyMs };
return offer;
@@@ -61,10 +70,10 @@ function route({ task, extra = {}, harnesses, opts = {}, now = Date.now() }) {
const req = requirementsFor(task, extra);
const offers = harnesses.list().flatMap((harness) => harnesses.agents(harness.id).map((agent) => {
const id = `${harness.id}:${agent.id || agent.model}`;
-- return toOffer(harness, agent, opts[id] || {});
++ return toOffer(harness, agent, { now, ...opts[id] });
}));
const result = plan(req, offers, { now });
-- // placement-result@1 types `selected` as a string, so a plan with no eligible candidate cannot validate.
++ // The Contracts compat gate refused widening placement-result@1 `selected` to null: a plan with nothing selected skips the check.
if (result.selected !== null) {
const check = contracts.validate('platform.placement-result@1', JSON.parse(JSON.stringify(result)));
if (!check.valid) {
ddiff --git a/test/docs.test.js b/test/docs.test.js
iindex 358b3b3..75238cb 100644
---- a/test/docs.test.js
++++ b/test/docs.test.js
@@@ -19,11 +19,11 @@ const sdkPkg = require('openvibe-sdk/package.json');
await check('package.json pins the contracts, SDK and shared tags that are installed', async () => {
const tag = (dep) => (pkg.dependencies[dep].match(/refs\/tags\/(v[0-9.]+)$/) || [])[1];
-- assert.strictEqual(tag('openvibe-contracts'), 'v0.84.0');
++ assert.strictEqual(tag('openvibe-contracts'), 'v0.86.0');
assert.strictEqual(tag('openvibe-sdk'), `v${sdkPkg.version}`);
assert.strictEqual(tag('openvibe-shared'), `v${require('openvibe-shared/package.json').version}`);
assert.strictEqual(sdkPkg.version, '0.26.0');
-- // The contracts tag v0.84.0 carries the released codes manifest and codes.app-manifest@1.
++ // The contracts tag v0.86.0 carries the released codes manifest and codes.app-manifest@1.
assert.ok(contracts.services.get('codes') && contracts.services.get('codes').status === 'alpha');
assert.ok(contracts.resolve('codes.app-manifest@1'));
});
@@@ -33,13 +33,13 @@ const sdkPkg = require('openvibe-sdk/package.json');
const r = await t.get(p);
assert.strictEqual(r.status, 200, p);
assert.ok(r.text.includes(`openvibe-contracts v${contractsVersion}`), `${p} names the installed contracts version`);
-- assert.ok(r.text.includes('OpenVibe.Contracts/tree/v0.84.0'), `${p} links the pinned tag`);
-- if (contractsVersion !== '0.84.0') assert.ok(r.text.includes('(tag v0.84.0)'), `${p} says the tag differs from the package version`);
++ assert.ok(r.text.includes('OpenVibe.Contracts/tree/v0.86.0'), `${p} links the pinned tag`);
++ if (contractsVersion !== '0.86.0') assert.ok(r.text.includes('(tag v0.86.0)'), `${p} says the tag differs from the package version`);
assert.ok(r.text.includes(`openvibe-sdk v${sdkPkg.version}`), `${p} names the SDK version`);
}
const v = (await t.get('/api/v1/docs/versions')).json();
assert.strictEqual(v.contracts, contractsVersion);
-- assert.strictEqual(v.contracts_tag, 'v0.84.0');
++ assert.strictEqual(v.contracts_tag, 'v0.86.0');
assert.strictEqual(v.sdk, sdkPkg.version);
});
ddiff --git a/test/harness-placement.test.js b/test/harness-placement.test.js
iindex 319a28f..108137c 100644
---- a/test/harness-placement.test.js
++++ b/test/harness-placement.test.js
@@@ -3,6 +3,7 @@
const assert = require('assert');
const fs = require('fs');
const path = require('path');
++const contracts = require('openvibe-contracts');
const { createHarnesses } = require('../server/domain/harnesses');
const { TASKS, toOffer, requirementsFor, route } = require('../server/domain/harness-placement');
const { check, done } = require('./helpers/boot');
@@@ -31,35 +32,55 @@ function priced(row, freshUsd) {
context_limits: { input_tokens: 100000, output_tokens: 8000 },
price_per_1k_tokens: { fresh_usd: 0.003, cached_usd: 0.0003, output_usd: 0.015 },
};
-- assert.deepStrictEqual(toOffer(harness, agent, { trust: 'partner', health: 'degraded', latencyMs: 900 }), {
++ const { agents, ...row } = harness;
++ assert.deepStrictEqual(toOffer(harness, agent, { trust: 'partner', health: 'degraded', latencyMs: 900, now }), {
offer_id: 'command-code:command-code-test',
-- kind: 'provider',
++ kind: 'harness',
provider: 'example',
region: 'global',
trust: 'partner',
-- capabilities: ['edit', 'review', 'host_access', 'mcp', 'resume'],
++ capabilities: ['task:edit', 'task:review', 'harness:host-access', 'harness:mcp', 'harness:resume'],
capacity: { workers: { harness: 2 } },
health: { status: 'degraded' },
pricing: { model: 'per-operation', unit: 'token', marginal_usd_per_unit: 0.003 / 1000 },
-- max_context_tokens: 100000,
++ updated_at: '2026-10-02T12:00:00.000Z',
++ detail: { ...row, limits: { ...row.limits, max_context_tokens: 100000 }, task_capabilities: ['edit', 'review'] },
latency_ms: { run_p95: 900 },
});
const deepseek = rows.find((row) => row.id === 'deepseek');
const offer = toOffer(deepseek, deepseek.agents[0]);
assert.strictEqual(offer.offer_id, 'deepseek:deepseek-chat');
-- assert.deepStrictEqual(offer.capabilities, ['edit', 'review']);
++ assert.deepStrictEqual(offer.capabilities, ['task:edit', 'task:review']);
assert.strictEqual(offer.trust, 'external');
assert.deepStrictEqual(offer.health, { status: 'up' });
assert.strictEqual('latency_ms' in offer, false);
-- assert.strictEqual(offer.max_context_tokens, 64000);
++ assert.strictEqual('max_context_tokens' in offer, false);
++ assert.strictEqual(offer.detail.limits.max_context_tokens, 64000);
++ assert.strictEqual('agents' in offer.detail, false);
assert.strictEqual(toOffer(deepseek, { ...deepseek.agents[0], id: undefined }).offer_id, `deepseek:${deepseek.agents[0].model}`);
});
++ await check('every seed offer validates as platform.resource-offer@1 of kind harness', async () => {
++ const harnesses = createHarnesses({ catalogPath });
++ const offers = harnesses.list().flatMap((harness) => harnesses.agents(harness.id).map((agent) => [harness, toOffer(harness, agent, { now })]));
++ assert.strictEqual(offers.length, 3);
++ for (const [harness, offer] of offers) {
++ const result = contracts.validate('platform.resource-offer@1', JSON.parse(JSON.stringify(offer)));
++ assert.ok(result.valid, `${offer.offer_id}: ${JSON.stringify(result.errors)}`);
++ assert.strictEqual(offer.detail.id, harness.id);
++ }
++ const browsing = { ...rows[0], task_capabilities: ['edit', 'browse'] };
++ const offer = toOffer(browsing, browsing.agents[0], { now });
++ assert.deepStrictEqual(offer.capabilities, ['task:edit', 'task:browse', 'harness:host-access', 'harness:mcp', 'harness:long-autonomy', 'harness:resume']);
++ assert.ok(contracts.validate('platform.resource-offer@1', JSON.parse(JSON.stringify(offer))).valid);
++ });
++
await check('requirementsFor maps each task and merges extras', async () => {
assert.deepStrictEqual(requirementsFor('long').capabilities, TASKS.long);
-- const req = requirementsFor('edit', { trust: 'first-party', capabilities: ['mcp'] });
-- assert.deepStrictEqual(req.capabilities, ['edit', 'mcp']);
++ assert.deepStrictEqual(requirementsFor('review').capabilities, ['task:review']);
++ const req = requirementsFor('edit', { trust: 'first-party', capabilities: ['harness:mcp'] });
++ assert.deepStrictEqual(req.capabilities, ['task:edit', 'harness:mcp']);
assert.deepStrictEqual(req.trust, ['first-party']);
assert.strictEqual(req.objective, 'balanced');
});
@@@ -69,7 +90,7 @@ function priced(row, freshUsd) {
const r = route({ task: 'review', harnesses, now });
assert.ok(r.selected);
const [harnessId] = r.selected.split(':');
-- assert.ok(toOffer(harnesses.get(harnessId), harnesses.agents(harnessId)[0]).capabilities.includes('review'));
++ assert.ok(toOffer(harnesses.get(harnessId), harnesses.agents(harnessId)[0]).capabilities.includes('task:review'));
assert.ok(r.reasons.length);
assert.deepStrictEqual(r.candidates.map((c) => c.id), ['claude-code:claude-code-sonnet', 'codex:codex-default', 'deepseek:deepseek-chat']);
});
@@@ -104,6 +125,17 @@ function priced(row, freshUsd) {
assert.strictEqual(flipped.selected, 'claude-code:claude-code-sonnet');
});
++ await check('a task:browse requirement excludes harnesses without it', async () => {
++ const [claude, codex] = rows;
++ const harnesses = stub([{ ...claude, task_capabilities: ['edit', 'review', 'browse'] }, codex]);
++ const r = route({ task: 'edit', extra: { capabilities: ['task:browse'] }, harnesses, now });
++ assert.strictEqual(r.selected, 'claude-code:claude-code-sonnet');
++ assert.deepStrictEqual(r.candidates.find((c) => c.id === 'codex:codex-default'), { id: 'codex:codex-default', included: false, reason: 'lacks task:browse' });
++ const none = route({ task: 'edit', extra: { capabilities: ['task:browse'] }, harnesses: createHarnesses({ catalogPath }), now });
++ assert.strictEqual(none.selected, null);
++ assert.ok(none.candidates.every((c) => c.reason === 'lacks task:browse'));
++ });
++
await check('an unknown task throws harness.task_unknown', async () => {
assert.throws(() => requirementsFor('deploy'), (error) => error.code === 'harness.task_unknown');
assert.throws(() => requirementsFor('toString'), (error) => error.code === 'harness.task_unknown');