openvibers/openvibe.codes Commits
Add the harness catalog page, sitemap and llms.txt entries (#8)
Changes
@@@ -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"]}@@@ -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: [@@@ -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.@@@ -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');@@@ -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="#maiconst 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="#maiassert.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); });
@@ -29,7 +29,8 @@
@@ -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",
"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",
"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)",
"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": [
"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",
"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 @@
@@ -56,6 +57,7 @@
"app and mod manifest validation and release metadata with codes.app.* events",
"app and mod manifest validation and release metadata with codes.app.* events",
"ADR-013 trust tiers",
"ADR-013 trust tiers",
"policy and governance pages (governance drafts pending owner review)",
"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"
]
]
}
}
@@ -68,6 +68,7 @@ function publicPages({ docs, governance = [] }) {
@@ -68,6 +68,7 @@ function publicPages({ docs, governance = [] }) {
{ path: '/docs/billing', changefreq: 'monthly', priority: 0.5 },
{ path: '/docs/billing', changefreq: 'monthly', priority: 0.5 },
{ path: '/docs/limits', changefreq: 'monthly', priority: 0.5 },
{ path: '/docs/limits', changefreq: 'monthly', priority: 0.5 },
{ path: '/docs/tools', 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/export', changefreq: 'monthly', priority: 0.5 },
{ path: '/docs/sdk', changefreq: 'weekly', priority: 0.7 },
{ path: '/docs/sdk', changefreq: 'weekly', priority: 0.7 },
{ path: '/oauth', changefreq: 'monthly', priority: 0.5 },
{ path: '/oauth', changefreq: 'monthly', priority: 0.5 },
@@ -154,6 +155,7 @@ function createDiscoveryRoutes(ctx) {
@@ -154,6 +155,7 @@ function createDiscoveryRoutes(ctx) {
{ title: 'Events', url: abs('/docs/events') },
{ title: 'Events', url: abs('/docs/events') },
{ title: 'Services', url: abs('/docs/services') },
{ title: 'Services', url: abs('/docs/services') },
{ title: 'Tools', url: abs('/docs/tools') },
{ 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: 'SDK', url: abs('/docs/sdk') },
] },
] },
{ title: 'Developer tools', links: [
{ title: 'Developer tools', links: [
@@ -16,6 +16,7 @@
@@ -16,6 +16,7 @@
* /docs/services the Network's registry with health as reported (never invented)
* /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/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/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/export the project export: the metadata JSON and the full archive's layout
* /docs/sdk[/:module] SDK reference from its .d.ts files
* /docs/sdk[/:module] SDK reference from its .d.ts files
* /docs/adr/:id ADRs as published in openvibe-contracts
* /docs/adr/:id ADRs as published in openvibe-contracts
@@ -57,6 +58,7 @@ function createDocsRoutes(ctx) {
@@ -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/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/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/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/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/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>
<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
@@ -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) ──
// ── 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
// 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.
// restated here. Billing not answering shows a problem, never remembered or invented numbers.
@@ -32,6 +32,7 @@ const PRIVATE = ['/projects', '/staff', '/auth/', '/oauth/test-callback'];
@@ -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('content-type'), /^application\/xml/);
assert.match(r.headers.get('cache-control') || '', /public, max-age=\d+/);
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</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');
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]);
const lastmods = [...r.text.matchAll(/<lastmod>([^<]+)<\/lastmod>/g)].map((m) => m[1]);
assert.ok(lastmods.length > 0, 'no lastmod anywhere');
assert.ok(lastmods.length > 0, 'no lastmod anywhere');
@@ -47,6 +48,7 @@ const PRIVATE = ['/projects', '/staff', '/auth/', '/oauth/test-callback'];
@@ -47,6 +48,7 @@ const PRIVATE = ['/projects', '/staff', '/auth/', '/oauth/test-callback'];
assert.match(r.headers.get('cache-control') || '', /public, max-age=\d+/);
assert.match(r.headers.get('cache-control') || '', /public, max-age=\d+/);
assert.ok(r.text.startsWith('# OpenVibe.Codes'), 'no title');
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)'), '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');
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}`);
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');
assert.ok(!r.text.includes('https://openvibe.codes/api/'), 'api path in llms.txt');
@@ -7,6 +7,8 @@
@@ -7,6 +7,8 @@
* shared navbar's sign-in follows the current page through the {path} template.
* shared navbar's sign-in follows the current page through the {path} template.
*/
*/
const assert = require('assert');
const assert = require('assert');
+const fs = require('fs');
+const path = require('path');
const { boot, check, done } = require('./helpers/boot');
const { boot, check, done } = require('./helpers/boot');
const MARKER = /<meta name="ov-boost" content="(codes@[0-9A-Za-z._+-]{1,64})">/;
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
@@ -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;
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 () => {
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);
const r = await t.get(p);
assert.strictEqual(r.status, 200, p);
assert.strictEqual(r.status, 200, p);
const m = r.text.match(MARKER);
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
@@ -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');
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();
await t.close();
done();
done();
})().catch((e) => { console.error(e); process.exit(1); });
})().catch((e) => { console.error(e); process.exit(1); });