openvibers/openvibe.codes Commits

Add the harness catalog page, sitemap and llms.txt entries (#8)

OP OpenVibers committed 2 Oct 2026 · 95068783f8 · 5 files, +64 −3

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

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

Changes

ddiff --git a/STATUS.json b/STATUS.json
iindex 4bf94aa..d1070ba 100644
---- a/STATUS.json
++++ b/STATUS.json
@@@ -29,7 +29,8 @@
"App and mod manifest validation; release metadata (draft, publish, deprecate, revoke) with codes.app.* events through the SDK outbox; trust tiers per ADR-013 (unreviewed, reviewed, first-party) as staff-set metadata, older names migrated at boot",
"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"
++ "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)"
],
"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",
@@@ -56,6 +57,7 @@
"app and mod manifest validation and release metadata with codes.app.* events",
"ADR-013 trust tiers",
"policy and governance pages (governance drafts pending owner review)",
-- "per-actor limits (openvibe-sdk/limits)"
++ "per-actor limits (openvibe-sdk/limits)",
++ "coding harness catalog, task routing and a read-only docs page"
]
}
ddiff --git a/server/http/discovery.js b/server/http/discovery.js
iindex c368703..b27aa2e 100644
---- a/server/http/discovery.js
++++ b/server/http/discovery.js
@@@ -68,6 +68,7 @@ function publicPages({ docs, governance = [] }) {
{ path: '/docs/billing', changefreq: 'monthly', priority: 0.5 },
{ path: '/docs/limits', changefreq: 'monthly', priority: 0.5 },
{ path: '/docs/tools', changefreq: 'monthly', priority: 0.5 },
++ { path: '/docs/harnesses', changefreq: 'weekly', priority: 0.6 },
{ path: '/docs/export', changefreq: 'monthly', priority: 0.5 },
{ path: '/docs/sdk', changefreq: 'weekly', priority: 0.7 },
{ path: '/oauth', changefreq: 'monthly', priority: 0.5 },
@@@ -154,6 +155,7 @@ function createDiscoveryRoutes(ctx) {
{ title: 'Events', url: abs('/docs/events') },
{ title: 'Services', url: abs('/docs/services') },
{ title: 'Tools', url: abs('/docs/tools') },
++ { title: 'Coding harnesses', url: abs('/docs/harnesses'), note: 'the harness catalog and how tasks are routed' },
{ title: 'SDK', url: abs('/docs/sdk') },
] },
{ title: 'Developer tools', links: [
ddiff --git a/server/http/docs.js b/server/http/docs.js
iindex 56c673e..414fd26 100644
---- a/server/http/docs.js
++++ b/server/http/docs.js
@@@ -16,6 +16,7 @@
* /docs/services the Network's registry with health as reported (never invented)
* /docs/billing the billing policy as OpenVibe.Billing's /policy.json states it (never restated)
* /docs/limits limits and tiers: each service's /limits.json as it answers (never restated)
++ * /docs/harnesses the coding harnesses and agents a task can be routed to, from the catalog
* /docs/export the project export: the metadata JSON and the full archive's layout
* /docs/sdk[/:module] SDK reference from its .d.ts files
* /docs/adr/:id ADRs as published in openvibe-contracts
@@@ -57,6 +58,7 @@ function createDocsRoutes(ctx) {
<li><a href="/docs/events"><strong>Events</strong></a><span>${docs.events.length} event types services declare they produce</span></li>
<li><a href="/docs/services"><strong>Services</strong></a><span>the registry as OpenVibe.Network reports it, with health</span></li>
<li><a href="/docs/tools"><strong>Tools API</strong></a><span>every OpenVibe tool you can call from code, from the live registry</span></li>
++<li><a href="/docs/harnesses"><strong>Harnesses</strong></a><span>the coding harnesses and agents a task can be routed to, with their capabilities, prices and limits</span></li>
<li><a href="/docs/billing"><strong>Billing policy</strong></a><span>prices, the creator split, fees, holds and cashouts, live from OpenVibe.Billing</span></li>
<li><a href="/docs/limits"><strong>Limits and tiers</strong></a><span>what a project may do in sandbox and production, live from the services that enforce it</span></li>
<li><a href="/docs/export"><strong>Project export</strong></a><span>take a project with you: its configuration, modules, objects and events as one download</span></li>
@@@ -365,6 +367,47 @@ ${families.map((f) => html`<h2>${f}</h2>${table(['Tool', 'Runs as', 'Access', 'I
});
});
++ // ── Coding harnesses (the catalog Codes routes coding tasks over, T16) ─────────
++ // The catalog is ctx.harnesses (the committed seed, validated at boot); nothing is fetched and
++ // no address target that is a local path is ever shown — only the address kind.
++ const isLocalPath = (t) => /^(?:[A-Za-z]:[\\/]|[~/]|\.\.?[\\/])/.test(String(t || ''));
++ const addressOf = (o) => {
++ const kind = (o.address && o.address.kind) || '—';
++ const target = o.address && o.address.target;
++ return target && !isLocalPath(target) ? html`${kind} <code>${target}</code>` : kind;
++ };
++ const usd = (n) => `$${Number(n || 0).toFixed(2)}`;
++ const duration = (n) => (n >= 3600 && n % 3600 === 0 ? `${n / 3600} h` : `${n} s`);
++ const flags = (c) => ['host_access', 'mcp', 'long_autonomy', 'resume'].filter((k) => c && c[k]).map((k) => k.replace(/_/g, ' ')).join(', ') || '—';
++ const count = (n) => Number(n).toLocaleString('en-US');
++ r.get('/harnesses', (req, res) => {
++ const offers = ctx.harnesses.list();
++ page(req, res, {
++ title: 'Coding harnesses',
++ description: 'The coding harnesses and agents Codes can route a task to, with their capabilities, prices and limits.',
++ crumbs: [{ label: 'Docs', href: '/docs' }, { label: 'Harnesses' }],
++ body: html`<h1>Coding harnesses</h1>
++<p>A task is routed with <code>POST /api/v1/harnesses/route</code> over <code>openvibe-sdk/placement</code>, which picks an offer from this catalog; see the <a href="/docs/api">API explorer</a>. The catalog below is read and validated when Codes starts, and every price and limit is as the offer states it.</p>
++${table(['Harness', 'Provider', 'Address', 'Capabilities', 'Price', 'Limits'], offers.map((o) => [
++ html`<strong>${o.name}</strong><br><code>${o.id}</code>`,
++ o.provider,
++ addressOf(o),
++ flags(o.capabilities),
++ `${usd(o.price.amount_usd)} / ${o.price.unit}`,
++ `${duration(o.limits.max_duration_seconds)}, ${count(o.limits.max_concurrent_runs)} concurrent, ${count(o.limits.max_context_tokens)} context tokens`,
++ ]))}
++<h2>Agents</h2>
++${offers.map((o) => html`<h3>${o.name} <code>${o.id}</code></h3>${o.agents.length
++ ? table(['Provider', 'Model', 'Context (in / out)', 'Price per 1k tokens'], o.agents.map((a) => [
++ a.provider,
++ a.model,
++ `${count(a.context_limits.input_tokens)} / ${count(a.context_limits.output_tokens)}`,
++ `${usd(a.price_per_1k_tokens.fresh_usd)} fresh, ${usd(a.price_per_1k_tokens.cached_usd)} cached, ${usd(a.price_per_1k_tokens.output_usd)} output`,
++ ]))
++ : html`<p class="muted">no agents yet</p>`}`)}`,
++ });
++ });
++
// ── Billing policy (live from OpenVibe.Billing, WS-K task 10) ──
// The numbers are Billing's /policy.json (read from the rates the ledger charges); nothing is
// restated here. Billing not answering shows a problem, never remembered or invented numbers.
ddiff --git a/test/discovery.test.js b/test/discovery.test.js
iindex 531644b..11e9855 100644
---- a/test/discovery.test.js
++++ b/test/discovery.test.js
@@@ -32,6 +32,7 @@ const PRIVATE = ['/projects', '/staff', '/auth/', '/oauth/test-callback'];
assert.match(r.headers.get('content-type'), /^application\/xml/);
assert.match(r.headers.get('cache-control') || '', /public, max-age=\d+/);
assert.ok(r.text.includes('<loc>https://openvibe.codes/docs</loc>'), 'no /docs entry');
++ assert.ok(r.text.includes('<loc>https://openvibe.codes/docs/harnesses</loc>'), 'no /docs/harnesses entry');
assert.ok(r.text.includes('<loc>https://openvibe.codes/policy/transparency</loc>'), 'no policy entry');
const lastmods = [...r.text.matchAll(/<lastmod>([^<]+)<\/lastmod>/g)].map((m) => m[1]);
assert.ok(lastmods.length > 0, 'no lastmod anywhere');
@@@ -47,6 +48,7 @@ const PRIVATE = ['/projects', '/staff', '/auth/', '/oauth/test-callback'];
assert.match(r.headers.get('cache-control') || '', /public, max-age=\d+/);
assert.ok(r.text.startsWith('# OpenVibe.Codes'), 'no title');
assert.ok(r.text.includes('(https://openvibe.codes/docs)'), 'no reference link');
++ assert.ok(r.text.includes('(https://openvibe.codes/docs/harnesses)'), 'no harnesses reference link');
assert.ok(r.text.includes('https://openvibe.codes/sitemap.xml'), 'sitemap not listed');
for (const p of PRIVATE) assert.ok(!r.text.includes(`https://openvibe.codes${p}`), `private path in llms.txt: ${p}`);
assert.ok(!r.text.includes('https://openvibe.codes/api/'), 'api path in llms.txt');
ddiff --git a/test/layout.test.js b/test/layout.test.js
iindex 697d45e..775067a 100644
---- a/test/layout.test.js
++++ b/test/layout.test.js
@@@ -7,6 +7,8 @@
* shared navbar's sign-in follows the current page through the {path} template.
*/
const assert = require('assert');
++const fs = require('fs');
++const path = require('path');
const { boot, check, done } = require('./helpers/boot');
const MARKER = /<meta name="ov-boost" content="(codes@[0-9A-Za-z._+-]{1,64})">/;
@@@ -18,7 +20,7 @@ const BOOST = /<script src="\/shared\/boost\.js\?v=[0-9a-f]{12}" data-main="#mai
const release = (await t.get('/release.json')).json().release;
await check('every rendered page carries the ov-boost marker (the app\'s release) and the data-main boost script', async () => {
-- for (const p of ['/', '/docs', '/oauth', '/manifests/validate']) {
++ for (const p of ['/', '/docs', '/oauth', '/manifests/validate', '/docs/harnesses']) {
const r = await t.get(p);
assert.strictEqual(r.status, 200, p);
const m = r.text.match(MARKER);
@@@ -39,6 +41,16 @@ const BOOST = /<script src="\/shared\/boost\.js\?v=[0-9a-f]{12}" data-main="#mai
assert.ok(!/"loginUrl":"\/auth\/login\?next=%2F/.test(r.text), 'loginUrl still has the current path baked in');
});
++ await check('the harness catalog page lists every seed harness server-side, with no local path', async () => {
++ const r = await t.get('/docs/harnesses');
++ assert.strictEqual(r.status, 200, r.text.slice(0, 300));
++ assert.match(r.headers.get('content-type'), /^text\/html/);
++ const rows = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'server', 'data', 'harness-offers.json'), 'utf8'));
++ for (const h of rows) assert.ok(r.text.includes(`<code>${h.id}</code>`), `${h.id} listed`);
++ assert.ok(r.text.includes('no agents yet'), 'a harness without agents says "no agents yet"');
++ assert.ok(!r.text.includes('/home/') && !r.text.includes('/mnt/'), 'no local filesystem path on the page');
++ });
++
await t.close();
done();
})().catch((e) => { console.error(e); process.exit(1); });