openvibers/openvibe.codes Commits

A runtime capability manifest on every harness offer (#369)

OP OpenVibers committed 3 days ago · 6b416de553 · 9 files, +442 −7

* A runtime capability manifest on every harness offer, and the routers use it

* Put the runtime manifest test where the engine's test guard finds it

* Keep the runtime test on the manifest this branch ships

Changes

ddiff --git a/docs/adr/050-codes-harness-fabric.md b/docs/adr/050-codes-harness-fabric.md
iindex 5f57f6d..9f6088f 100644
---- a/docs/adr/050-codes-harness-fabric.md
++++ b/docs/adr/050-codes-harness-fabric.md
@@@ -82,7 +82,10 @@ first `docs/adr/` — the plan (§8) lists **ADR-050 Codes Harness Fabric** amon
(`app.js:79-80`), never a runtime surprise.
3. **One harness × agent pair is one `platform.resource-offer@1` of `kind: harness`** with the catalog row as its
`platform.harness-offer@1` `detail` (`harness-placement.js:31-58`). Offers carry `task:<kind>`, `harness:<flag>` and
-- `runtime:<class>` capabilities; a row that declares no runtime declares none (no default is synthesized).
++ `runtime:<class>` capabilities; a row that declares no runtime declares none (no default is synthesized). They also
++ carry one `harness:<field>-<value>` capability per field of the runtime capability manifest
++ (`engine/runtime.js`, `docs/harness-runtime.md`) — terminal status, steering, checkpointing, subagents, context
++ handoff and enforcement — so the router routes on the harness's declared behavior, never on a provider name.
4. **The read surface is `GET /api/v1/harnesses` and `POST /api/v1/harnesses/route`, plus the read-only docs page.**
These are additive and public-read; they expose the catalog and the placement decision with reasons and candidates
(`api.js:61-81`). Nothing in this surface launches a harness, streams a run, meters usage or resumes a session.
ddiff --git a/docs/harness-runtime.md b/docs/harness-runtime.md
nnew file mode 100644
iindex 0000000..6873ac0
---- /dev/null
++++ b/docs/harness-runtime.md
@@@ -0,0 +1,68 @@
++# The runtime capability manifest
++
++Every harness in `server/data/harness-offers.json` declares what it can actually do at runtime, in
++`engine/runtime.js`. The manifest is data, not a provider-name check: a task states the capability it needs and the
++router excludes every harness that does not declare it, with a reason. This is the plan's "runtime capability
++manifest on every harness offer" (Release 1, feat 12; t3code's provider capability system is the model).
++
++The catalog row cannot carry it: `platform.harness-offer@1` closes the row to unknown fields. The contract's own place
++for a runtime capability is the offer's `capabilities` array, so `toOffer()` (`server/domain/harness-placement.js`)
++turns each field into one `harness:<field>-<value>` capability. A harness the manifest does not know declares
++nothing, and is excluded only when a requirement is set.
++
++## Fields and values
++
++| field | values | meaning |
++|---|---|---|
++| `terminal_status` | `structured` | a machine-readable result line (stream-json, or the API loop's own events) |
++| | `parsed` | Codes reads the CLI's prose and decides from it |
++| | `none` | nothing to read |
++| `steering` | `live` | a message can be appended to the running session |
++| | `resume` | the session is continued by id, after or between runs |
++| | `none` | a new run starts over |
++| `checkpointing` | `git` | Codes can write the worktree to a git ref and restore it |
++| | `none` | no checkpoint |
++| `subagents` | `native` | the harness's own children, with its own lifecycle and completion |
++| | `tool` | children through a shell or protocol with no lifecycle report |
++| | `none` | no children |
++| `context_handoff` | `session` | the harness's session id resumes the conversation |
++| | `transcript` | Codes' own store replays it |
++| | `none` | only the hand-off note |
++| `enforcement` | `native` | the harness's own sandbox or permission mode |
++| | `client-boundary` | Codes' tool loop, or a sandbox Codes requires around it |
++
++## The rows
++
++| harness | terminal_status | steering | checkpointing | subagents | context_handoff | enforcement |
++|---|---|---|---|---|---|---|
++| `claude-code` | structured | resume | none | native | session | native |
++| `claude-other` | structured | resume | none | native | session | native |
++| `codex` | structured | resume | none | none | session | native |
++| `opencode` | structured | resume | none | none | session | client-boundary |
++| `command-code` | structured | resume | none | none | session | client-boundary |
++| `aider` | parsed | none | none | none | none | client-boundary |
++| `minimax-code` | structured | resume | none | none | session | native |
++| `deepseek` | structured | resume | none | none | session | client-boundary |
++| `openvibe-ai` | structured | resume | none | none | session | client-boundary |
++| `openai-compatible` | structured | resume | none | none | session | client-boundary |
++
++`checkpointing` flips to `git` on every row when the per-turn checkpoint refs land (`harness/job.js`): the manifest is
++the runtime's declaration, so it must not claim a checkpoint before one exists.
++
++## Using it
++
++```js
++const runtime = require('./engine/runtime');
++
++runtime.forHarness('codex'); // the fields
++runtime.capabilities(runtime.forHarness('codex')); // ['harness:terminal-status-structured', …]
++runtime.fits('cm', { subagents: 'native' }); // true: cm is Claude Code
++runtime.fits('mm', { subagents: 'native' }); // false: MiniMax Code declares none
++```
++
++- `POST /api/v1/harnesses/route` and `openvibe-codes route --need` take the capability names directly:
++ `{ "requirements": { "capabilities": ["harness:steering-resume"] } }`.
++- `engine/router.js` `pick()` takes `needs` as the names or as `{ field: value }`, and skips a ladder rung whose
++ harness does not declare it.
++- `test/harness-runtime.test.js` keeps the manifest, the catalog and both routers in step; a harness added to the
++ catalog without a manifest fails it.
ddiff --git a/engine/README.md b/engine/README.md
iindex 5de6c7d..cb607bd 100644
---- a/engine/README.md
++++ b/engine/README.md
@@@ -52,6 +52,22 @@ shared Know store's own `recall()` (the portal's `know/lessons.js`; on a Link it
Each CLI: installed, signed in, and the limit windows it can read. Nothing else: no key, no token, no account name.
++### `runtime.js` — what each harness can actually do
++
++The runtime capability manifest: terminal-status quality, steering, checkpointing, subagent lifecycle, context
++handoff and where the permission boundary is enforced, per harness, as data. Every offer carries the fields as
++`harness:<field>-<value>` capabilities (`server/domain/harness-placement.js`), so a task requires what it needs and
++the router excludes the rest with a reason instead of a provider-name check. `pick()` also takes `needs` and skips a
++ladder rung whose harness does not declare it. `[docs/harness-runtime.md](../docs/harness-runtime.md)` has the table.
++
++```js
++const runtime = require('./engine/runtime');
++
++runtime.fits('cm', { subagents: 'native' }); // true
++runtime.fits('mm', { subagents: 'native' }); // false: MiniMax Code declares no subagents
++runtime.capabilities(runtime.forHarness('codex'));
++```
++
### `router.js` — what kind of task is this, and which runner may take it
A brief is classified into one of five tiers (`basic`, `research`, `standard`, `hard`, `design`); a line `tier: hard`
ddiff --git a/engine/router.js b/engine/router.js
iindex 3c72ea9..cb06f01 100644
---- a/engine/router.js
++++ b/engine/router.js
@@@ -19,6 +19,7 @@
const fs = require('fs');
const path = require('path');
const { fitEffort, stepUp, EFFORTS } = require('./effort');
++const runtime = require('./runtime');
const LADDER = {
// Chores: renames, docs, formatting, counting, simple checks — free and plan Flash models first.
@@@ -242,13 +243,17 @@ function connected(runner, connections) {
* @param {object} [ctx.ladder] the ladder to use (the portal's by default)
* @param {object} [ctx.connections] the person's connections, per runner (Codes C3)
* @param {object} [ctx.fleet] the fleet per runner: { [runner]: { signed_in, windows } } (Codes C9c)
++ * @param {string[]|object} [ctx.needs] runtime capabilities the task needs (engine/runtime.js), as names
++ * (`['harness:steering-resume']`) or fields (`{ steering: 'resume' }`); a runner whose harness does not
++ * declare one is skipped, never chosen and then found wanting
* @returns {{runner: string, model: string|null, effort: string|null, tier: string, brief: string|null, why: string}|null}
*/
--function pick(prompt, { cfg, health, counts, models, runs, JOBS, name, ladder, connections, fleet } = {}) {
++function pick(prompt, { cfg, health, counts, models, runs, JOBS, name, ladder, connections, fleet, needs } = {}) {
const c = classify(prompt, { JOBS, name });
const rec = track(runs);
const reliable = (r) => { const t = rec[r]; return !t || t.done + t.failed < 4 || t.done / (t.done + t.failed) >= 0.4; };
-- const usable = (r) => cfg.subagents[r] && cfg.subagents[r].enabled && (counts[r] || 0) < cfg.subagents[r].max && health[r] && health[r].state === 'ok' && reliable(r) && connected(r, connections) && roomFor(fleet, r);
++ const capable = (r) => runtime.fits(r, needs);
++ const usable = (r) => cfg.subagents[r] && cfg.subagents[r].enabled && (counts[r] || 0) < cfg.subagents[r].max && health[r] && health[r].state === 'ok' && reliable(r) && capable(r) && connected(r, connections) && roomFor(fleet, r);
for (const [runner, model, wanted] of (ladder || LADDER)[c.tier]) {
if (!usable(runner)) continue;
// A model the runner no longer lists is skipped for that runner's default (lists refresh from each provider).
ddiff --git a/engine/runtime.js b/engine/runtime.js
nnew file mode 100644
iindex 0000000..64518a6
---- /dev/null
++++ b/engine/runtime.js
@@@ -0,0 +1,180 @@
++'use strict';
++
++/**
++ * The runtime capability manifest: what each harness Codes runs can actually do, as data.
++ *
++ * Every offer carries these as `harness:<field>-<value>` capabilities (`server/domain/harness-placement.js`), so a
++ * task states what it needs — "a harness that can resume a session", "one whose sandbox is its own" — and the router
++ * excludes the rest with a reason. No caller asks a provider's name; a harness that lies here lies to the router.
++ *
++ * The fields (the plan's runtime cap, tools §4; t3code's provider capability system is the model):
++ *
++ * terminal_status how the harness reports how a run ended
++ * structured — a machine-readable result line (stream-json / an API's own loop)
++ * parsed — Codes reads the CLI's prose and decides from it
++ * none — nothing to read
++ * steering how a running or ended task takes a correction
++ * live — a message can be appended to the running session
++ * resume — the session is continued by id, after or between runs
++ * none — a new run starts over
++ * checkpointing whether Codes can checkpoint the harness's working tree per turn
++ * git — the worktree can be written to a git ref and restored
++ * none — no checkpoint
++ * subagents how the harness runs children
++ * native — the harness's own tools, with its own lifecycle and completion
++ * tool — spawned through a shell or a protocol with no lifecycle report
++ * none — no children
++ * context_handoff what carries the context to the next attempt
++ * session — the harness's session id resumes the conversation
++ * transcript — Codes' own session store replays it
++ * none — only the hand-off note
++ * enforcement where the harness's permission boundary is enforced
++ * native — the harness's own sandbox or permission mode
++ * client-boundary — Codes' tool loop, or a sandbox Codes requires around it
++ *
++ * `none` is a declaration, not an omission: a requirement the harness cannot meet excludes it. An unknown harness (a
++ * portal runner with no Codes catalog row) declares nothing, so it is excluded only when a requirement is set.
++ *
++ * The manifest is Codes' own data, not a field of `platform.harness-offer@1`: the contract closes the row to unknown
++ * fields, and the offer's `capabilities` array is the contract's own place for a capability. A harness added to the
++ * catalog without a manifest fails the test that keeps the two in step.
++ */
++
++/** The closed value sets of every field. A value outside its set is a mistake, never a capability. */
++const FIELDS = Object.freeze({
++ terminal_status: Object.freeze(['structured', 'parsed', 'none']),
++ steering: Object.freeze(['live', 'resume', 'none']),
++ checkpointing: Object.freeze(['git', 'none']),
++ subagents: Object.freeze(['native', 'tool', 'none']),
++ context_handoff: Object.freeze(['session', 'transcript', 'none']),
++ enforcement: Object.freeze(['native', 'client-boundary']),
++});
++
++/** The capability namespace each field writes into: `terminal_status: 'structured'` → `harness:terminal-status-structured`. */
++const CAPABILITY = Object.freeze({
++ terminal_status: 'harness:terminal-status',
++ steering: 'harness:steering',
++ checkpointing: 'harness:checkpointing',
++ subagents: 'harness:subagents',
++ context_handoff: 'harness:context-handoff',
++ enforcement: 'harness:enforcement',
++});
++
++/**
++ * One manifest per harness in `server/data/harness-offers.json`.
++ *
++ * Grounded in each adapter (harness/adapters/cli.js, harness/adapters/api.js) and its CLI's flags, not in its name:
++ * every CLI here ends with a result line except Aider (its prose is parsed); every CLI with `--resume`/`--session`/`-s`
++ * continues its session; Claude Code alone exposes subagents as tools. `checkpointing` is `none` until the per-turn
++ * checkpoint refs land: this manifest is the runtime's declaration, so it must not claim one early.
++ */
++const MANIFEST = Object.freeze({
++ 'claude-code': Object.freeze({
++ terminal_status: 'structured', steering: 'resume', checkpointing: 'none',
++ subagents: 'native', context_handoff: 'session', enforcement: 'native',
++ }),
++ 'claude-other': Object.freeze({
++ terminal_status: 'structured', steering: 'resume', checkpointing: 'none',
++ subagents: 'native', context_handoff: 'session', enforcement: 'native',
++ }),
++ codex: Object.freeze({
++ terminal_status: 'structured', steering: 'resume', checkpointing: 'none',
++ subagents: 'none', context_handoff: 'session', enforcement: 'native',
++ }),
++ opencode: Object.freeze({
++ terminal_status: 'structured', steering: 'resume', checkpointing: 'none',
++ subagents: 'none', context_handoff: 'session', enforcement: 'client-boundary',
++ }),
++ 'command-code': Object.freeze({
++ terminal_status: 'structured', steering: 'resume', checkpointing: 'none',
++ subagents: 'none', context_handoff: 'session', enforcement: 'client-boundary',
++ }),
++ aider: Object.freeze({
++ terminal_status: 'parsed', steering: 'none', checkpointing: 'none',
++ subagents: 'none', context_handoff: 'none', enforcement: 'client-boundary',
++ }),
++ 'minimax-code': Object.freeze({
++ terminal_status: 'structured', steering: 'resume', checkpointing: 'none',
++ subagents: 'none', context_handoff: 'session', enforcement: 'native',
++ }),
++ deepseek: Object.freeze({
++ terminal_status: 'structured', steering: 'resume', checkpointing: 'none',
++ subagents: 'none', context_handoff: 'session', enforcement: 'client-boundary',
++ }),
++ 'openvibe-ai': Object.freeze({
++ terminal_status: 'structured', steering: 'resume', checkpointing: 'none',
++ subagents: 'none', context_handoff: 'session', enforcement: 'client-boundary',
++ }),
++ 'openai-compatible': Object.freeze({
++ terminal_status: 'structured', steering: 'resume', checkpointing: 'none',
++ subagents: 'none', context_handoff: 'session', enforcement: 'client-boundary',
++ }),
++});
++
++/**
++ * The portal's short runner ids (engine/router.js's ladder) for the same harness. The portal has no row for
++ * `claude-other`, and `cm`/`claude` are Claude Code on Anthropic; the others are one-to-one.
++ */
++const RUNNER_HARNESS = Object.freeze({
++ cm: 'claude-code', claude: 'claude-code',
++ cx: 'codex', cc: 'command-code', oc: 'opencode', dsh: 'deepseek', mm: 'minimax-code',
++});
++
++/** The manifest for a harness id (Codes'), or null for one the catalog does not declare. */
++function forHarness(harness) {
++ return MANIFEST[harness] || null;
++}
++
++/** The manifest for a ladder runner (the portal's short id, or a Codes harness id), or null. */
++function forRunner(runner) {
++ const key = String(runner || '');
++ return MANIFEST[RUNNER_HARNESS[key] || key] || null;
++}
++
++/** The offer capabilities one manifest declares, in field order. */
++function capabilities(manifest) {
++ if (!manifest) return [];
++ return Object.keys(FIELDS).map((field) => `${CAPABILITY[field]}-${manifest[field]}`);
++}
++
++/**
++ * Turn a requirement into capability names. Two shapes are accepted:
++ *
++ * ['harness:steering-resume'] the capabilities as they are
++ * { steering: 'resume', subagents: 'native' } the fields and their values
++ *
++ * @param {string[]|object|null} needs
++ * @returns {string[]}
++ */
++function requirements(needs) {
++ if (!needs) return [];
++ if (Array.isArray(needs)) return needs.filter((c) => typeof c === 'string' && c.trim()).map((c) => c.trim());
++ const caps = [];
++ for (const [field, value] of Object.entries(needs)) {
++ // An unknown field cannot be met: keep its name so the exclusion says why, rather than silently dropping it.
++ caps.push(`${CAPABILITY[field] || `harness:${field.replace(/_/g, '-')}`}-${value}`);
++ }
++ return caps;
++}
++
++/**
++ * Does a harness or ladder runner declare every capability a caller needs? No requirement fits everyone; a harness the
++ * manifest does not know fits nothing that is asked of it.
++ *
++ * @param {string} id a harness id or a portal runner id
++ * @param {string[]|object|null} needs capability names or a { field: value } object
++ * @returns {boolean}
++ */
++function fits(id, needs) {
++ const wanted = requirements(needs);
++ if (!wanted.length) return true;
++ const manifest = forRunner(id);
++ if (!manifest) return false;
++ const has = new Set(capabilities(manifest));
++ return wanted.every((cap) => has.has(cap));
++}
++
++module.exports = {
++ FIELDS, CAPABILITY, MANIFEST, RUNNER_HARNESS,
++ forHarness, forRunner, capabilities, requirements, fits,
++};
ddiff --git a/server/domain/harness-placement.js b/server/domain/harness-placement.js
iindex c74b704..d40bd0b 100644
---- a/server/domain/harness-placement.js
++++ b/server/domain/harness-placement.js
@@@ -6,6 +6,7 @@
*/
const contracts = require('openvibe-contracts');
const { place, optionKey } = require('openvibe-sdk/placement');
++const runtime = require('../../engine/runtime');
// A catalog row without task_capabilities derives its task kinds from the capability booleans:
// edit needs capabilities.edit === true, review needs capabilities.review === true.
@@@ -34,6 +35,8 @@ function toOffer(harness, agent, opts = {}) {
const caps = row.capabilities || {};
const limits = row.limits || {};
const tasks = row.task_capabilities || tasksFor(caps);
++ // What this harness can actually do at runtime (engine/runtime.js): the fidelity the router needs, as capabilities.
++ const fidelity = runtime.capabilities(runtime.forHarness(harness.id));
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 = {
@@@ -42,7 +45,7 @@ function toOffer(harness, agent, opts = {}) {
provider: agent.provider || harness.provider,
region: 'global',
trust: opts.trust || 'external',
-- capabilities: [...tasks.map((task) => `task:${task}`), ...Object.keys(HARNESS_FLAGS).filter((name) => caps[name] === true).map((name) => HARNESS_FLAGS[name]), ...(caps.runtimes || []).map((runtime) => `runtime:${runtime}`)],
++ capabilities: [...tasks.map((task) => `task:${task}`), ...Object.keys(HARNESS_FLAGS).filter((name) => caps[name] === true).map((name) => HARNESS_FLAGS[name]), ...(caps.runtimes || []).map((kind) => `runtime:${kind}`), ...fidelity],
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 },
ddiff --git a/test/engine-runtime.test.js b/test/engine-runtime.test.js
nnew file mode 100644
iindex 0000000..01e8563
---- /dev/null
++++ b/test/engine-runtime.test.js
@@@ -0,0 +1,77 @@
++'use strict';
++/**
++ * The runtime capability manifest (engine/runtime.js): every catalog harness declares what it can do, every offer
++ * carries it as `harness:<field>-<value>` capabilities, and both routers (placement and the tier router) exclude a
++ * harness that does not declare what the task needs — no provider-name conditional in either.
++ */
++const assert = require('assert');
++const fs = require('fs');
++const path = require('path');
++const { createHarnesses } = require('../server/domain/harnesses');
++const { toOffer, route } = require('../server/domain/harness-placement');
++const runtime = require('../engine/runtime');
++const { pick } = require('../engine/router');
++const { check, done } = require('./helpers/boot');
++
++const catalogPath = path.join(__dirname, '..', 'server', 'data', 'harness-offers.json');
++const rows = JSON.parse(fs.readFileSync(catalogPath, 'utf8'));
++
++(async () => {
++ await check('every catalog harness declares every field, with a known value', () => {
++ for (const row of rows) {
++ const manifest = runtime.forHarness(row.id);
++ assert.ok(manifest, `${row.id} has no runtime manifest`);
++ assert.deepStrictEqual(Object.keys(manifest).sort(), Object.keys(runtime.FIELDS).sort(), row.id);
++ for (const [field, values] of Object.entries(runtime.FIELDS)) {
++ assert.ok(values.includes(manifest[field]), `${row.id}.${field}: ${manifest[field]} is not one of ${values.join('|')}`);
++ }
++ }
++ assert.deepStrictEqual(Object.keys(runtime.MANIFEST).sort(), rows.map((row) => row.id).sort(), 'no manifest without a catalog row');
++ });
++
++ await check('a manifest becomes one capability per field, and fits() reads it', () => {
++ assert.deepStrictEqual(runtime.capabilities(runtime.forHarness('claude-code')), [
++ 'harness:terminal-status-structured', 'harness:steering-resume', 'harness:checkpointing-none',
++ 'harness:subagents-native', 'harness:context-handoff-session', 'harness:enforcement-native',
++ ]);
++ assert.deepStrictEqual(runtime.requirements({ steering: 'resume', subagents: 'native' }), ['harness:steering-resume', 'harness:subagents-native']);
++ assert.strictEqual(runtime.fits('claude-code', { subagents: 'native' }), true);
++ assert.strictEqual(runtime.fits('codex', { subagents: 'native' }), false);
++ assert.strictEqual(runtime.fits('cx', ['harness:steering-resume']), true, 'the portal runner id names the same harness');
++ assert.strictEqual(runtime.fits('pg', ['harness:steering-resume']), false, 'an unknown runner declares nothing');
++ assert.strictEqual(runtime.fits('pg', []), true, 'nothing required, nothing to exclude');
++ });
++
++ await check('every offer carries the manifest as capabilities the placement router can require', () => {
++ const harnesses = createHarnesses({ catalogPath });
++ for (const harness of harnesses.list()) {
++ const offer = toOffer(harness, harnesses.agents(harness.id)[0]);
++ for (const cap of runtime.capabilities(runtime.forHarness(harness.id))) {
++ assert.ok(offer.capabilities.includes(cap), `${offer.offer_id} lacks ${cap}`);
++ }
++ }
++ const r = route({ task: 'edit', extra: { capabilities: ['harness:subagents-native'] }, harnesses, now: Date.now() });
++ assert.strictEqual(r.selected.split(':')[0], 'claude-code');
++ for (const id of ['codex:codex-default', 'opencode:opencode-default', 'aider:aider-default', 'deepseek:deepseek-chat']) {
++ assert.deepStrictEqual(r.candidates.find((c) => c.id === id), { id, included: false, reason: 'lacks harness:subagents-native' });
++ }
++ const none = route({ task: 'edit', extra: { capabilities: ['harness:checkpointing-git'] }, harnesses, now: Date.now() });
++ assert.strictEqual(none.selected, null, 'no harness declares checkpointing yet');
++ assert.ok(none.candidates.every((c) => c.reason === 'lacks harness:checkpointing-git'));
++ });
++
++ await check('the tier router skips a ladder rung its manifest cannot meet', () => {
++ const ctx = {
++ cfg: { subagents: { mm: { enabled: true, max: 2 }, cm: { enabled: true, max: 2 } } },
++ health: { mm: { state: 'ok' }, cm: { state: 'ok' } },
++ counts: {}, models: {}, runs: [],
++ ladder: { basic: [['mm', null, 'high'], ['cm', null, 'high']] },
++ };
++ assert.strictEqual(pick('Fix the parser', ctx).runner, 'mm', 'no requirement: the first rung stays');
++ assert.strictEqual(pick('Fix the parser', { ...ctx, needs: ['harness:subagents-native'] }).runner, 'cm');
++ assert.strictEqual(pick('Fix the parser', { ...ctx, needs: { steering: 'resume' } }).runner, 'mm');
++ assert.strictEqual(pick('Fix the parser', { ...ctx, needs: ['harness:subagents-native'], ladder: { basic: [['mm', null, 'high']] } }), null);
++ });
++
++ done();
++})().catch((e) => { console.error(e); process.exit(1); });
ddiff --git a/test/harness-placement.test.js b/test/harness-placement.test.js
iindex eead1cb..e3c8fa7 100644
---- a/test/harness-placement.test.js
++++ b/test/harness-placement.test.js
@@@ -40,7 +40,9 @@ function priced(row, freshUsd) {
provider: 'example',
region: 'global',
trust: 'partner',
-- capabilities: ['task:edit', 'harness:host-access', 'harness:mcp', 'harness:resume', 'harness:edit', 'harness:tools', 'runtime:code'],
++ capabilities: ['task:edit', 'harness:host-access', 'harness:mcp', 'harness:resume', 'harness:edit', 'harness:tools', 'runtime:code',
++ 'harness:terminal-status-structured', 'harness:steering-resume', 'harness:checkpointing-none',
++ 'harness:subagents-none', 'harness:context-handoff-session', 'harness:enforcement-client-boundary'],
capacity: { workers: { harness: 2 } },
health: { status: 'degraded' },
pricing: { model: 'per-operation', unit: 'token', marginal_usd_per_unit: 0.003 / 1000 },
@@@ -52,7 +54,9 @@ function priced(row, freshUsd) {
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, ['task:edit', 'task:review', 'harness:host-access', 'harness:resume', 'harness:edit', 'harness:review', 'harness:tools']);
++ assert.deepStrictEqual(offer.capabilities, ['task:edit', 'task:review', 'harness:host-access', 'harness:resume', 'harness:edit', 'harness:review', 'harness:tools',
++ 'harness:terminal-status-structured', 'harness:steering-resume', 'harness:checkpointing-none',
++ 'harness:subagents-none', 'harness:context-handoff-session', 'harness:enforcement-client-boundary']);
assert.strictEqual(offer.trust, 'external');
assert.deepStrictEqual(offer.health, { status: 'up' });
assert.strictEqual('latency_ms' in offer, false);
@@@ -73,7 +77,9 @@ function priced(row, freshUsd) {
}
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', 'harness:edit', 'harness:review', 'harness:tools', 'harness:vision', 'runtime:code']);
++ assert.deepStrictEqual(offer.capabilities, ['task:edit', 'task:browse', 'harness:host-access', 'harness:mcp', 'harness:long-autonomy', 'harness:resume', 'harness:edit', 'harness:review', 'harness:tools', 'harness:vision', 'runtime:code',
++ 'harness:terminal-status-structured', 'harness:steering-resume', 'harness:checkpointing-none',
++ 'harness:subagents-native', 'harness:context-handoff-session', 'harness:enforcement-native']);
assert.ok(contracts.validate('platform.resource-offer@1', JSON.parse(JSON.stringify(offer))).valid);
});
ddiff --git a/test/harness-runtime.test.js b/test/harness-runtime.test.js
nnew file mode 100644
iindex 0000000..01e8563
---- /dev/null
++++ b/test/harness-runtime.test.js
@@@ -0,0 +1,77 @@
++'use strict';
++/**
++ * The runtime capability manifest (engine/runtime.js): every catalog harness declares what it can do, every offer
++ * carries it as `harness:<field>-<value>` capabilities, and both routers (placement and the tier router) exclude a
++ * harness that does not declare what the task needs — no provider-name conditional in either.
++ */
++const assert = require('assert');
++const fs = require('fs');
++const path = require('path');
++const { createHarnesses } = require('../server/domain/harnesses');
++const { toOffer, route } = require('../server/domain/harness-placement');
++const runtime = require('../engine/runtime');
++const { pick } = require('../engine/router');
++const { check, done } = require('./helpers/boot');
++
++const catalogPath = path.join(__dirname, '..', 'server', 'data', 'harness-offers.json');
++const rows = JSON.parse(fs.readFileSync(catalogPath, 'utf8'));
++
++(async () => {
++ await check('every catalog harness declares every field, with a known value', () => {
++ for (const row of rows) {
++ const manifest = runtime.forHarness(row.id);
++ assert.ok(manifest, `${row.id} has no runtime manifest`);
++ assert.deepStrictEqual(Object.keys(manifest).sort(), Object.keys(runtime.FIELDS).sort(), row.id);
++ for (const [field, values] of Object.entries(runtime.FIELDS)) {
++ assert.ok(values.includes(manifest[field]), `${row.id}.${field}: ${manifest[field]} is not one of ${values.join('|')}`);
++ }
++ }
++ assert.deepStrictEqual(Object.keys(runtime.MANIFEST).sort(), rows.map((row) => row.id).sort(), 'no manifest without a catalog row');
++ });
++
++ await check('a manifest becomes one capability per field, and fits() reads it', () => {
++ assert.deepStrictEqual(runtime.capabilities(runtime.forHarness('claude-code')), [
++ 'harness:terminal-status-structured', 'harness:steering-resume', 'harness:checkpointing-none',
++ 'harness:subagents-native', 'harness:context-handoff-session', 'harness:enforcement-native',
++ ]);
++ assert.deepStrictEqual(runtime.requirements({ steering: 'resume', subagents: 'native' }), ['harness:steering-resume', 'harness:subagents-native']);
++ assert.strictEqual(runtime.fits('claude-code', { subagents: 'native' }), true);
++ assert.strictEqual(runtime.fits('codex', { subagents: 'native' }), false);
++ assert.strictEqual(runtime.fits('cx', ['harness:steering-resume']), true, 'the portal runner id names the same harness');
++ assert.strictEqual(runtime.fits('pg', ['harness:steering-resume']), false, 'an unknown runner declares nothing');
++ assert.strictEqual(runtime.fits('pg', []), true, 'nothing required, nothing to exclude');
++ });
++
++ await check('every offer carries the manifest as capabilities the placement router can require', () => {
++ const harnesses = createHarnesses({ catalogPath });
++ for (const harness of harnesses.list()) {
++ const offer = toOffer(harness, harnesses.agents(harness.id)[0]);
++ for (const cap of runtime.capabilities(runtime.forHarness(harness.id))) {
++ assert.ok(offer.capabilities.includes(cap), `${offer.offer_id} lacks ${cap}`);
++ }
++ }
++ const r = route({ task: 'edit', extra: { capabilities: ['harness:subagents-native'] }, harnesses, now: Date.now() });
++ assert.strictEqual(r.selected.split(':')[0], 'claude-code');
++ for (const id of ['codex:codex-default', 'opencode:opencode-default', 'aider:aider-default', 'deepseek:deepseek-chat']) {
++ assert.deepStrictEqual(r.candidates.find((c) => c.id === id), { id, included: false, reason: 'lacks harness:subagents-native' });
++ }
++ const none = route({ task: 'edit', extra: { capabilities: ['harness:checkpointing-git'] }, harnesses, now: Date.now() });
++ assert.strictEqual(none.selected, null, 'no harness declares checkpointing yet');
++ assert.ok(none.candidates.every((c) => c.reason === 'lacks harness:checkpointing-git'));
++ });
++
++ await check('the tier router skips a ladder rung its manifest cannot meet', () => {
++ const ctx = {
++ cfg: { subagents: { mm: { enabled: true, max: 2 }, cm: { enabled: true, max: 2 } } },
++ health: { mm: { state: 'ok' }, cm: { state: 'ok' } },
++ counts: {}, models: {}, runs: [],
++ ladder: { basic: [['mm', null, 'high'], ['cm', null, 'high']] },
++ };
++ assert.strictEqual(pick('Fix the parser', ctx).runner, 'mm', 'no requirement: the first rung stays');
++ assert.strictEqual(pick('Fix the parser', { ...ctx, needs: ['harness:subagents-native'] }).runner, 'cm');
++ assert.strictEqual(pick('Fix the parser', { ...ctx, needs: { steering: 'resume' } }).runner, 'mm');
++ assert.strictEqual(pick('Fix the parser', { ...ctx, needs: ['harness:subagents-native'], ladder: { basic: [['mm', null, 'high']] } }), null);
++ });
++
++ done();
++})().catch((e) => { console.error(e); process.exit(1); });