openvibers/openvibe.codes Commits

Serve the harness catalog and task routing over the API (#7)

OP OpenVibers committed 2 Oct 2026 · b27f5944f6 · 4 files, +109 −2

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

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

Changes

ddiff --git a/server/app.js b/server/app.js
iindex 25776c7..0beb616 100644
---- a/server/app.js
++++ b/server/app.js
@@@ -33,6 +33,7 @@ const { createReleases } = require('./domain/releases');
const { createPlayground } = require('./domain/playground');
const { createArchiver } = require('./domain/project-archive');
const { createLimitsReader } = require('./domain/limits');
++const { createHarnesses } = require('./domain/harnesses');
const { createDocsRoutes } = require('./http/docs');
const { createToolRoutes } = require('./http/tools');
const { createPageRoutes } = require('./http/pages');
@@@ -75,7 +76,9 @@ async function createApp(opts = {}) {
// Each enforcing service's /limits.json, for /docs/limits and the project usage page.
const limits = createLimitsReader();
-- const ctx = { config, store, docs, keys, sso, network, outbox, trust, releases, playground, archiver, limits, indexnow, log };
++ // The harness catalog (server/data/harness-offers.json), read once: a bad seed row fails boot.
++ const harnesses = createHarnesses();
++ const ctx = { config, store, docs, keys, sso, network, outbox, trust, releases, playground, archiver, limits, harnesses, indexnow, log };
const app = express();
app.disable('x-powered-by');
ddiff --git a/server/http/actor-limits.js b/server/http/actor-limits.js
iindex 0d4ec2a..b3f576c 100644
---- a/server/http/actor-limits.js
++++ b/server/http/actor-limits.js
@@@ -57,6 +57,8 @@ const BUDGETS = {
// Validating a manifest or checking a webhook signature: a developer's pace, not a crawler's.
'codes.manifest.validate': { minute: 30, hour: 600 },
'codes.webhook.tool': { minute: 30, hour: 600 },
++ // Routing a coding task over the harness catalog (no I/O, but a POST: the reads' defaults skip it).
++ 'codes.harness.route': { minute: 30, hour: 600 },
// Staff setting an app's trust tier.
'codes.trust.set': { minute: 30, hour: 300 },
};
ddiff --git a/server/http/api.js b/server/http/api.js
iindex c55b282..ce5879c 100644
---- a/server/http/api.js
++++ b/server/http/api.js
@@@ -5,6 +5,8 @@
*
* GET /docs/versions the pinned versions the docs were generated from
* POST /manifests/validate { kind: app|mod, manifest } → { valid, errors, warnings }
++ * GET /harnesses the harness catalog, each harness with its agents
++ * POST /harnesses/route { task, requirements? } → { selected, reasons, candidates }
*
* codes.release.read (public; no token needed — a token that IS presented must hold it):
* GET /apps/:app/releases public releases of an app (never drafts) + trust tier
@@@ -25,6 +27,7 @@ const rateLimit = require('express-rate-limit');
const { http, serviceAuth } = require('openvibe-contracts');
const manifests = require('../domain/manifests');
const { ReleaseError } = require('../domain/releases');
++const placement = require('../domain/harness-placement');
const APP_RE = /^app_[0-9A-HJKMNP-TV-Z]{26}$/;
const REL_RE = /^rel_[0-9A-HJKMNP-TV-Z]{26}$/;
@@@ -32,7 +35,7 @@ const MANAGE = 'codes.release.manage';
const READ = 'codes.release.read';
function createApi(ctx) {
-- const { config, docs, releases, trust, keys, actorLimits } = ctx;
++ const { config, docs, releases, trust, keys, actorLimits, harnesses } = ctx;
const r = asyncRouter();
// Per-actor limits (http/actor-limits.js): reads take the defaults once the token (if any) is checked;
// writes name their budget after the guard and before the body is read.
@@@ -55,6 +58,28 @@ function createApi(ctx) {
res.status(v.valid ? 200 : 422).json({ ...v, contracts_version: docs.contractsVersion });
});
++ r.get('/harnesses', reads, (req, res) => {
++ res.json({ harnesses: harnesses.list().map((h) => ({ ...h, agents: harnesses.agents(h.id) })) });
++ });
++
++ // Every harness × agent offer with the defaults (health up, no latency); placement runs no I/O.
++ r.post('/harnesses/route', limiter, B('codes.harness.route'), json, (req, res) => {
++ const b = req.body || {};
++ const plain = (v) => v !== null && typeof v === 'object' && !Array.isArray(v);
++ if (typeof b.task !== 'string' || !b.task) return problem(req, res, 422, 'harness.invalid', 'task is a non-empty string');
++ if (b.requirements !== undefined && !plain(b.requirements)) return problem(req, res, 422, 'harness.invalid', 'requirements is an object');
++ const caps = b.requirements && b.requirements.capabilities;
++ if (caps !== undefined && !(Array.isArray(caps) && caps.every((c) => typeof c === 'string'))) return problem(req, res, 422, 'harness.invalid', 'requirements.capabilities is an array of strings');
++ try {
++ res.json(placement.route({ task: b.task, extra: b.requirements || {}, harnesses }));
++ } catch (err) {
++ if (err && err.code === 'harness.task_unknown') return problem(req, res, 422, 'harness.task_unknown', `task is one of ${Object.keys(placement.TASKS).join(', ')}`);
++ const code = err && err.code === 'harness.placement_invalid' ? 'harness.placement_invalid' : 'internal.error';
++ ctx.log.error('[Codes] api error:', err && err.message ? err.message.slice(0, 200) : err);
++ return problem(req, res, 500, code, 'Internal error');
++ }
++ });
++
// Guards: the key the token names is looked up first (the SDK's JWKS client), then the synchronous contracts guard
// runs against it.
const guardOpts = { issuer: config.networkIssuer, audience: 'openvibe.codes', acceptSandbox: true };
ddiff --git a/test/harnesses-api.test.js b/test/harnesses-api.test.js
nnew file mode 100644
iindex 0000000..21cf542
---- /dev/null
++++ b/test/harnesses-api.test.js
@@@ -0,0 +1,77 @@
++'use strict';
++
++/**
++ * The harness read API (/api/v1/harnesses): the catalog with its agents, routing a task over the
++ * catalog's offers, 422 problems for a bad body or an unknown task, and the per-actor limits on both.
++ */
++const assert = require('assert');
++const fs = require('fs');
++const path = require('path');
++const { boot, check, done } = require('./helpers/boot');
++
++const rows = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'server', 'data', 'harness-offers.json'), 'utf8'));
++
++(async () => {
++ const t = await boot();
++ const post = (json, headers) => t.get('/api/v1/harnesses/route', { method: 'POST', json, headers });
++
++ await check('GET /harnesses returns every seed harness with its agents', async () => {
++ const r = await t.get('/api/v1/harnesses');
++ assert.strictEqual(r.status, 200, r.text.slice(0, 300));
++ const { harnesses } = r.json();
++ assert.deepStrictEqual(harnesses.map((h) => h.id), rows.map((row) => row.id));
++ for (const row of rows) {
++ const h = harnesses.find((x) => x.id === row.id);
++ // Seed rows without an agent yet (command-code, aider) come back with agents: [].
++ assert.deepStrictEqual(h, { ...row, agents: row.agents || [] });
++ }
++ });
++
++ await check('POST /harnesses/route {task:review} returns a selected offer and candidates', async () => {
++ const r = await post({ task: 'review' });
++ assert.strictEqual(r.status, 200, r.text.slice(0, 300));
++ const body = r.json();
++ assert.strictEqual(typeof body.selected, 'string');
++ assert.ok(Array.isArray(body.reasons));
++ const agents = rows.reduce((n, row) => n + row.agents.length, 0);
++ assert.strictEqual(body.candidates.length, agents);
++ assert.ok(body.candidates.some((c) => c.id === body.selected && c.included === true));
++ });
++
++ await check('a bad body is 422 harness.invalid', async () => {
++ for (const json of [{}, { task: '' }, { task: 7 }, { task: 'review', requirements: 'x' }, { task: 'review', requirements: [] }, { task: 'review', requirements: { capabilities: 5 } }]) {
++ const r = await post(json);
++ assert.deepStrictEqual([r.status, r.json().code], [422, 'harness.invalid'], JSON.stringify(json));
++ assert.ok(/^application\/problem\+json/.test(r.headers.get('content-type')));
++ }
++ });
++
++ await check('an unknown task is 422 harness.task_unknown', async () => {
++ const r = await post({ task: 'nope' });
++ assert.deepStrictEqual([r.status, r.json().code], [422, 'harness.task_unknown']);
++ });
++ await t.close();
++
++ // The per-actor limits (http/actor-limits.js), as test/actor-limits.test.js asserts them.
++ const clock = Date.UTC(2026, 8, 27, 12, 0, 15);
++ const l = await boot({ actorLimits: true, limitsNow: () => clock, env: { CODES_LIMITS_MINUTE: '3', CODES_LIMITS_HOUR: '100' } });
++ const from = (ip, o = {}) => ({ ...o, headers: { 'X-Forwarded-For': ip } });
++
++ await check('GET /harnesses takes the read limit: 3 a minute per caller, then 429 rate_limited', async () => {
++ for (let i = 0; i < 3; i++) assert.strictEqual((await l.get('/api/v1/harnesses', from('203.0.113.7'))).status, 200);
++ const r = await l.get('/api/v1/harnesses', from('203.0.113.7'));
++ assert.deepStrictEqual([r.status, r.json().code], [429, 'rate_limited']);
++ assert.strictEqual((await l.get('/api/v1/harnesses', from('203.0.113.8'))).status, 200, 'another address still passes');
++ });
++
++ await check('POST /harnesses/route has its own budget: 30 a minute per caller, then 429 rate_limited', async () => {
++ const route = (ip) => l.get('/api/v1/harnesses/route', from(ip, { method: 'POST', json: { task: 'review' } }));
++ for (let i = 0; i < 30; i++) assert.strictEqual((await route('203.0.113.9')).status, 200);
++ const r = await route('203.0.113.9');
++ assert.deepStrictEqual([r.status, r.json().code], [429, 'rate_limited']);
++ assert.ok(r.json().detail.includes('codes.harness.route'), r.json().detail);
++ assert.strictEqual((await route('203.0.113.10')).status, 200, 'another address still passes');
++ });
++ await l.close();
++ done();
++})();