Files
msd-core/tests/reviewer-manifest-body.test.cjs
Jakub Zych fe3ed06691
Some checks failed
Tests / PR mergeability (push) Successful in 18s
Tests / Base branch health (push) Successful in 9s
Tests / Detect test scope (push) Successful in 16s
Tests / lint-tests (push) Failing after 1m43s
Tests / plugin-validate (push) Successful in 58s
Tests / test (ubuntu-latest, 24, shard 1/3) (push) Failing after 19s
Tests / test (ubuntu-latest, 24, shard 2/3) (push) Failing after 20s
Tests / test (ubuntu-latest, 24, shard 3/3) (push) Failing after 20s
Tests / test (ubuntu-latest, 24) (push) Failing after 18s
Tests / test (inert CI) (push) Has been skipped
Tests / QA loop walk (smell ratchet) (push) Failing after 19s
Tests / Coverage gate (merged shards) (push) Has been skipped
Tests / Publish emitted-baseline artifact (push) Has been skipped
Duplicate auto-close sweep / sweep (push) Successful in 19s
CI timeout budget report / report (push) Failing after 14s
Close Draft PRs (sweep) / Sweep open draft PRs (push) Successful in 9s
Dismiss Unauthorized PR Approvals / dismiss-unauthorized-approval (push) Successful in 9s
Tests / conformance test (macos-latest, 24) (push) Has been cancelled
Tests / conformance test (windows-latest, 24, shard 1/3) (push) Has been cancelled
Tests / conformance test (windows-latest, 24, shard 2/3) (push) Has been cancelled
Tests / conformance test (windows-latest, 24, shard 3/3) (push) Has been cancelled
Tests / Required tests (push) Has been cancelled
chore: clear dead test and allowlist leftovers of dropped runtimes
2026-10-06 20:35:12 +02:00

2309 lines
104 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// docs-guard-exempt: record.docs is a metadata field string comparison, not a real docs/ file read.
'use strict';
process.env.MSD_TEST_MODE = '1';
/**
* reviewer-manifest-body.test.cjs — behavioral tests for the reviewer lane body
* (ADR-2782, chore #2795 Phase 2): `validateReviewerBody`, `collectReviewerWarnings` / `collectReviewerWarningRecords`,
* the `role:'reviewer'` dispatch branch of `validateCapability`, the reviewer-lane
* uniqueness rules inside `validateCrossCapability`, and the harvest widening in
* `buildRegistry` / `loadAndValidate`.
*
* Implements every row in `.msd/phase/chore-2795-reviewer-manifest-body/50-test-matrix.md`
* that carries a Test name (sections A–J). Test names are copied verbatim from the
* matrix. See `.msd/phase/chore-2795-reviewer-manifest-body/40-design.md` for the
* behavior table the matrix derives from.
*
* Level choice: rows describing the SHAPE of the `reviewer` body in isolation
* (A1–A3, A7–A11, and all of B–F) call `validateReviewerBody` directly — the
* cheapest unit that proves the behavior, per the matrix's own "Units and level"
* table. Rows describing ROLE-CONDITIONED admissibility of the body (A4–A6,
* A12–A15) call `validateCapability(cap, folderId)`, since that dispatch only
* exists there. Section G calls `validateCrossCapability(Map, Set)` — the real
* caller shape, never a plain object. Section H splits across `buildRegistry`
* (the harvest itself) and `validateCrossCapability` (the config-key ownership
* loop it also widened) per where each behavior actually lives in the source.
*/
const { test, describe } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const os = require('node:os');
const path = require('node:path');
const fc = require('fast-check');
const { cleanup } = require('./helpers.cjs');
const { HTTP_REACHABLE_PROBE_TIMEOUT_FIXTURE_MS } = require('./helpers/timeouts.cjs');
/** NOT a subprocess spawn timeout. Fixture DATA: the default valid `timeoutMs` for a command-capability-kind probe. */
const COMMAND_CAPABILITY_PROBE_TIMEOUT_FIXTURE_MS = 3000;
/** NOT a subprocess spawn timeout. Fixture DATA: manifest probe.timeoutMs boundary set (this validator's own -1/0/1 triple is unrelated to, and coincidentally overlaps with, tests/plan-phase-stall-detection.test.cjs's runBashScript seam-level boundary triple -- disclosed, not merged; different validator, different file). */
const MANIFEST_PROBE_TIMEOUT_BOUNDARY_ZERO_MS = 0;
const MANIFEST_PROBE_TIMEOUT_BOUNDARY_ONE_MS = 1;
const MANIFEST_PROBE_TIMEOUT_BOUNDARY_NEGATIVE_MS = -1;
const MANIFEST_PROBE_TIMEOUT_BOUNDARY_NON_INTEGER_MS = 1.5;
/** NOT a subprocess spawn timeout. Fixture DATA: an arbitrary value proving a command-exists-kind probe rejects ANY timeoutMs field -- the specific number is not itself under test. */
const COMMAND_EXISTS_FORBIDDEN_TIMEOUT_MS = 500;
const {
LANE_SLUG_RE,
validateReviewerBody,
collectReviewerWarnings,
collectReviewerWarningRecords,
REVIEWER_WARNING,
REMOVED_REVIEWER_CLI_FIELD,
validateCapability,
validateCrossCapability,
VALID_LANE_EFFORT_CHANNELS,
VALID_MODEL_DISCOVERY,
VALID_EMPTY_OUTPUT,
VALID_EVIDENCE_CLASSES,
VALID_LANE_HANDLERS,
KNOWN_REVIEWER_FIELDS,
KNOWN_HOST_BEHAVIORS,
MAX_REPORTED_UNKNOWN_KEYS,
MAX_REPORTED_KEY_CHARS,
} = require('../msd-core/bin/lib/capability-validator.cjs');
const { loadAndValidate, buildRegistry } = require('../scripts/gen-capability-registry.cjs');
// ─── Fixture builders ──────────────────────────────────────────────────────
// House convention (tests/gen-registry.test.cjs): builder functions return a
// VALID fixture, which each test then mutates. Every call returns a FRESH
// object — no module-level mutable shared state, no execution-order dependence.
/** A valid `spawn`-transport reviewer lane body. */
function validLane() {
return {
slug: 'my-lane',
flags: ['--my-lane'],
transport: 'spawn',
probe: { kind: 'command-exists', binary: 'my-lane' },
invoke: {
binary: 'my-lane',
args: [],
promptChannel: 'stdin',
outputChannel: 'stdout',
modelArg: null,
effortChannel: 'none',
},
timeoutFloorMs: 5000,
emptyOutput: 'stub-with-stderr',
reviewsSection: 'My Lane',
evidenceClass: 'source-grounded',
requiresBinaries: ['my-lane'],
promptBudgetKey: null,
handler: null,
};
}
/** A valid `openai-http`-transport reviewer lane body. */
function validHttpLane() {
return {
slug: 'lm-studio-http',
flags: ['--lm-studio'],
transport: 'openai-http',
probe: {
kind: 'http-reachable',
hostConfigKey: 'lmStudio.baseUrl',
path: '/v1/models',
timeoutMs: HTTP_REACHABLE_PROBE_TIMEOUT_FIXTURE_MS,
},
invoke: {
hostConfigKey: 'lmStudio.baseUrl',
path: '/v1/chat/completions',
modelDiscovery: 'none',
effortChannel: 'none',
},
timeoutFloorMs: 5000,
emptyOutput: 'stub-with-stderr',
reviewsSection: 'LM Studio',
evidenceClass: 'source-grounded',
requiresBinaries: [],
promptBudgetKey: null,
handler: null,
};
}
/** A fresh, well-formed spawn lane, mutated in place by `mutator` before return. */
function laneOverride(mutator) {
const lane = validLane();
mutator(lane);
return lane;
}
/** A fresh, well-formed http lane, mutated in place by `mutator` before return. */
function httpOverride(mutator) {
const lane = validHttpLane();
mutator(lane);
return lane;
}
/**
* A valid `role:'reviewer'` capability envelope (id/title/description/tier/
* requires/version all satisfy `validateCapability`'s common envelope) carrying
* a well-formed lane body. `overrides` is shallow-merged last, so passing
* `{ reviewer: undefined }` represents "no reviewer key" (property present,
* value undefined — behaviorally identical to an absent key for every check
* in this file, and it is what `JSON.stringify` drops when a fixture is
* written to disk in section I).
*/
function capWith(overrides) {
return Object.assign(
{
id: 'test-cap',
role: 'reviewer',
title: 'Test Capability',
description: 'A test capability for the reviewer manifest body test suite.',
tier: 'standard',
requires: [],
version: '1.0.0',
reviewer: validLane(),
},
overrides || {},
);
}
// ─── A. Body presence / shape ──────────────────────────────────────────────
describe('A. Body presence / shape', () => {
test('runtimeCapWithoutReviewerBodyIsValidAndNotALane', () => {
const errs = validateReviewerBody({ id: 'x', role: 'runtime' });
assert.deepEqual(errs, [], `expected no errors, got: ${JSON.stringify(errs)}`);
});
test('featureCapWithoutReviewerBodyIsValid', () => {
const errs = validateReviewerBody({ id: 'x', role: 'feature' });
assert.deepEqual(errs, [], `expected no errors, got: ${JSON.stringify(errs)}`);
});
test('runtimeCapMayCarryReviewerBody', () => {
const errs = validateReviewerBody({ id: 'x', role: 'runtime', reviewer: validLane() });
assert.deepEqual(errs, [], `expected no errors, got: ${JSON.stringify(errs)}`);
});
test('reviewerRoleWithLaneBodyIsValid', () => {
const cap = capWith();
const errs = validateCapability(cap, cap.id);
assert.deepEqual(errs, [], `expected no errors, got: ${JSON.stringify(errs)}`);
});
test('reviewerRoleWithoutLaneBodyIsRejected', () => {
const cap = capWith({ reviewer: undefined });
const errs = validateCapability(cap, cap.id);
assert.ok(
errs.some((e) => e.includes('role:reviewer capability must have a "reviewer" body')),
`expected the lane-ness assertion error, got: ${JSON.stringify(errs)}`,
);
});
test('featureRoleRejectsReviewerBody', () => {
const cap = capWith({ role: 'feature', reviewer: validLane() });
const errs = validateCapability(cap, cap.id);
assert.ok(
errs.some((e) => e.includes('role:feature capability must not have a "reviewer" body')),
`expected the feature-forbids-reviewer-body error, got: ${JSON.stringify(errs)}`,
);
});
// A7/A1 are the highest-consequence pair: `typeof null === 'object'`, so an
// explicit `null` must NOT be read as "absent" (A1), yet must still error (A7).
test('reviewerNullIsRejectedNotTreatedAsAbsent', () => {
const errs = validateReviewerBody({ id: 'x', reviewer: null });
assert.ok(
errs.some((e) => e.includes('reviewer must be an object (got: null)')),
`expected a null-is-not-absent error, got: ${JSON.stringify(errs)}`,
);
});
test('reviewerBooleanIsRejected', () => {
const errs = validateReviewerBody({ id: 'x', reviewer: true });
assert.ok(
errs.some((e) => e.includes('reviewer must be an object (got: boolean)')),
`expected a must-be-an-object error, got: ${JSON.stringify(errs)}`,
);
});
test('reviewerZeroIsRejected', () => {
// Falsy-but-present: 0 must not be misread as absent (only `undefined` is).
const errs = validateReviewerBody({ id: 'x', reviewer: 0 });
assert.ok(
errs.some((e) => e.includes('reviewer must be an object (got: number)')),
`expected a must-be-an-object error, got: ${JSON.stringify(errs)}`,
);
});
test('reviewerStringIsRejected', () => {
const errs = validateReviewerBody({ id: 'x', reviewer: 'spawn' });
assert.ok(
errs.some((e) => e.includes('reviewer must be an object (got: string)')),
`expected a must-be-an-object error, got: ${JSON.stringify(errs)}`,
);
});
test('reviewerArrayIsRejected', () => {
const errs = validateReviewerBody({ id: 'x', reviewer: [] });
assert.ok(
errs.some((e) => e.includes('reviewer must be an object (got: array)')),
`expected an array-is-not-a-body error, got: ${JSON.stringify(errs)}`,
);
});
test('emptyReviewerBodyReportsAllMissingFields', () => {
const errs = validateReviewerBody({ id: 'x', reviewer: {} });
const requiredFieldMarkers = [
'reviewer.slug',
'reviewer.flags',
'reviewer.transport',
'reviewer.probe',
'reviewer.invoke',
'reviewer.timeoutFloorMs',
'reviewer.emptyOutput',
'reviewer.reviewsSection',
'reviewer.evidenceClass',
'reviewer.requiresBinaries',
'reviewer.promptBudgetKey',
'reviewer.handler',
];
for (const marker of requiredFieldMarkers) {
assert.ok(
errs.some((e) => e.includes(marker)),
`expected an error mentioning ${marker}, got: ${JSON.stringify(errs)}`,
);
}
assert.equal(
errs.length,
requiredFieldMarkers.length,
`expected exactly one error per required field (not just the first), got: ${JSON.stringify(errs)}`,
);
});
test('unknownFieldInsideReviewerBodyWarnsButValidates', () => {
const lane = validLane();
lane.futureField = 'from-a-newer-msd';
const cap = { id: 'cap-x', reviewer: lane };
const errs = validateReviewerBody(cap);
assert.deepEqual(errs, [], `unknown field must not be a validation error, got: ${JSON.stringify(errs)}`);
// Asserted on the typed IR, not the rendered prose (CONTRIBUTING.md,
// "Prohibited: Raw Text Matching on Test Outputs"). The `message` field
// exists for operator console output only.
const records = collectReviewerWarningRecords(cap);
assert.equal(records.length, 1, `expected exactly one record, got: ${JSON.stringify(records)}`);
assert.equal(records[0].code, REVIEWER_WARNING.UNKNOWN_REVIEWER_FIELD);
assert.equal(records[0].capId, 'cap-x');
assert.equal(records[0].field, 'reviewer.futureField');
assert.deepEqual(records[0].knownFields, [...KNOWN_REVIEWER_FIELDS]);
// The renderer still produces one string per record for the two production
// consumers (gen-capability-registry -> stderr, capability-loader -> OverlayMeta.warnings).
assert.equal(collectReviewerWarnings(cap).length, records.length);
});
test('unknownRoleIsRejectedWithEnumeratedMembers', () => {
const cap = capWith({ role: 'wat' });
const errs = validateCapability(cap, cap.id);
assert.ok(
errs.some((e) => e.includes('role must be one of: feature, runtime, reviewer')),
`expected the role error to enumerate all three members, got: ${JSON.stringify(errs)}`,
);
});
test('reviewerRoleDoesNotRequireRuntimeCompat', () => {
const cap = capWith();
assert.equal(cap.runtimeCompat, undefined, 'fixture must not declare runtimeCompat');
const errs = validateCapability(cap, cap.id);
assert.deepEqual(errs, [], `expected no errors (runtimeCompat is a runtime-only concern), got: ${JSON.stringify(errs)}`);
});
test('reviewerRoleRejectsRuntimeBody', () => {
const cap = capWith({ runtime: { configFormat: 'toml' } });
const errs = validateCapability(cap, cap.id);
assert.ok(
errs.some((e) => e.includes('role:reviewer capability must not have a "runtime" body')),
`expected a no-runtime-body error, got: ${JSON.stringify(errs)}`,
);
});
test('reviewerRoleRejectsFeatureOnlyFields', () => {
const cap = capWith({ skills: ['s'], agents: ['a'], steps: [{ point: 'plan:pre' }] });
const errs = validateCapability(cap, cap.id);
for (const field of ['skills', 'agents', 'steps']) {
assert.ok(
errs.some((e) => e.includes(`role:reviewer capability must not have "${field}"`)),
`expected a feature-only-field error for "${field}", got: ${JSON.stringify(errs)}`,
);
}
});
});
// ─── B. transport discriminator (D2) ───────────────────────────────────────
describe('B. transport discriminator (D2)', () => {
test('transportIsRequiredAndNeverInferred', () => {
const lane = laneOverride((l) => { delete l.transport; });
const errs = validateReviewerBody({ id: 'x', reviewer: lane });
assert.ok(
errs.some((e) => e.includes('reviewer.transport must be one of: spawn, openai-http')),
`expected a transport-required error, got: ${JSON.stringify(errs)}`,
);
});
test('spawnTransportAcceptsSpawnInvoke', () => {
const errs = validateReviewerBody({ id: 'x', reviewer: validLane() });
assert.deepEqual(errs, [], `expected no errors, got: ${JSON.stringify(errs)}`);
});
test('httpTransportAcceptsHttpInvoke', () => {
const errs = validateReviewerBody({ id: 'x', reviewer: validHttpLane() });
assert.deepEqual(errs, [], `expected no errors, got: ${JSON.stringify(errs)}`);
});
test('spawnTransportRejectsHttpOnlyInvokeFields', () => {
const lane = laneOverride((l) => { l.invoke.hostConfigKey = 'x.y'; });
const errs = validateReviewerBody({ id: 'x', reviewer: lane });
assert.ok(
errs.some((e) => e.includes('reviewer.invoke.hostConfigKey is not permitted for transport "spawn"')),
`expected a forbidden-field error, got: ${JSON.stringify(errs)}`,
);
});
test('httpTransportRejectsSpawnOnlyInvokeFields', () => {
const lane = httpOverride((l) => { l.invoke.binary = 'lm-studio'; });
const errs = validateReviewerBody({ id: 'x', reviewer: lane });
assert.ok(
errs.some((e) => e.includes('reviewer.invoke.binary is not permitted for transport "openai-http"')),
`expected a forbidden-field error, got: ${JSON.stringify(errs)}`,
);
});
test('invokeWithBothSubShapesIsRejected', () => {
const lane = laneOverride((l) => { l.invoke.hostConfigKey = 'x.y'; });
const errs = validateReviewerBody({ id: 'x', reviewer: lane });
assert.ok(
errs.some((e) => e.includes('reviewer.invoke mixes spawn-only fields') && e.includes('openai-http-only fields')),
`expected D2's exact both-subshapes error, got: ${JSON.stringify(errs)}`,
);
});
test('invokeWithNeitherSubShapeIsRejected', () => {
const lane = laneOverride((l) => { l.invoke = {}; });
const errs = validateReviewerBody({ id: 'x', reviewer: lane });
assert.ok(
errs.some((e) => e.includes('reviewer.invoke.binary must be a non-empty string for transport "spawn"')),
`expected the spawn sub-shape to be validated against an empty invoke, got: ${JSON.stringify(errs)}`,
);
assert.ok(
!errs.some((e) => e.includes('mixes spawn-only fields')),
`neither-subshape must not ALSO report the mixed-subshape error, got: ${JSON.stringify(errs)}`,
);
});
test('unknownTransportEnumeratesValidMembers', () => {
const lane = laneOverride((l) => { l.transport = 'grpc'; });
const errs = validateReviewerBody({ id: 'x', reviewer: lane });
assert.ok(
errs.some((e) => e.includes('reviewer.transport must be one of: spawn, openai-http') && e.includes('"grpc"')),
`expected an unknown-transport error enumerating valid members, got: ${JSON.stringify(errs)}`,
);
});
test('invokeIsRequired', () => {
const lane = laneOverride((l) => { delete l.invoke; });
const errs = validateReviewerBody({ id: 'x', reviewer: lane });
assert.ok(
errs.some((e) => e.includes('reviewer.invoke must be an object')),
`expected an invoke-required error, got: ${JSON.stringify(errs)}`,
);
});
test('invokeNullIsRejected', () => {
const lane = laneOverride((l) => { l.invoke = null; });
const errs = validateReviewerBody({ id: 'x', reviewer: lane });
assert.ok(
errs.some((e) => e.includes('reviewer.invoke must be an object')),
`expected an invoke-must-be-object error, got: ${JSON.stringify(errs)}`,
);
});
test('invokeArrayIsRejected', () => {
const lane = laneOverride((l) => { l.invoke = []; });
const errs = validateReviewerBody({ id: 'x', reviewer: lane });
assert.ok(
errs.some((e) => e.includes('reviewer.invoke must be an object')),
`expected an invoke-must-be-object error, got: ${JSON.stringify(errs)}`,
);
});
test('reservedNameAsTransportIsRejected', () => {
// A reserved JS name is rejected by closed-enum membership itself — a
// VALID_* set never contains __proto__/constructor/prototype, so no separate
// reserved-name branch is needed here and the enum error is the more useful
// one because it names the valid members. The inline literal guards stay
// where they do real work: the key-derived write sites.
for (const reserved of ['__proto__', 'constructor', 'prototype']) {
const lane = laneOverride((l) => { l.transport = reserved; });
const errs = validateReviewerBody({ id: 'x', reviewer: lane });
assert.ok(
errs.some((e) => e.includes('reviewer.transport must be one of: spawn, openai-http')),
`expected ${reserved} to be rejected by enum membership, got: ${JSON.stringify(errs)}`,
);
}
});
});
// ─── C. spawn invoke fields ─────────────────────────────────────────────────
describe('C. spawn invoke fields', () => {
test('spawnBinaryIsRequired', () => {
const lane = laneOverride((l) => { delete l.invoke.binary; });
const errs = validateReviewerBody({ id: 'x', reviewer: lane });
assert.ok(
errs.some((e) => e.includes('reviewer.invoke.binary must be a non-empty string for transport "spawn"')),
`expected a binary-required error, got: ${JSON.stringify(errs)}`,
);
});
test('spawnEmptyBinaryIsRejected', () => {
const lane = laneOverride((l) => { l.invoke.binary = ''; });
const errs = validateReviewerBody({ id: 'x', reviewer: lane });
assert.ok(
errs.some((e) => e.includes('reviewer.invoke.binary must be a non-empty string for transport "spawn"')),
`expected an empty-binary error, got: ${JSON.stringify(errs)}`,
);
});
test('spawnArgsArrayIsRequired', () => {
const lane = laneOverride((l) => { delete l.invoke.args; });
const errs = validateReviewerBody({ id: 'x', reviewer: lane });
assert.ok(
errs.some((e) => e.includes('reviewer.invoke.args must be an array')),
`expected an args-required error, got: ${JSON.stringify(errs)}`,
);
});
test('spawnEmptyArgsArrayIsValid', () => {
const lane = laneOverride((l) => { l.invoke.args = []; });
const errs = validateReviewerBody({ id: 'x', reviewer: lane });
assert.deepEqual(errs, [], `expected no errors (a lane may take no args), got: ${JSON.stringify(errs)}`);
});
test('spawnArgsRejectsNonStringElement', () => {
const lane = laneOverride((l) => { l.invoke.args = ['-p', 7]; });
const errs = validateReviewerBody({ id: 'x', reviewer: lane });
assert.ok(
errs.some((e) => e.includes('reviewer.invoke.args entry 7 must be a string')),
`expected a non-string-element error, got: ${JSON.stringify(errs)}`,
);
});
test('promptChannelStdinIsValid', () => {
const lane = laneOverride((l) => { l.invoke.promptChannel = 'stdin'; });
const errs = validateReviewerBody({ id: 'x', reviewer: lane });
assert.deepEqual(errs, [], `expected no errors, got: ${JSON.stringify(errs)}`);
});
test('promptChannelNoneIsValidPerAmendment', () => {
const lane = laneOverride((l) => { l.invoke.promptChannel = 'none'; });
const errs = validateReviewerBody({ id: 'x', reviewer: lane });
assert.deepEqual(errs, [], `expected no errors (A1 amendment), got: ${JSON.stringify(errs)}`);
});
test('promptChannelArgvFileRefIsValid', () => {
const lane = laneOverride((l) => { l.invoke.promptChannel = 'argv-file-ref'; });
const errs = validateReviewerBody({ id: 'x', reviewer: lane });
assert.deepEqual(errs, [], `expected no errors, got: ${JSON.stringify(errs)}`);
});
// Zero shipped lanes use promptChannel:'argv' — this row is its only coverage
// until Phase 5b ships one (40-design.md C6 / Known Limits).
test('promptChannelArgvIsValidDespiteNoShippedLane', () => {
const lane = laneOverride((l) => { l.invoke.promptChannel = 'argv'; });
const errs = validateReviewerBody({ id: 'x', reviewer: lane });
assert.deepEqual(errs, [], `expected no errors, got: ${JSON.stringify(errs)}`);
});
test('outputChannelStdoutIsValid', () => {
const lane = laneOverride((l) => { l.invoke.outputChannel = 'stdout'; });
const errs = validateReviewerBody({ id: 'x', reviewer: lane });
assert.deepEqual(errs, [], `expected no errors, got: ${JSON.stringify(errs)}`);
});
test('fileArgOutputWithOutputArgIsValid', () => {
const lane = laneOverride((l) => {
l.invoke.outputChannel = 'file-arg';
l.invoke.outputArg = 'out.txt';
});
const errs = validateReviewerBody({ id: 'x', reviewer: lane });
assert.deepEqual(errs, [], `expected no errors (A2/A3 amendment), got: ${JSON.stringify(errs)}`);
});
test('fileArgOutputWithoutOutputArgIsRejected', () => {
const lane = laneOverride((l) => { l.invoke.outputChannel = 'file-arg'; });
const errs = validateReviewerBody({ id: 'x', reviewer: lane });
assert.ok(
errs.some((e) => e.includes('reviewer.invoke.outputArg is required') && e.includes('"file-arg"')),
`expected a required-iff error, got: ${JSON.stringify(errs)}`,
);
});
test('stdoutOutputWithOutputArgIsRejected', () => {
const lane = laneOverride((l) => {
l.invoke.outputChannel = 'stdout';
l.invoke.outputArg = 'out.txt';
});
const errs = validateReviewerBody({ id: 'x', reviewer: lane });
assert.ok(
errs.some((e) => e.includes('reviewer.invoke.outputArg is only permitted when outputChannel is "file-arg"')),
`expected a forbidden-field error, got: ${JSON.stringify(errs)}`,
);
});
test('modelArgNullMeansNoOverride', () => {
const lane = laneOverride((l) => { l.invoke.modelArg = null; });
const errs = validateReviewerBody({ id: 'x', reviewer: lane });
assert.deepEqual(errs, [], `expected no errors, got: ${JSON.stringify(errs)}`);
});
test('modelArgEmptyStringIsRejected', () => {
const lane = laneOverride((l) => { l.invoke.modelArg = ''; });
const errs = validateReviewerBody({ id: 'x', reviewer: lane });
assert.ok(
errs.some((e) => e.includes('reviewer.invoke.modelArg must be a non-empty string or null')),
`expected an empty-string-is-not-none error, got: ${JSON.stringify(errs)}`,
);
});
test('effortChannelAcceptsAllThreeMembers', () => {
for (const effortChannel of VALID_LANE_EFFORT_CHANNELS) {
const lane = laneOverride((l) => { l.invoke.effortChannel = effortChannel; });
const errs = validateReviewerBody({ id: 'x', reviewer: lane });
assert.deepEqual(errs, [], `effortChannel=${effortChannel} expected no errors, got: ${JSON.stringify(errs)}`);
}
});
// `invoke.env` (#2483). OPTIONAL, unlike every sibling above — absent is the common case, so the
// absent and present-and-valid rows are both real behavior rather than padding.
// NOTE: `env`'s optionality has no test of its own, deliberately. The env-less state is already
// validated by spawnTransportAcceptsSpawnInvoke above (validLane() declares no `env`) and the
// env-bearing state by envAcceptsStringPairs below, so a dedicated optionality test asserts no
// behavior neither of those reaches — it is organization, not coverage.
test('envAcceptsStringPairs', () => {
const lane = laneOverride((l) => { l.invoke.env = { A_VAR: '1', _B2: '' }; });
const errs = validateReviewerBody({ id: 'x', reviewer: lane });
assert.deepEqual(errs, [], `expected no errors, got: ${JSON.stringify(errs)}`);
});
test('envRejectsNonObjectShapes', () => {
for (const bad of [['A=1'], 'A=1', 42, null, true]) {
const lane = laneOverride((l) => { l.invoke.env = bad; });
const errs = validateReviewerBody({ id: 'x', reviewer: lane });
assert.ok(
errs.some((e) => e.includes('reviewer.invoke.env must be an object of environment name/value pairs')),
`env=${JSON.stringify(bad)} expected a shape error, got: ${JSON.stringify(errs)}`,
);
}
});
test('envRejectsNonStringValues', () => {
for (const bad of [1, null, { nested: true }, ['x']]) {
const lane = laneOverride((l) => { l.invoke.env = { FOO: bad }; });
const errs = validateReviewerBody({ id: 'x', reviewer: lane });
assert.ok(
errs.some((e) => e.includes('reviewer.invoke.env.FOO must be a string')),
`value=${JSON.stringify(bad)} expected a value-type error, got: ${JSON.stringify(errs)}`,
);
}
});
// Named for what it actually proves: rejection by a portable-name POLICY, not by impossibility.
// Measured — of the names below only NUL is rejected by spawnSync; `=`, a leading digit, a dash and
// a space are all carried to the child (`{'A=B':'v'}` arrives as the entry `A=B=v`). An earlier
// name and comment asserted these could not be expressed at all; that was wrong twice over.
test('envRejectsKeysOutsideThePortableNameGrammar', () => {
for (const bad of ['', 'A=B', '2LEADING_DIGIT', 'has space', 'has-dash']) {
const lane = laneOverride((l) => { l.invoke.env = { [bad]: '1' }; });
const errs = validateReviewerBody({ id: 'x', reviewer: lane });
assert.ok(
errs.some((e) => e.includes('is not a valid environment variable name')),
`key=${JSON.stringify(bad)} expected a key-grammar error, got: ${JSON.stringify(errs)}`,
);
}
});
// Built with JSON.parse deliberately: in an object LITERAL `__proto__` is special-cased and creates
// no own key at all, so a literal-built fixture would assert nothing. A manifest is JSON, where it
// IS an own key — it passes the grammar above, then vanishes when assigned onto the resolver's
// accumulator (the inherited setter consumes the assignment; for a string value it is a no-op and
// does not even change the prototype). Declared-but-never-delivered is what this rejection catches.
test('envRejectsProtoKeyThatWouldSilentlyVanish', () => {
const lane = laneOverride((l) => { l.invoke.env = JSON.parse('{"__proto__":"1"}'); });
const errs = validateReviewerBody({ id: 'x', reviewer: lane });
assert.ok(
errs.some((e) => e.includes('reviewer.invoke.env key "__proto__" is not permitted')),
`expected a reserved-key error, got: ${JSON.stringify(errs)}`,
);
});
// Defence in depth, and the test says so: the BOUNDARY is install-time consent (capability-trust
// discloses every declared pair and binds it to the signature), so this list being incomplete is a
// known property rather than a gap. What it buys is that the highest-confidence, lowest-legitimacy
// routes cannot be taken quietly. `PATH` is included deliberately — it is the most complete
// primitive of the set and no shipped reviewer manifest declares it (asserted separately below).
test('envRejectsExecutionPrimitiveNames', () => {
for (const bad of ['PATH', 'NODE_OPTIONS', 'LD_PRELOAD', 'DYLD_INSERT_LIBRARIES', 'BASH_ENV',
'PYTHONPATH', 'PERL5OPT', 'RUBYOPT', 'GIT_SSH_COMMAND', 'JAVA_TOOL_OPTIONS']) {
const lane = laneOverride((l) => { l.invoke.env = { [bad]: '/tmp/evil' }; });
const errs = validateReviewerBody({ id: 'x', reviewer: lane });
assert.ok(
errs.some((e) => e.includes(`reviewer.invoke.env key "${bad}" is not permitted`)),
`key=${bad} expected an execution-primitive rejection, got: ${JSON.stringify(errs)}`,
);
}
});
// Case folding is not pedantry: Windows environment lookup is case-insensitive, so `Path` reaches
// the child as `PATH`. An exact-case set is bypassed by changing one letter, which makes it worse
// than no list — it reads as a control while passing the exact input it names.
test('envDenylistIsCaseInsensitive', () => {
for (const bad of ['Path', 'path', 'node_options', 'Node_Options', 'Ld_Preload', 'bash_env']) {
const lane = laneOverride((l) => { l.invoke.env = { [bad]: '/tmp/evil' }; });
const errs = validateReviewerBody({ id: 'x', reviewer: lane });
assert.ok(
errs.some((e) => e.includes('is not permitted (it makes the spawned reviewer')),
`key=${bad} must be denied regardless of case, got: ${JSON.stringify(errs)}`,
);
}
});
// The rejection above must not break a lane that ships today. If this ever fails, the denylist has
// outgrown its evidence and the entry that broke it needs a decision, not a silent removal.
test('noShippedReviewerDeclaresADeniedEnvKey', () => {
const fs = require('node:fs');
const path = require('node:path');
const capsDir = path.join(__dirname, '..', 'capabilities');
const offenders = [];
for (const d of fs.readdirSync(capsDir)) {
const f = path.join(capsDir, d, 'capability.json');
if (!fs.existsSync(f)) continue;
const m = JSON.parse(fs.readFileSync(f, 'utf8'));
const env = m.reviewer && m.reviewer.invoke && m.reviewer.invoke.env;
if (!env) continue;
const errs = validateReviewerBody({ id: m.id || d, reviewer: m.reviewer });
const denied = errs.filter((e) => e.includes('is not permitted (it makes the spawned reviewer'));
if (denied.length) offenders.push(`${d}: ${denied.join('; ')}`);
}
assert.deepStrictEqual(offenders, [], 'a shipped reviewer capability declares a denied env key');
});
test('httpTransportRejectsEnv', () => {
const lane = httpOverride((l) => { l.invoke.env = { SNEAK: '1' }; });
const errs = validateReviewerBody({ id: 'x', reviewer: lane });
assert.ok(
errs.some((e) => e.includes('reviewer.invoke.env is not permitted for transport "openai-http"')),
`expected a forbidden-field error, got: ${JSON.stringify(errs)}`,
);
});
});
// ─── D. openai-http invoke fields ──────────────────────────────────────────
describe('D. openai-http invoke fields', () => {
test('httpHostConfigKeyIsRequired', () => {
const lane = httpOverride((l) => { delete l.invoke.hostConfigKey; });
const errs = validateReviewerBody({ id: 'x', reviewer: lane });
assert.ok(
errs.some((e) => e.includes('reviewer.invoke.hostConfigKey must be a non-empty dotted config key')),
`expected a hostConfigKey-required error, got: ${JSON.stringify(errs)}`,
);
});
test('httpEmptyHostConfigKeyIsRejected', () => {
const lane = httpOverride((l) => { l.invoke.hostConfigKey = ''; });
const errs = validateReviewerBody({ id: 'x', reviewer: lane });
assert.ok(
errs.some((e) => e.includes('reviewer.invoke.hostConfigKey must be a non-empty dotted config key')),
`expected an empty-hostConfigKey error, got: ${JSON.stringify(errs)}`,
);
});
test('httpPathIsRequiredAndNonEmpty', () => {
for (const badPath of [undefined, '']) {
const lane = httpOverride((l) => {
if (badPath === undefined) delete l.invoke.path;
else l.invoke.path = badPath;
});
const errs = validateReviewerBody({ id: 'x', reviewer: lane });
assert.ok(
errs.some((e) => e.includes('reviewer.invoke.path must be a non-empty string for transport "openai-http"')),
`path=${JSON.stringify(badPath)}: expected a path-required error, got: ${JSON.stringify(errs)}`,
);
}
});
test('modelDiscoveryAcceptsBothMembers', () => {
for (const modelDiscovery of VALID_MODEL_DISCOVERY) {
const lane = httpOverride((l) => { l.invoke.modelDiscovery = modelDiscovery; });
const errs = validateReviewerBody({ id: 'x', reviewer: lane });
assert.deepEqual(errs, [], `modelDiscovery=${modelDiscovery} expected no errors, got: ${JSON.stringify(errs)}`);
}
});
test('httpTransportRejectsNonNoneEffortChannel', () => {
const lane = httpOverride((l) => { l.invoke.effortChannel = 'argv'; });
const errs = validateReviewerBody({ id: 'x', reviewer: lane });
assert.ok(
errs.some((e) => e.includes('reviewer.invoke.effortChannel must be "none" for transport "openai-http"')),
`expected an effortChannel-fixed-to-none error, got: ${JSON.stringify(errs)}`,
);
});
});
// ─── E. probe (D7) — bounded-probe control ─────────────────────────────────
describe('E. probe (D7) — bounded-probe control', () => {
test('probeIsRequiredObject', () => {
const laneAbsent = laneOverride((l) => { delete l.probe; });
const errsAbsent = validateReviewerBody({ id: 'x', reviewer: laneAbsent });
assert.ok(
errsAbsent.some((e) => e.includes('reviewer.probe must be an object with a "kind" from:')),
`absent probe: expected a probe-required error, got: ${JSON.stringify(errsAbsent)}`,
);
const laneNonObject = laneOverride((l) => { l.probe = 'not-an-object'; });
const errsNonObject = validateReviewerBody({ id: 'x', reviewer: laneNonObject });
assert.ok(
errsNonObject.some((e) => e.includes('reviewer.probe must be an object with a "kind" from:')),
`non-object probe: expected a probe-required error, got: ${JSON.stringify(errsNonObject)}`,
);
});
test('commandExistsProbeIsValid', () => {
const lane = laneOverride((l) => { l.probe = { kind: 'command-exists', binary: 'my-lane' }; });
const errs = validateReviewerBody({ id: 'x', reviewer: lane });
assert.deepEqual(errs, [], `expected no errors, got: ${JSON.stringify(errs)}`);
});
// Zero shipped lanes use probe.kind:'command-capability' — this row is its
// only coverage until Phase 5b ships one.
test('commandCapabilityProbeIsValidDespiteNoShippedLane', () => {
const lane = laneOverride((l) => {
l.probe = { kind: 'command-capability', binary: 'my-lane', needle: 'v1.2', timeoutMs: COMMAND_CAPABILITY_PROBE_TIMEOUT_FIXTURE_MS };
});
const errs = validateReviewerBody({ id: 'x', reviewer: lane });
assert.deepEqual(errs, [], `expected no errors, got: ${JSON.stringify(errs)}`);
});
test('httpReachableProbeIsValid', () => {
const lane = laneOverride((l) => {
l.probe = { kind: 'http-reachable', hostConfigKey: 'lmStudio.baseUrl', path: '/v1/models', timeoutMs: HTTP_REACHABLE_PROBE_TIMEOUT_FIXTURE_MS };
});
const errs = validateReviewerBody({ id: 'x', reviewer: lane });
assert.deepEqual(errs, [], `expected no errors, got: ${JSON.stringify(errs)}`);
});
test('commandCapabilityProbeRequiresTimeout', () => {
const lane = laneOverride((l) => {
l.probe = { kind: 'command-capability', binary: 'my-lane', needle: 'v1.2' };
});
const errs = validateReviewerBody({ id: 'x', reviewer: lane });
assert.ok(
errs.some((e) => e.includes('reviewer.probe.timeoutMs must be a positive integer of milliseconds for kind "command-capability"')),
`expected a timeout-required error, got: ${JSON.stringify(errs)}`,
);
});
test('httpReachableProbeRequiresTimeout', () => {
const lane = laneOverride((l) => {
l.probe = { kind: 'http-reachable', hostConfigKey: 'lmStudio.baseUrl', path: '/v1/models' };
});
const errs = validateReviewerBody({ id: 'x', reviewer: lane });
assert.ok(
errs.some((e) => e.includes('reviewer.probe.timeoutMs must be a positive integer of milliseconds for kind "http-reachable"')),
`expected a timeout-required error, got: ${JSON.stringify(errs)}`,
);
});
test('probeTimeoutZeroIsRejected', () => {
const lane = laneOverride((l) => {
l.probe = { kind: 'http-reachable', hostConfigKey: 'x.y', path: '/z', timeoutMs: MANIFEST_PROBE_TIMEOUT_BOUNDARY_ZERO_MS };
});
const errs = validateReviewerBody({ id: 'x', reviewer: lane });
assert.ok(
errs.some((e) => e.includes('reviewer.probe.timeoutMs must be a positive integer of milliseconds') && e.includes('(got: 0)')),
`expected a boundary (limit-1) rejection, got: ${JSON.stringify(errs)}`,
);
});
test('probeTimeoutOneIsAccepted', () => {
const lane = laneOverride((l) => {
l.probe = { kind: 'http-reachable', hostConfigKey: 'x.y', path: '/z', timeoutMs: MANIFEST_PROBE_TIMEOUT_BOUNDARY_ONE_MS };
});
const errs = validateReviewerBody({ id: 'x', reviewer: lane });
assert.deepEqual(errs, [], `expected no errors (boundary: limit), got: ${JSON.stringify(errs)}`);
});
test('probeNegativeTimeoutIsRejected', () => {
const lane = laneOverride((l) => {
l.probe = { kind: 'http-reachable', hostConfigKey: 'x.y', path: '/z', timeoutMs: MANIFEST_PROBE_TIMEOUT_BOUNDARY_NEGATIVE_MS };
});
const errs = validateReviewerBody({ id: 'x', reviewer: lane });
assert.ok(
errs.some((e) => e.includes('reviewer.probe.timeoutMs must be a positive integer of milliseconds') && e.includes('(got: -1)')),
`expected a negative-timeout rejection, got: ${JSON.stringify(errs)}`,
);
});
test('probeFractionalTimeoutIsRejected', () => {
const lane = laneOverride((l) => {
l.probe = { kind: 'http-reachable', hostConfigKey: 'x.y', path: '/z', timeoutMs: MANIFEST_PROBE_TIMEOUT_BOUNDARY_NON_INTEGER_MS };
});
const errs = validateReviewerBody({ id: 'x', reviewer: lane });
assert.ok(
errs.some((e) => e.includes('reviewer.probe.timeoutMs must be a positive integer of milliseconds') && e.includes('(got: 1.5)')),
`expected a fractional-timeout rejection (integer only), got: ${JSON.stringify(errs)}`,
);
});
test('probeNumericStringTimeoutIsRejected', () => {
const lane = laneOverride((l) => {
l.probe = { kind: 'http-reachable', hostConfigKey: 'x.y', path: '/z', timeoutMs: '900' };
});
const errs = validateReviewerBody({ id: 'x', reviewer: lane });
assert.ok(
errs.some((e) => e.includes('reviewer.probe.timeoutMs must be a positive integer of milliseconds') && e.includes('(got: "900")')),
`expected a numeric-string rejection, got: ${JSON.stringify(errs)}`,
);
});
test('probeNaNTimeoutIsRejected', () => {
const lane = laneOverride((l) => {
l.probe = { kind: 'http-reachable', hostConfigKey: 'x.y', path: '/z', timeoutMs: NaN };
});
const errs = validateReviewerBody({ id: 'x', reviewer: lane });
assert.ok(
errs.some((e) => e.includes('reviewer.probe.timeoutMs must be a positive integer of milliseconds')),
`expected a NaN rejection, got: ${JSON.stringify(errs)}`,
);
});
test('probeInfiniteTimeoutIsRejected', () => {
const lane = laneOverride((l) => {
l.probe = { kind: 'http-reachable', hostConfigKey: 'x.y', path: '/z', timeoutMs: Infinity };
});
const errs = validateReviewerBody({ id: 'x', reviewer: lane });
// Note: JSON.stringify(Infinity) renders as "null" in the message's "(got: …)"
// suffix — that's a property of JSON.stringify, not of this assertion; the
// load-bearing check is that Infinity (literally unbounded, the exact defect
// D7 exists to prevent) is rejected by the same branch as every other
// non-positive-integer value.
assert.ok(
errs.some((e) => e.includes('reviewer.probe.timeoutMs must be a positive integer of milliseconds for kind "http-reachable"')),
`expected an unbounded-timeout rejection, got: ${JSON.stringify(errs)}`,
);
});
test('commandExistsProbeRejectsTimeout', () => {
const lane = laneOverride((l) => {
l.probe = { kind: 'command-exists', binary: 'my-lane', timeoutMs: COMMAND_EXISTS_FORBIDDEN_TIMEOUT_MS };
});
const errs = validateReviewerBody({ id: 'x', reviewer: lane });
assert.ok(
errs.some((e) => e.includes('reviewer.probe.timeoutMs is not permitted for kind "command-exists"')),
`expected a forbidden-timeout error (no process is started), got: ${JSON.stringify(errs)}`,
);
});
test('unknownProbeKindEnumeratesValidMembers', () => {
const lane = laneOverride((l) => { l.probe = { kind: 'nope' }; });
const errs = validateReviewerBody({ id: 'x', reviewer: lane });
assert.ok(
errs.some((e) => e.includes('reviewer.probe.kind must be one of: command-exists, command-capability, http-reachable')),
`expected an unknown-kind error enumerating all three kinds, got: ${JSON.stringify(errs)}`,
);
});
test('commandCapabilityProbeRequiresNeedle', () => {
const lane = laneOverride((l) => {
l.probe = { kind: 'command-capability', binary: 'my-lane', timeoutMs: COMMAND_CAPABILITY_PROBE_TIMEOUT_FIXTURE_MS };
});
const errs = validateReviewerBody({ id: 'x', reviewer: lane });
assert.ok(
errs.some((e) => e.includes('reviewer.probe.needle must be a non-empty string for kind "command-capability"')),
`expected a needle-required error, got: ${JSON.stringify(errs)}`,
);
});
});
// ─── F. Lane scalars ────────────────────────────────────────────────────────
describe('F. Lane scalars', () => {
test('slugIsRequiredNonEmptyString', () => {
for (const badSlug of [undefined, '', 42]) {
const lane = laneOverride((l) => {
if (badSlug === undefined) delete l.slug;
else l.slug = badSlug;
});
const errs = validateReviewerBody({ id: 'x', reviewer: lane });
assert.ok(
errs.some((e) => e.includes('reviewer.slug must be a non-empty string')),
`slug=${JSON.stringify(badSlug)}: expected a slug-required error, got: ${JSON.stringify(errs)}`,
);
}
});
test('snakeCaseSlugIsAcceptedUnlikeCapabilityId', () => {
const lane = laneOverride((l) => { l.slug = 'lm_studio'; });
const errs = validateReviewerBody({ id: 'x', reviewer: lane });
assert.deepEqual(errs, [], `expected no errors (slug is not KEBAB_RE-checked), got: ${JSON.stringify(errs)}`);
});
// DEFECT.GENERATIVE-FIX parity assertion. The slug grammar exists in TWO places
// and cannot be reduced to one: Phase 1 owns it in src/review-lane-descriptor.cts,
// but that module compiles to gitignored build output, and the manifest validator
// is a committed plain .cjs that must load on a fresh worktree before build:lib.
// A divergence here silently reintroduces the translation layer ADR-2782 exists
// to delete — a slug the core descriptor accepts would be rejected by the
// manifest validator, and only for lanes nobody has shipped yet, so no other
// test would notice. This caught a real divergence in review: the validator
// required a leading LETTER while the descriptor allows a leading digit, which
// would have rejected a model-named lane such as `4o-mini`.
test('laneSlugGrammarMatchesPhase1Descriptor', () => {
// Built artifact — present after `npm run build:lib`, which CI runs before tests.
const descriptor = require('../msd-core/bin/lib/review-lane-descriptor.cjs');
assert.ok(
descriptor.LANE_SLUG_RE instanceof RegExp,
'Phase 1 must export LANE_SLUG_RE; if it moved, this parity assertion needs updating, not deleting',
);
assert.equal(
String(LANE_SLUG_RE), String(descriptor.LANE_SLUG_RE),
'reviewer.slug grammar has diverged between the manifest validator and the Phase 1 core descriptor',
);
// Behavioural parity, not just source equality: the same inputs must get the
// same verdict from both surfaces.
for (const slug of ['gemini', 'lm_studio', 'llama_cpp', '4o-mini', '2b-local']) {
const lane = laneOverride((l) => { l.slug = slug; });
const accepted = validateReviewerBody({ id: 'x', reviewer: lane }).length === 0;
assert.equal(
accepted, descriptor.LANE_SLUG_RE.test(slug),
`slug "${slug}": manifest validator and core descriptor disagree`,
);
}
});
test('slugRejectsUppercase', () => {
const lane = laneOverride((l) => { l.slug = 'Lm-Studio'; });
const errs = validateReviewerBody({ id: 'x', reviewer: lane });
assert.ok(
errs.some((e) => e.includes('reviewer.slug "Lm-Studio" must match')),
`expected an uppercase-rejection error, got: ${JSON.stringify(errs)}`,
);
});
test('slugRejectsWhitespace', () => {
const lane = laneOverride((l) => { l.slug = 'lm studio'; });
const errs = validateReviewerBody({ id: 'x', reviewer: lane });
assert.ok(
errs.some((e) => e.includes('reviewer.slug "lm studio" must match')),
`expected a whitespace-rejection error, got: ${JSON.stringify(errs)}`,
);
});
test('slugRejectsFlagSyntax', () => {
const lane = laneOverride((l) => { l.slug = '--x'; });
const errs = validateReviewerBody({ id: 'x', reviewer: lane });
assert.ok(
errs.some((e) => e.includes('reviewer.slug "--x" must match')),
`expected a flag-syntax-rejection error (a slug is not a flag), got: ${JSON.stringify(errs)}`,
);
});
test('flagsArrayIsRequired', () => {
for (const badFlags of [undefined, 'not-an-array']) {
const lane = laneOverride((l) => {
if (badFlags === undefined) delete l.flags;
else l.flags = badFlags;
});
const errs = validateReviewerBody({ id: 'x', reviewer: lane });
assert.ok(
errs.some((e) => e.includes('reviewer.flags must be an array of CLI flags')),
`flags=${JSON.stringify(badFlags)}: expected a flags-required error, got: ${JSON.stringify(errs)}`,
);
}
});
test('emptyFlagsArrayIsRejected', () => {
const lane = laneOverride((l) => { l.flags = []; });
const errs = validateReviewerBody({ id: 'x', reviewer: lane });
assert.ok(
errs.some((e) => e.includes('reviewer.flags must declare at least one flag')),
`expected an unnameable-lane rejection, got: ${JSON.stringify(errs)}`,
);
});
test('duplicateFlagWithinOneLaneIsRejected', () => {
const lane = laneOverride((l) => { l.flags = ['--x', '--x']; });
const errs = validateReviewerBody({ id: 'x', reviewer: lane });
assert.ok(
errs.some((e) => e.includes('reviewer.flags lists "--x" more than once')),
`expected a self-duplicate-flag error, got: ${JSON.stringify(errs)}`,
);
});
test('flagWithoutDoubleDashIsRejected', () => {
const lane = laneOverride((l) => { l.flags = ['x']; });
const errs = validateReviewerBody({ id: 'x', reviewer: lane });
assert.ok(
errs.some((e) => e.includes('reviewer.flags entry "x" must match')),
`expected a missing-double-dash error, got: ${JSON.stringify(errs)}`,
);
});
test('multipleFlagsPerLaneAreValidPerAmendment', () => {
const lane = laneOverride((l) => { l.flags = ['--antigravity', '--agy']; });
const errs = validateReviewerBody({ id: 'x', reviewer: lane });
assert.deepEqual(errs, [], `expected no errors (A4 amendment), got: ${JSON.stringify(errs)}`);
});
test('timeoutFloorIsRequired', () => {
const lane = laneOverride((l) => { delete l.timeoutFloorMs; });
const errs = validateReviewerBody({ id: 'x', reviewer: lane });
assert.ok(
errs.some((e) => e.includes('reviewer.timeoutFloorMs must be a positive integer of milliseconds')),
`expected a timeoutFloorMs-required error, got: ${JSON.stringify(errs)}`,
);
});
test('timeoutFloorZeroIsRejected', () => {
const lane = laneOverride((l) => { l.timeoutFloorMs = 0; });
const errs = validateReviewerBody({ id: 'x', reviewer: lane });
assert.ok(
errs.some((e) => e.includes('reviewer.timeoutFloorMs must be a positive integer of milliseconds') && e.includes('(got: 0)')),
`expected a boundary (limit-1) rejection, got: ${JSON.stringify(errs)}`,
);
});
test('timeoutFloorOneIsAccepted', () => {
const lane = laneOverride((l) => { l.timeoutFloorMs = 1; });
const errs = validateReviewerBody({ id: 'x', reviewer: lane });
assert.deepEqual(errs, [], `expected no errors (boundary: limit), got: ${JSON.stringify(errs)}`);
});
test('emptyOutputAcceptsBothMembers', () => {
for (const emptyOutput of VALID_EMPTY_OUTPUT) {
const lane = laneOverride((l) => { l.emptyOutput = emptyOutput; });
const errs = validateReviewerBody({ id: 'x', reviewer: lane });
assert.deepEqual(errs, [], `emptyOutput=${emptyOutput} expected no errors, got: ${JSON.stringify(errs)}`);
}
});
test('reviewsSectionIsRequiredNonEmpty', () => {
for (const bad of [undefined, '']) {
const lane = laneOverride((l) => {
if (bad === undefined) delete l.reviewsSection;
else l.reviewsSection = bad;
});
const errs = validateReviewerBody({ id: 'x', reviewer: lane });
assert.ok(
errs.some((e) => e.includes('reviewer.reviewsSection must be a non-empty string')),
`reviewsSection=${JSON.stringify(bad)}: expected a reviewsSection-required error, got: ${JSON.stringify(errs)}`,
);
}
});
test('evidenceClassAcceptsBothMembers', () => {
for (const evidenceClass of VALID_EVIDENCE_CLASSES) {
const lane = laneOverride((l) => { l.evidenceClass = evidenceClass; });
const errs = validateReviewerBody({ id: 'x', reviewer: lane });
assert.deepEqual(errs, [], `evidenceClass=${evidenceClass} expected no errors, got: ${JSON.stringify(errs)}`);
}
});
test('requiresBinariesArrayIsRequired', () => {
for (const bad of [undefined, 'not-an-array']) {
const lane = laneOverride((l) => {
if (bad === undefined) delete l.requiresBinaries;
else l.requiresBinaries = bad;
});
const errs = validateReviewerBody({ id: 'x', reviewer: lane });
assert.ok(
errs.some((e) => e.includes('reviewer.requiresBinaries must be an array')),
`requiresBinaries=${JSON.stringify(bad)}: expected a required-array error, got: ${JSON.stringify(errs)}`,
);
}
});
test('emptyRequiresBinariesIsValid', () => {
const lane = laneOverride((l) => { l.requiresBinaries = []; });
const errs = validateReviewerBody({ id: 'x', reviewer: lane });
assert.deepEqual(errs, [], `expected no errors (no external tool required), got: ${JSON.stringify(errs)}`);
});
test('promptBudgetKeyNullIsValid', () => {
const lane = laneOverride((l) => { l.promptBudgetKey = null; });
const errs = validateReviewerBody({ id: 'x', reviewer: lane });
assert.deepEqual(errs, [], `expected no errors, got: ${JSON.stringify(errs)}`);
});
test('promptBudgetKeyEmptyStringIsRejected', () => {
const lane = laneOverride((l) => { l.promptBudgetKey = ''; });
const errs = validateReviewerBody({ id: 'x', reviewer: lane });
assert.ok(
errs.some((e) => e.includes('reviewer.promptBudgetKey must be a dotted config key or null') && e.includes('(got: "")')),
`expected an empty-string-is-not-none error, got: ${JSON.stringify(errs)}`,
);
});
test('handlerNullIsValidDefault', () => {
const lane = laneOverride((l) => { l.handler = null; });
const errs = validateReviewerBody({ id: 'x', reviewer: lane });
assert.deepEqual(errs, [], `expected no errors (default), got: ${JSON.stringify(errs)}`);
});
test('handlerAcceptsEachFirstPartyMember', () => {
for (const handler of VALID_LANE_HANDLERS) {
const lane = laneOverride((l) => { l.handler = handler; });
const errs = validateReviewerBody({ id: 'x', reviewer: lane });
assert.deepEqual(errs, [], `handler=${handler} expected no errors, got: ${JSON.stringify(errs)}`);
}
});
test('unknownHandlerIsRejectedAtBuildTime', () => {
const lane = laneOverride((l) => { l.handler = 'acme'; });
const errs = validateReviewerBody({ id: 'x', reviewer: lane });
assert.ok(
errs.some((e) => e.includes('reviewer.handler must be null or one of: antigravity, openai-compatible') && e.includes('"acme"')),
`expected an unknown-handler error, got: ${JSON.stringify(errs)}`,
);
});
test('pathTraversalHandlerIsRejected', () => {
const lane = laneOverride((l) => { l.handler = '../evil'; });
const errs = validateReviewerBody({ id: 'x', reviewer: lane });
assert.ok(
errs.some((e) => e.includes('reviewer.handler must be null or one of: antigravity, openai-compatible') && e.includes('"../evil"')),
`expected a path-shaped-handler rejection, got: ${JSON.stringify(errs)}`,
);
});
});
// ─── F2. timeoutConfigKey (#3274) — mirrors modelConfigKey's D4 forward/backward-compat shape ──
describe('F2. timeoutConfigKey (#3274)', () => {
test('reviewer.timeoutConfigKey is optional — absent is valid (row 14)', () => {
const lane = laneOverride(() => {});
assert.equal(lane.timeoutConfigKey, undefined, 'fixture must not declare timeoutConfigKey');
const errs = validateReviewerBody({ id: 'x', reviewer: lane });
assert.deepEqual(errs, [], `expected no errors, got: ${JSON.stringify(errs)}`);
});
test('reviewer.timeoutConfigKey: null is valid (row 15)', () => {
const lane = laneOverride((l) => { l.timeoutConfigKey = null; });
const errs = validateReviewerBody({ id: 'x', reviewer: lane });
assert.deepEqual(errs, [], `expected no errors, got: ${JSON.stringify(errs)}`);
});
test('reviewer.timeoutConfigKey: a dotted string is valid (row 16)', () => {
const lane = laneOverride((l) => { l.timeoutConfigKey = 'review.timeouts.acme'; });
const errs = validateReviewerBody({ id: 'x', reviewer: lane });
assert.deepEqual(errs, [], `expected no errors, got: ${JSON.stringify(errs)}`);
});
test('reviewer.timeoutConfigKey: empty string is rejected (row 17)', () => {
const lane = laneOverride((l) => { l.timeoutConfigKey = ''; });
const errs = validateReviewerBody({ id: 'x', reviewer: lane });
assert.ok(
errs.some((e) => e.includes('reviewer.timeoutConfigKey must be a dotted config key or null') && e.includes('(got: "")')),
`expected an empty-string-is-not-none error, got: ${JSON.stringify(errs)}`,
);
});
test('reviewer.timeoutConfigKey: non-string, non-null is rejected (row 18)', () => {
for (const bad of [42, {}, []]) {
const lane = laneOverride((l) => { l.timeoutConfigKey = bad; });
const errs = validateReviewerBody({ id: 'x', reviewer: lane });
assert.ok(
errs.some((e) => e.includes('reviewer.timeoutConfigKey must be a dotted config key or null')),
`timeoutConfigKey=${JSON.stringify(bad)}: expected a type-rejection error, got: ${JSON.stringify(errs)}`,
);
}
});
});
// ─── G. Uniqueness (D8) — validateCrossCapability(Map, Set) ────────────────
describe('G. Uniqueness (D8) — validateCrossCapability(Map, Set)', () => {
// Each lane defaults to a slug/flags/reviewsSection derived from its own id,
// so distinct caps never accidentally collide — a test forces exactly ONE
// axis to collide via `reviewerOverrides`, isolating what it claims to test.
function laneCap(id, reviewerOverrides) {
return {
id,
role: 'reviewer',
reviewer: Object.assign(
validLane(),
{ slug: `${id}-slug`, flags: [`--${id}`], reviewsSection: `Section for ${id}` },
reviewerOverrides || {},
),
};
}
test('duplicateSlugAcrossCapabilitiesIsRejected', () => {
const capA = laneCap('cap-a');
const capB = laneCap('cap-b', { slug: capA.reviewer.slug });
const errs = validateCrossCapability(new Map([['cap-a', capA], ['cap-b', capB]]), new Set());
assert.ok(
errs.some((e) => e.includes(`reviewer slug "${capA.reviewer.slug}" is declared by "cap-a" and "cap-b"`)),
`expected a slug-collision error naming both ids, got: ${JSON.stringify(errs)}`,
);
});
// Regression guard for a real order-dependence defect in the first cut of the
// lane-uniqueness check. That version reported a collision when the SECOND
// claimant arrived, so with three lanes on one key it named whichever pair
// happened to arrive first: forward order blamed {cap-0,cap-1}+{cap-0,cap-2},
// reversed blamed {cap-1,cap-2}+{cap-0,cap-2}. Map order is readdir order at
// build time and candidate order at load time, so a cross-platform CI lane
// could disagree with a local run about the text of the same failure. The
// pairwise case (G7) is order-independent either way, which is exactly why it
// did not catch this. Claims are now accumulated and reported after the sweep:
// ONE error per colliding key naming EVERY claimant, ids sorted.
test('threeWayCollisionIsReportedOnceAndOrderIndependently', () => {
const ids = ['cap-0', 'cap-1', 'cap-2'];
const caps = ids.map((id) => laneCap(id, { slug: 'shared-slug' }));
const laneErrs = (entries) => validateCrossCapability(new Map(entries), new Set())
.filter((e) => e.startsWith('reviewer slug '));
const forward = laneErrs(ids.map((id, i) => [id, caps[i]]));
const reversed = laneErrs(ids.map((id, i) => [id, caps[i]]).reverse());
assert.equal(
forward.length, 1,
`a 3-way collision is ONE error naming all three, got: ${JSON.stringify(forward)}`,
);
assert.deepEqual(
forward, reversed,
`error text must not depend on Map insertion order, got forward=${JSON.stringify(forward)} reversed=${JSON.stringify(reversed)}`,
);
for (const id of ids) {
assert.ok(forward[0].includes(`"${id}"`), `every claimant must be named; ${id} missing from: ${forward[0]}`);
}
});
test('overlappingFlagsAcrossCapabilitiesAreRejected', () => {
const capA = laneCap('cap-a');
const capB = laneCap('cap-b', { flags: [...capA.reviewer.flags] });
const errs = validateCrossCapability(new Map([['cap-a', capA], ['cap-b', capB]]), new Set());
assert.ok(
errs.some((e) => e.includes(`reviewer flag "${capA.reviewer.flags[0]}" is declared by "cap-a" and "cap-b"`)),
`expected a flag-collision error (flattened across arrays), got: ${JSON.stringify(errs)}`,
);
});
test('duplicateReviewsSectionIsRejected', () => {
const capA = laneCap('cap-a');
const capB = laneCap('cap-b', { reviewsSection: capA.reviewer.reviewsSection });
const errs = validateCrossCapability(new Map([['cap-a', capA], ['cap-b', capB]]), new Set());
assert.ok(
errs.some((e) => e.includes(`reviewer reviewsSection "${capA.reviewer.reviewsSection}" is declared by "cap-a" and "cap-b"`)),
`expected a reviewsSection-collision error, got: ${JSON.stringify(errs)}`,
);
});
test('reviewsSectionUniquenessIsCaseSensitiveByDesign', () => {
const capA = laneCap('cap-a');
const capB = laneCap('cap-b', { reviewsSection: capA.reviewer.reviewsSection.toUpperCase() });
const errs = validateCrossCapability(new Map([['cap-a', capA], ['cap-b', capB]]), new Set());
assert.deepEqual(
errs, [],
`case-differing reviewsSection must NOT collide (Rejected #4, Known Limit), got: ${JSON.stringify(errs)}`,
);
});
test('singleLaneHasNoUniquenessError', () => {
const capA = laneCap('cap-a');
const errs = validateCrossCapability(new Map([['cap-a', capA]]), new Set());
assert.deepEqual(errs, [], `expected no errors, got: ${JSON.stringify(errs)}`);
});
test('capabilitiesWithoutLanesContributeNoUniquenessErrors', () => {
const capA = { id: 'cap-a', role: 'runtime' };
const capB = { id: 'cap-b', role: 'runtime' };
const errs = validateCrossCapability(new Map([['cap-a', capA], ['cap-b', capB]]), new Set());
assert.deepEqual(errs, [], `expected no errors, got: ${JSON.stringify(errs)}`);
});
test('nonLaneCapabilityIdMayMatchALaneSlug', () => {
const capA = laneCap('cap-a', { slug: 'shared-name' });
const capB = { id: 'shared-name', role: 'runtime' };
const errs = validateCrossCapability(new Map([['cap-a', capA], ['shared-name', capB]]), new Set());
assert.deepEqual(
errs, [],
`a capability id matching a lane slug string is not a collision (instances/ids are not lanes), got: ${JSON.stringify(errs)}`,
);
});
test('uniquenessErrorsAreOrderIndependent', () => {
const capA = laneCap('cap-a');
const capB = laneCap('cap-b', { slug: capA.reviewer.slug });
const forward = new Map([['cap-a', capA], ['cap-b', capB]]);
const reversed = new Map([['cap-b', capB], ['cap-a', capA]]);
const errsForward = validateCrossCapability(forward, new Set());
const errsReversed = validateCrossCapability(reversed, new Set());
assert.ok(errsForward.length > 0, `expected a real collision to exercise, got: ${JSON.stringify(errsForward)}`);
assert.deepEqual(
errsForward, errsReversed,
`error set must not depend on Map insertion order: forward=${JSON.stringify(errsForward)} reversed=${JSON.stringify(errsReversed)}`,
);
});
test('threeLanesWithOneCollisionReportOnce', () => {
const capA = laneCap('cap-a');
const capB = laneCap('cap-b', { slug: capA.reviewer.slug });
const capC = laneCap('cap-c');
const errs = validateCrossCapability(
new Map([['cap-a', capA], ['cap-b', capB], ['cap-c', capC]]),
new Set(),
);
const slugErrors = errs.filter((e) => e.startsWith('reviewer slug'));
assert.equal(slugErrors.length, 1, `expected exactly one collision error, not two, got: ${JSON.stringify(errs)}`);
assert.equal(errs.length, 1, `expected no unrelated errors from the non-colliding third lane, got: ${JSON.stringify(errs)}`);
});
});
// ─── H. Harvest widening — buildRegistry(capMap) ───────────────────────────
describe('H. Harvest widening — buildRegistry(capMap)', () => {
test('runtimeCapConfigIsHarvestedNotDropped', () => {
const capMap = new Map([['my-runtime', {
id: 'my-runtime',
role: 'runtime',
config: { 'myRuntime.enabled': { type: 'boolean', default: false, description: 'Enable my runtime feature.' } },
}]]);
const registry = buildRegistry(capMap);
assert.equal(registry.configKeys['myRuntime.enabled'], 'my-runtime');
assert.deepEqual(registry.configSchema['myRuntime.enabled'], {
owner: 'my-runtime',
type: 'boolean',
default: false,
description: 'Enable my runtime feature.',
});
});
test('reviewerCapConfigIsHarvested', () => {
const capMap = new Map([['my-reviewer', {
id: 'my-reviewer',
role: 'reviewer',
config: { 'myReviewer.enabled': { type: 'boolean', default: true, description: 'Enable my reviewer lane.' } },
}]]);
const registry = buildRegistry(capMap);
assert.equal(registry.configKeys['myReviewer.enabled'], 'my-reviewer');
assert.deepEqual(registry.configSchema['myReviewer.enabled'], {
owner: 'my-reviewer',
type: 'boolean',
default: true,
description: 'Enable my reviewer lane.',
});
});
test('runtimeCapWithoutConfigIsUnchanged', () => {
const cap = { id: 'my-runtime', role: 'runtime' };
const capMap = new Map([['my-runtime', cap]]);
const registry = buildRegistry(capMap);
assert.equal(registry.runtimes['my-runtime'], cap);
// registry.configKeys is Object.create(null) (S2b prototype-pollution guard),
// so comparing against a plain `{}` literal would fail on prototype alone —
// compare via Object.keys() instead of asserting shape equality.
assert.deepEqual(Object.keys(registry.configKeys), []);
});
test('reviewerCapIsNotStoredAsRuntime', () => {
const cap = { id: 'my-reviewer', role: 'reviewer' };
const capMap = new Map([['my-reviewer', cap]]);
const registry = buildRegistry(capMap);
assert.equal(registry.capabilities['my-reviewer'], cap);
assert.equal(registry.runtimes['my-reviewer'], undefined);
});
// Lives in validateCrossCapability's config-key ownership loop, not
// buildRegistry — that loop is the OTHER half of the harvest widening
// (40-design.md: "the role filter was `role !== 'feature'`"), so this row
// and H6 exercise validateCrossCapability directly rather than the harvest.
test('nonFeatureConfigKeyCollidingWithCentralSchemaIsFlagged', () => {
const capMap = new Map([['my-runtime', {
id: 'my-runtime',
role: 'runtime',
config: { 'shared.key': { type: 'string', default: 'x', description: 'y' } },
}]]);
const errs = validateCrossCapability(capMap, new Set(['shared.key']));
assert.ok(
errs.some((e) => e.includes('shared.key') && e.includes('central config-schema')),
`expected a central-schema collision error, got: ${JSON.stringify(errs)}`,
);
});
test('configKeyOwnedByTwoRolesIsRejected', () => {
const capMap = new Map([
['cap-a', { id: 'cap-a', role: 'runtime', config: { 'dup.key': { type: 'string', default: 'x', description: 'y' } } }],
['cap-b', { id: 'cap-b', role: 'reviewer', config: { 'dup.key': { type: 'string', default: 'z', description: 'w' } } }],
]);
const errs = validateCrossCapability(capMap, new Set());
assert.ok(
errs.some((e) => e.includes('dup.key') && e.includes('owned by both "cap-a" and "cap-b"')),
`expected a two-role ownership-collision error, got: ${JSON.stringify(errs)}`,
);
});
test('shippedRegistryConfigKeysAreOwnedByDeclaringCapabilities', () => {
// Phase 2 introduced the harvest widening (ownership follows DECLARING a
// config slice, not being a feature) and asserted it was INERT — no shipped
// non-feature capability owned a config key, so the widening changed nothing.
//
// #2797 (Phase 4) is what consumes it: the reviewer lanes now own their own
// config keys, and those lanes are `role: "reviewer"` (lane-only CLIs) or
// `role: "runtime"` (dual-purpose install targets). Asserting `role ===
// 'feature'` here would now be asserting the migration did not happen.
//
// The protection is kept, just restated: every key's owner must exist, and a
// `review.*` key must be owned by a capability that actually declares a
// reviewer lane — not by an arbitrary capability that happened to claim it.
const { capMap, errors } = loadAndValidate(new Set());
assert.deepEqual(errors, [], `expected the real shipped capability set to validate cleanly, got: ${JSON.stringify(errors)}`);
const registry = buildRegistry(capMap);
let reviewKeys = 0;
for (const [key, ownerId] of Object.entries(registry.configKeys)) {
const owner = capMap.get(ownerId);
assert.ok(owner, `configKeys owner "${ownerId}" for key "${key}" must exist in capMap`);
assert.ok(
['feature', 'reviewer', 'runtime'].includes(owner.role),
`config key "${key}" is owned by capability "${ownerId}" with unexpected role "${owner.role}"`,
);
if (key.startsWith('review.')) {
reviewKeys += 1;
assert.ok(
owner.reviewer && typeof owner.reviewer.slug === 'string',
`review config key "${key}" must be owned by a capability declaring a reviewer lane, ` +
`but "${ownerId}" declares none`,
);
}
}
assert.ok(reviewKeys > 0, 'expected the federated reviewer config keys to be present (#2797)');
});
});
// ─── I. Integration — loadAndValidate(centralKeys, tmpCapDir) ──────────────
describe('I. Integration — loadAndValidate(centralKeys, tmpCapDir)', () => {
function withCapDir(foldersToContent, fn) {
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-reviewer-body-'));
try {
for (const [folder, contentOrCap] of Object.entries(foldersToContent)) {
const dir = path.join(tmp, folder);
fs.mkdirSync(dir, { recursive: true });
const content = typeof contentOrCap === 'string' ? contentOrCap : `${JSON.stringify(contentOrCap, null, 2)}\n`;
fs.writeFileSync(path.join(dir, 'capability.json'), content, 'utf8');
}
return fn(tmp);
} finally {
cleanup(tmp);
}
}
test('reviewerCapabilityLoadsFromDisk', () => {
const cap = capWith({
id: 'lm-studio',
reviewer: Object.assign(validLane(), { slug: 'lm-studio', flags: ['--lm-studio'] }),
});
withCapDir({ 'lm-studio': cap }, (tmp) => {
const { capMap, errors } = loadAndValidate(new Set(), tmp);
assert.deepEqual(errors, [], `expected no errors, got: ${JSON.stringify(errors)}`);
assert.ok(capMap.has('lm-studio'));
});
});
test('malformedReviewerBodyReportsFolderId', () => {
const cap = capWith({ id: 'lm-studio', reviewer: {} });
withCapDir({ 'lm-studio': cap }, (tmp) => {
const { errors } = loadAndValidate(new Set(), tmp);
assert.ok(
errors.some((e) => e.startsWith('lm-studio/capability.json:') && e.includes('reviewer.slug')),
`expected the folder id to prefix the reported error, got: ${JSON.stringify(errors)}`,
);
});
});
test('emptyCapabilityFileIsReportedNotCrashed', () => {
withCapDir({ 'lm-studio': '' }, (tmp) => {
let errors;
assert.doesNotThrow(() => { ({ errors } = loadAndValidate(new Set(), tmp)); });
assert.ok(
errors.some((e) => e.includes('lm-studio/capability.json') && e.includes('JSON parse error')),
`expected a JSON-parse error naming the folder, got: ${JSON.stringify(errors)}`,
);
});
});
test('crlfCapabilityFileParsesIdentically', () => {
const cap = capWith({
id: 'lm-studio',
reviewer: Object.assign(validLane(), { slug: 'lm-studio', flags: ['--lm-studio'] }),
});
const lfContent = `${JSON.stringify(cap, null, 2)}\n`;
const crlfContent = lfContent.replace(/\n/g, '\r\n');
withCapDir({ 'lm-studio': lfContent }, (tmpLf) => {
withCapDir({ 'lm-studio': crlfContent }, (tmpCrlf) => {
const lfResult = loadAndValidate(new Set(), tmpLf);
const crlfResult = loadAndValidate(new Set(), tmpCrlf);
assert.deepEqual(lfResult.errors, [], `LF variant should validate cleanly, got: ${JSON.stringify(lfResult.errors)}`);
assert.deepEqual(crlfResult.errors, [], `CRLF variant should validate cleanly, got: ${JSON.stringify(crlfResult.errors)}`);
assert.deepEqual(
crlfResult.capMap.get('lm-studio'),
lfResult.capMap.get('lm-studio'),
'a CRLF-authored manifest must parse identically to its LF counterpart',
);
});
});
});
test('unreadableCapabilityFileIsReportedNotCrashed', () => {
const cap = capWith({
id: 'lm-studio',
reviewer: Object.assign(validLane(), { slug: 'lm-studio', flags: ['--lm-studio'] }),
});
withCapDir({ 'lm-studio': cap }, (tmp) => {
const capPath = path.join(tmp, 'lm-studio', 'capability.json');
// House rule: inject a deterministic IO failure by monkeypatching the fs
// method (save the original, override it to throw, restore in `finally`).
// NEVER `chmod 0o000` — root bypasses mode bits, so that trick silently
// passes with zero coverage in root Docker/CI. The override is scoped to
// the exact capability.json path under test and delegates every other
// read (e.g. loadAndValidate's own wired-loop-point scan) to the real
// implementation, so it fails only the read this test cares about.
const originalReadFileSync = fs.readFileSync;
let result;
try {
fs.readFileSync = (p, ...rest) => {
if (p === capPath) {
throw new Error('injected unreadable-file failure (simulated EACCES)');
}
return originalReadFileSync.call(fs, p, ...rest);
};
assert.doesNotThrow(() => { result = loadAndValidate(new Set(), tmp); });
} finally {
fs.readFileSync = originalReadFileSync;
}
assert.ok(
result.errors.some((e) => e.includes('lm-studio') && e.includes('injected unreadable-file failure')),
`expected the unreadable file to be reported, not crashed, got: ${JSON.stringify(result.errors)}`,
);
});
});
test('snakeCaseIdIsRejectedEvenWhenSlugIsSnakeCase', () => {
// The Phase 5a trap: folder is kebab ("lm-studio") and reviewer.slug is
// snake ("lm_studio", accepted per F2) — but `id` itself must ALSO be kebab.
const badCap = capWith({
id: 'lm_studio',
reviewer: Object.assign(validLane(), { slug: 'lm_studio', flags: ['--lm-studio'] }),
});
withCapDir({ 'lm-studio': badCap }, (tmp) => {
const { errors } = loadAndValidate(new Set(), tmp);
assert.ok(
errors.some((e) => e.includes('lm-studio/capability.json') && e.includes('kebab-case')),
`expected a snake_case id to be rejected, got: ${JSON.stringify(errors)}`,
);
});
const goodCap = capWith({
id: 'lm-studio',
reviewer: Object.assign(validLane(), { slug: 'lm_studio', flags: ['--lm-studio'] }),
});
withCapDir({ 'lm-studio': goodCap }, (tmp) => {
const { capMap, errors } = loadAndValidate(new Set(), tmp);
assert.deepEqual(
errors, [],
`a kebab id with a snake_case reviewer.slug must be accepted, got: ${JSON.stringify(errors)}`,
);
assert.ok(capMap.has('lm-studio'));
});
});
// ADR-2782 D4.3, LOAD-TIME half. The build-time generator only ever sees
// first-party in-repo manifests, so an unknown field on an INSTALLED
// third-party lane — the case D4.3 exists for — surfaced nowhere at runtime
// until the loader was wired to collect diagnostics. A global-scope overlay is
// used because project scope additionally requires a consent record; the
// subject here is the diagnostic channel, not the consent gate.
test('overlayLaneWithUnknownFieldIsAcceptedAndDiagnosed', () => {
const home = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-reviewer-overlay-'));
try {
const capDir = path.join(home, '.msd', 'capabilities', 'acme-reviewer');
fs.mkdirSync(capDir, { recursive: true });
fs.writeFileSync(path.join(capDir, 'capability.json'), JSON.stringify({
id: 'acme-reviewer',
role: 'reviewer',
version: '1.0.0',
title: 'Acme',
description: 'Third-party reviewer lane.',
tier: 'full',
requires: [],
engines: { msd: '>=1.0.0' },
reviewer: Object.assign(validLane(), {
slug: 'acme',
flags: ['--acme'],
reviewsSection: 'Acme',
// The field a NEWER MSD would understand and this one does not.
futureFieldFromNewerMsd: true,
}),
}));
const { loadRegistry } = require('../msd-core/bin/lib/capability-loader.cjs');
const registry = loadRegistry({
includeInstalled: true, cwd: process.cwd(), msdHome: home, hostVersion: '1.8.0',
});
// Accepted, NOT skipped — an unknown field must never cost the user the lane.
assert.ok(
registry.capabilities && registry.capabilities['acme-reviewer'],
'a third-party lane with an unknown reviewer field must still be accepted',
);
const overlay = registry._overlay;
assert.ok(overlay, 'overlay meta must be attached when an overlay is composed');
assert.deepEqual(
overlay.warnings, [],
`an unknown field is a diagnostic, not a skip, got: ${JSON.stringify(overlay.warnings)}`,
);
assert.ok(
overlay.diagnostics.some((d) => d.includes('futureFieldFromNewerMsd')),
`expected a diagnostic naming the unknown field, got: ${JSON.stringify(overlay.diagnostics)}`,
);
} finally {
cleanup(home);
}
});
});
// ─── J. Property-based (fast-check) ────────────────────────────────────────
describe('J. Property-based (fast-check)', () => {
// The first version of this property used a bare `fc.anything()` as the whole
// reviewer value, and it was FALSE CONFIDENCE: at default constraints
// fc.anything() emits no BigInt, no circular reference, no getter and no
// Proxy — 20,000 sampled draws produced zero of each — which is precisely the
// value space that broke the contract. Worse, even enabling withBigInt is not
// enough under whole-value fuzzing, because the bug needs an exotic value in a
// SPECIFICALLY NAMED field and random key names essentially never land on one.
// So the generator is field-targeted, and the value space is widened by hand
// to include the shapes JSON cannot express but a JS caller can still pass.
const assertTotal = (cap, label) => {
let result;
try {
result = validateReviewerBody(cap);
} catch (err) {
assert.fail(`validateReviewerBody threw for ${label}: ${err && err.message}`);
}
assert.ok(Array.isArray(result), `must return an array for ${label}`);
assert.ok(result.every((e) => typeof e === 'string'), `every entry must be a string for ${label}`);
};
test('validateReviewerBodyNeverThrowsOnArbitraryInput', () => {
// Whole-value fuzzing — the original property, kept as the broad sweep.
fc.assert(
fc.property(fc.anything({ withBigInt: true }), (reviewerValue) => {
assertTotal({ id: 'prop-test', reviewer: reviewerValue }, 'whole-body fuzz');
return true;
}),
{ numRuns: 500 },
);
// Field-targeted fuzzing — this is the variant that actually falsifies the
// pre-fix implementation, on roughly the first generated case.
const TARGET_FIELDS = [
'slug', 'flags', 'transport', 'probe', 'invoke', 'timeoutFloorMs',
'emptyOutput', 'reviewsSection', 'evidenceClass', 'requiresBinaries',
'promptBudgetKey', 'handler',
];
fc.assert(
fc.property(
fc.constantFrom(...TARGET_FIELDS),
fc.anything({ withBigInt: true }),
(field, value) => {
const lane = laneOverride((l) => { l[field] = value; });
assertTotal({ id: 'prop-test', reviewer: lane }, `${field} = ${String(field)}`);
return true;
},
),
{ numRuns: 1000 },
);
});
// The shapes fast-check cannot generate but a JS caller can still hand us.
// These are enumerated rather than fuzzed because a generator that produced
// them would be reimplementing the enumeration anyway.
test('validateReviewerBodyNeverThrowsOnValuesJsonCannotExpress', () => {
const circular = () => { const o = {}; o.self = o; return o; };
const throwingGetter = () => {
const o = {};
Object.defineProperty(o, 'valueOf', { get() { throw new Error('boom'); } });
Object.defineProperty(o, 'toJSON', { get() { throw new Error('boom'); } });
return o;
};
const hostile = [
['bigint', 10n],
['circular', circular()],
['throwing-getter', throwingGetter()],
['symbol', Symbol('s')],
['function', () => {}],
['null-prototype', Object.create(null)],
];
for (const field of ['slug', 'transport', 'timeoutFloorMs', 'handler', 'promptBudgetKey', 'probe', 'invoke']) {
for (const [name, value] of hostile) {
const lane = laneOverride((l) => { l[field] = value; });
assertTotal({ id: 'prop-test', reviewer: lane }, `${field} = ${name}`);
}
}
// Array-element positions, which take a different code path from scalars.
for (const field of ['flags', 'requiresBinaries']) {
for (const [name, value] of hostile) {
const lane = laneOverride((l) => { l[field] = [value]; });
assertTotal({ id: 'prop-test', reviewer: lane }, `${field}[0] = ${name}`);
}
}
const argsLane = laneOverride((l) => { l.invoke.args = [circular()]; });
assertTotal({ id: 'prop-test', reviewer: argsLane }, 'invoke.args[0] = circular');
// Read-time throws: a getter or Proxy trap fires BEFORE any message is built,
// so describeValue() alone cannot save these — only the structural wrapper can.
assertTotal(
Object.defineProperty({ reviewer: {} }, 'id', { get() { throw new Error('boom'); } }),
'throwing getter on cap.id',
);
const slugThrows = laneOverride(() => {});
Object.defineProperty(slugThrows, 'slug', { get() { throw new Error('boom'); } });
assertTotal({ id: 'x', reviewer: slugThrows }, 'throwing getter on reviewer.slug');
assertTotal({ id: 'x', reviewer: new Proxy({}, { get() { throw new Error('boom'); } }) }, 'Proxy get trap');
assertTotal({ id: 'x', reviewer: new Proxy({}, { ownKeys() { throw new Error('boom'); } }) }, 'Proxy ownKeys trap');
// collectReviewerWarnings carries the same contract — it runs on
// loadRegistry's ACCEPT path, so a throwing diagnostic would cost a user a
// lane that is otherwise perfectly valid.
for (const cap of [
Object.defineProperty({ reviewer: {} }, 'id', { get() { throw new Error('boom'); } }),
{ id: 'x', reviewer: new Proxy({}, { ownKeys() { throw new Error('boom'); } }) },
]) {
let warnings;
try {
warnings = collectReviewerWarnings(cap);
} catch (err) {
assert.fail(`collectReviewerWarnings threw: ${err && err.message}`);
}
assert.ok(Array.isArray(warnings), 'collectReviewerWarnings must always return an array');
}
});
test('uniquenessIsInvariantUnderMapOrder', () => {
// Groups of ARBITRARY size (1..4 capabilities sharing one slug), so N-way
// collisions are exercised, not just pairwise ones. An earlier cut of the
// uniqueness check reported a collision the moment a second claimant arrived,
// which named a different pair depending on insertion order once three lanes
// shared a key; claims are now accumulated and reported after the sweep, so
// the guarantee holds for any N. Errors are compared UNSORTED and filtered to
// the lane errors, so this asserts deterministic emission ORDER too — sorting
// both sides first would hide exactly the defect this guards.
fc.assert(
fc.property(
fc.array(fc.integer({ min: 1, max: 4 }), { minLength: 1, maxLength: 4 }),
fc.array(fc.integer({ min: 0, max: 999 }), { minLength: 0, maxLength: 8 }),
(groupSizes, shuffleSeed) => {
const entries = [];
groupSizes.forEach((size, groupIdx) => {
for (let member = 0; member < size; member += 1) {
const id = `cap-${groupIdx}-${member}`;
entries.push([id, {
id,
role: 'reviewer',
// Every member of a group shares one slug; flags and sections stay
// distinct so the slug is the only colliding dimension.
reviewer: Object.assign(validLane(), {
slug: `group-slug-${groupIdx}`,
flags: [`--${id}`],
reviewsSection: `Section ${id}`,
}),
}]);
}
});
const laneErrors = (ordered) => validateCrossCapability(new Map(ordered), new Set())
.filter((e) => e.startsWith('reviewer '));
// A deterministic permutation derived from the generated seed, so the
// property covers arbitrary orderings rather than only forward/reverse.
const shuffled = [...entries];
shuffleSeed.forEach((n, i) => {
const j = n % shuffled.length;
const k = (i + n) % shuffled.length;
[shuffled[j], shuffled[k]] = [shuffled[k], shuffled[j]];
});
const forward = laneErrors(entries);
const reversed = laneErrors([...entries].reverse());
const permuted = laneErrors(shuffled);
assert.deepEqual(reversed, forward, 'lane errors must not depend on Map insertion order (reversed)');
assert.deepEqual(permuted, forward, 'lane errors must not depend on Map insertion order (permuted)');
// One error per colliding group, naming every claimant.
const expectedCollisions = groupSizes.filter((s) => s > 1).length;
assert.equal(
forward.length, expectedCollisions,
`expected one error per colliding group, got: ${JSON.stringify(forward)}`,
);
return true;
},
),
{ numRuns: 200 },
);
});
test('absentReviewerBodyNeverContributesErrors', () => {
fc.assert(
fc.property(
fc.dictionary(fc.string(), fc.anything()),
(dict) => {
delete dict.reviewer; // guarantee absence regardless of low-probability random key collision
const capUndefinedKey = { ...dict, reviewer: undefined };
const capDeletedKey = { ...dict };
const errsUndefinedKey = validateReviewerBody(capUndefinedKey);
const errsDeletedKey = validateReviewerBody(capDeletedKey);
assert.deepEqual(errsUndefinedKey, errsDeletedKey, 'an explicit undefined must behave identically to an absent key');
assert.deepEqual(errsUndefinedKey, [], 'D4.1 — absence must never be an error');
return true;
},
),
{ numRuns: 200 },
);
});
});
// ─── K. Removed `hostBehaviors.reviewerCli` alias (ADR-2782 D9, chore #2801) ──
//
// Phase 7 deletes the derived legacy alias. `collectReviewerWarnings` is the
// channel the removal announces itself on, because it is already wired to BOTH
// surfaces a manifest can arrive through: the build-time generator
// (`gen-capability-registry.cjs` -> stderr) and the overlay loader
// (`capability-loader.cts` -> OverlayMeta.warnings, on the ACCEPT path for every
// accepted capability). An out-of-tree manifest still setting the alias reaches
// the second one.
//
// Rows K1-K9 implement W1-W9 of
// `.msd/phase/chore-2801-remove-reviewercli-alias/50-test-matrix.md`.
//
// The load-bearing structural fact these rows pin down: the removal check must
// run BEFORE `collectReviewerWarningRecordFields`' `reviewer`-body early-return. An
// alias-only manifest — precisely the case the deprecation window existed for —
// has no `reviewer` body, so a check placed after that guard would fire only for
// capabilities that do not need it. K1 is the row that fails if it is misplaced.
/** A whole runtime manifest — the shape production passes to this function. */
function runtimeCapWithHostBehaviors(hostBehaviors, extra = {}) {
return {
id: 'legacy-cli',
role: 'runtime',
runtime: { hostBehaviors },
...extra,
};
}
describe('K. Removed hostBehaviors.reviewerCli alias (#2801)', () => {
/** Records for the removal notice only, keyed on the typed code. */
function removalRecords(cap) {
return collectReviewerWarningRecords(cap)
.filter((rec) => rec.code === REVIEWER_WARNING.REMOVED_HOST_BEHAVIOR);
}
test('reviewerWarningCodeSurfaceIsLocked', () => {
// The third of the three coordinated changes a new code requires. Without
// this, a code can be added or renamed with no test noticing.
assert.deepEqual(
Object.keys(REVIEWER_WARNING).sort(),
['REMOVED_HOST_BEHAVIOR', 'UNKNOWN_HOST_BEHAVIOR', 'UNKNOWN_REVIEWER_FIELD'],
);
assert.equal(Object.isFrozen(REVIEWER_WARNING), true, 'the code enum must be frozen');
assert.equal(REMOVED_REVIEWER_CLI_FIELD, 'runtime.hostBehaviors.reviewerCli');
});
test('removedReviewerCliAliasWarnsWhenPresentWithoutABody', () => {
// No `reviewer` body at all — the alias-only manifest. This is the row that
// proves the check runs before the body early-return.
const cap = runtimeCapWithHostBehaviors({ reviewerCli: true });
const records = removalRecords(cap);
assert.equal(
records.length, 1,
`expected exactly one removal record for an alias-only manifest, got: ${JSON.stringify(collectReviewerWarningRecords(cap))}`,
);
assert.equal(records[0].capId, 'legacy-cli');
assert.equal(records[0].field, REMOVED_REVIEWER_CLI_FIELD);
});
test('removedReviewerCliAliasWarnsAlongsideADeclaredBody', () => {
const cap = runtimeCapWithHostBehaviors({ reviewerCli: true }, { reviewer: validLane() });
assert.equal(removalRecords(cap).length, 1, 'a declared body must not suppress the removal notice');
assert.deepEqual(
validateReviewerBody(cap), [],
'the vestigial key must stay a WARNING — never a validation error (Postel: liberal in what we accept)',
);
});
test('removedReviewerCliAliasWarnsRegardlessOfItsValue', () => {
// Presence-based, deliberately: after removal the key is unknown at ANY
// value, exactly as an unknown `reviewer.*` field is. A value-sensitive
// warning would tell an author carrying `reviewerCli: false` that their
// stale key is fine, when it is simply dead.
// (40-design.md -> Rejected 3.)
for (const value of [true, false, 'true', 0, 1, null, {}, []]) {
const cap = runtimeCapWithHostBehaviors({ reviewerCli: value });
assert.equal(
removalRecords(cap).length, 1,
`expected a removal record for reviewerCli = ${JSON.stringify(value)}, got: ${JSON.stringify(collectReviewerWarningRecords(cap))}`,
);
}
});
test('similarlyNamedHostBehaviorKeysAreNotTheRemovedField', () => {
// Exact own-key match only: a near-miss name must never be reported as the
// removed `reviewerCli`. Since #2801 closed the vocabulary these names DO
// now draw an unknown-host-behavior notice, which is correct — they are not
// declared behaviors — but they must not draw the removal notice.
const cap = runtimeCapWithHostBehaviors({
reviewerCliPath: '/usr/bin/thing',
reviewer_cli: true,
reviewerCLI: true,
reapplyCommand: 'x',
});
const records = collectReviewerWarningRecords(cap);
assert.deepEqual(
records.filter((rec) => rec.code === REVIEWER_WARNING.REMOVED_HOST_BEHAVIOR), [],
'only the exact own key `reviewerCli` is the removed field',
);
assert.deepEqual(
records.map((rec) => rec.field).sort(),
[
'runtime.hostBehaviors.reviewerCLI',
'runtime.hostBehaviors.reviewer_cli',
'runtime.hostBehaviors.reviewerCliPath',
].sort(),
'the three undeclared names draw an unknown-host-behavior notice; the declared reapplyCommand does not',
);
});
test('malformedHostBehaviorsNeitherWarnsNorThrows', () => {
const shapes = [
['null', { id: 'c', role: 'runtime', runtime: { hostBehaviors: null } }],
['array', { id: 'c', role: 'runtime', runtime: { hostBehaviors: [] } }],
['string', { id: 'c', role: 'runtime', runtime: { hostBehaviors: 'reviewerCli' } }],
['number', { id: 'c', role: 'runtime', runtime: { hostBehaviors: 42 } }],
['empty object', { id: 'c', role: 'runtime', runtime: { hostBehaviors: {} } }],
['no hostBehaviors', { id: 'c', role: 'runtime', runtime: {} }],
['no runtime', { id: 'c', role: 'reviewer', reviewer: validLane() }],
['runtime null', { id: 'c', role: 'runtime', runtime: null }],
];
for (const [name, cap] of shapes) {
let records;
try {
records = collectReviewerWarningRecords(cap);
} catch (err) {
assert.fail(`collectReviewerWarningRecords threw for ${name}: ${err && err.message}`);
}
assert.deepEqual(
records.filter((rec) => rec.code === REVIEWER_WARNING.REMOVED_HOST_BEHAVIOR), [],
`${name} must not produce a removal record`,
);
}
});
test('removalWarningAndUnknownFieldWarningCoexist', () => {
// Two independent diagnostics on one manifest. Neither may swallow the other
// — an early `return` after the first would hide the second.
const lane = validLane();
lane.futureField = 'from-a-newer-msd';
const cap = runtimeCapWithHostBehaviors({ reviewerCli: true }, { id: 'both-cap', reviewer: lane });
const records = collectReviewerWarningRecords(cap);
assert.deepEqual(
records.map((rec) => rec.code).sort(),
[REVIEWER_WARNING.REMOVED_HOST_BEHAVIOR, REVIEWER_WARNING.UNKNOWN_REVIEWER_FIELD].sort(),
`expected exactly one of each code, got: ${JSON.stringify(records)}`,
);
});
test('inheritedReviewerCliFromPrototypeDoesNotWarn', () => {
// Own-key read: a polluted prototype must not manufacture a removal record
// on every otherwise-innocent manifest.
const polluted = Object.create({ reviewerCli: true });
polluted.reapplyCommand = 'x';
const cap = runtimeCapWithHostBehaviors(polluted);
assert.deepEqual(
collectReviewerWarningRecords(cap), [],
'an inherited reviewerCli is not a declared field',
);
});
test('removalWarningNamesTheReviewerBodyReplacement', () => {
// A removal notice that does not say what to do instead is not a migration
// path. The field was undocumented for its whole life and only documented at
// 1.9.0 as ALREADY deprecated, so we cannot enumerate who depends on it
// (Hyrum) — the exit has to carry its own instructions. Asserted on the
// typed fields, never on the rendered sentence.
const [record] = removalRecords(runtimeCapWithHostBehaviors({ reviewerCli: true }));
assert.ok(record, 'expected a removal record');
assert.equal(record.replacement, 'reviewer');
assert.equal(record.docs, 'docs/how-to/ship-a-reviewer-lane.md');
});
test('renderedStringsStayOneToOneWithRecords', () => {
// The two production consumers still receive strings; the renderer must not
// drop or duplicate a diagnostic.
const lane = validLane();
lane.futureField = 'x';
for (const cap of [
runtimeCapWithHostBehaviors({ reviewerCli: true }),
runtimeCapWithHostBehaviors({ reviewerCli: true }, { reviewer: lane }),
runtimeCapWithHostBehaviors({ reapplyCommand: 'x' }),
]) {
const records = collectReviewerWarningRecords(cap);
const strings = collectReviewerWarnings(cap);
assert.equal(strings.length, records.length);
assert.deepEqual(strings, records.map((rec) => rec.message));
}
});
test('collectReviewerWarningsStaysTotalOverTheNewHostBehaviorsReadPath', () => {
// W9 — the totality contract (#1461 OVL-1) now covers a second read path.
// A throwing getter or Proxy trap fires on the READ, before any message is
// built, so only the structural wrapper can save these. Both the IR and the
// renderer must survive, since the renderer maps over the IR.
const throwing = () => { throw new Error('boom'); };
const hostBehaviorsGetterThrows = { id: 'x', role: 'runtime', runtime: {} };
Object.defineProperty(hostBehaviorsGetterThrows.runtime, 'hostBehaviors', { get: throwing });
const reviewerCliGetterThrows = { id: 'x', role: 'runtime', runtime: { hostBehaviors: {} } };
Object.defineProperty(reviewerCliGetterThrows.runtime.hostBehaviors, 'reviewerCli', { get: throwing });
const cases = [
['runtime getter throws', Object.defineProperty({ id: 'x' }, 'runtime', { get: throwing })],
['hostBehaviors getter throws', hostBehaviorsGetterThrows],
['reviewerCli getter throws', reviewerCliGetterThrows],
['hostBehaviors Proxy traps throw', {
id: 'x',
role: 'runtime',
runtime: { hostBehaviors: new Proxy({}, { has: throwing, get: throwing, getOwnPropertyDescriptor: throwing, ownKeys: throwing }) },
}],
];
for (const [name, cap] of cases) {
let records;
let strings;
try {
records = collectReviewerWarningRecords(cap);
strings = collectReviewerWarnings(cap);
} catch (err) {
assert.fail(`${name}: threw ${err && err.message}`);
}
assert.ok(Array.isArray(records), `${name}: records must always be an array`);
assert.ok(Array.isArray(strings), `${name}: strings must always be an array`);
}
});
});
// ─── L. Closed `hostBehaviors` vocabulary (ADR-1016, closed by #2801) ────────
describe('L. Closed hostBehaviors vocabulary (#2801)', () => {
const ROOT = path.resolve(__dirname, '..');
/** Every hostBehaviors key the shipped manifests actually declare. */
function shippedHostBehaviorKeys() {
const keys = new Set();
const capsDir = path.join(ROOT, 'capabilities');
for (const dir of fs.readdirSync(capsDir, { withFileTypes: true })) {
if (!dir.isDirectory()) continue;
const file = path.join(capsDir, dir.name, 'capability.json');
if (!fs.existsSync(file)) continue;
const cap = JSON.parse(fs.readFileSync(file, 'utf8'));
const hb = cap && cap.runtime && cap.runtime.hostBehaviors;
if (hb && typeof hb === 'object' && !Array.isArray(hb)) {
for (const key of Object.keys(hb)) keys.add(key);
}
}
return keys;
}
test('vocabularyExactlyMatchesWhatTheShippedManifestsDeclare', () => {
// DEFECT.GENERATIVE-FIX: two surfaces, one truth. A key added to a manifest
// without being declared here would warn on every build; a key left here
// after its last manifest drops it is dead vocabulary. Both directions fail.
const shipped = shippedHostBehaviorKeys();
assert.deepEqual(
[...shipped].sort(), [...KNOWN_HOST_BEHAVIORS].sort(),
'the closed vocabulary and the shipped manifests must name the same keys',
);
});
test('noShippedCapabilityDrawsAHostBehaviorWarning', () => {
// The closure must be inert for everything that ships today. If this fails,
// closing the vocabulary broke a real capability rather than a hypothetical one.
const capsDir = path.join(ROOT, 'capabilities');
const offenders = [];
for (const dir of fs.readdirSync(capsDir, { withFileTypes: true })) {
if (!dir.isDirectory()) continue;
const file = path.join(capsDir, dir.name, 'capability.json');
if (!fs.existsSync(file)) continue;
const cap = JSON.parse(fs.readFileSync(file, 'utf8'));
for (const rec of collectReviewerWarningRecords(cap)) {
if (rec.code === REVIEWER_WARNING.UNKNOWN_HOST_BEHAVIOR
|| rec.code === REVIEWER_WARNING.REMOVED_HOST_BEHAVIOR) {
offenders.push(`${dir.name}: ${rec.field}`);
}
}
}
assert.deepEqual(offenders, [], `no shipped capability may draw a hostBehaviors notice, got: ${JSON.stringify(offenders)}`);
});
test('anUndeclaredHostBehaviorWarnsAndIsNotAnError', () => {
const cap = runtimeCapWithHostBehaviors({ someFutureSwitch: true });
const records = collectReviewerWarningRecords(cap);
assert.equal(records.length, 1, `expected one record, got: ${JSON.stringify(records)}`);
assert.equal(records[0].code, REVIEWER_WARNING.UNKNOWN_HOST_BEHAVIOR);
assert.equal(records[0].field, 'runtime.hostBehaviors.someFutureSwitch');
// Forward-compat invariant: a warning, never a validation error.
assert.deepEqual(validateCapability({ ...cap, version: '1.0.0' }, cap.id).filter((e) => e.includes('someFutureSwitch')), []);
});
test('aDeclaredHostBehaviorIsSilentAtAnyValue', () => {
for (const value of [true, false, 'x', 0, null, {}, []]) {
const cap = runtimeCapWithHostBehaviors({ reapplyCommand: value });
assert.deepEqual(
collectReviewerWarningRecords(cap), [],
`a declared key must be silent regardless of value, got value ${JSON.stringify(value)}`,
);
}
});
test('theRemovedAliasDrawsItsOwnNoticeNotTheGenericOne', () => {
// reviewerCli is excluded from the unknown-key sweep on purpose: it has a
// migration pointer the generic notice does not carry, and two records for
// one key would be noise.
const records = collectReviewerWarningRecords(runtimeCapWithHostBehaviors({ reviewerCli: true }));
assert.equal(records.length, 1, `expected exactly one record, got: ${JSON.stringify(records)}`);
assert.equal(records[0].code, REVIEWER_WARNING.REMOVED_HOST_BEHAVIOR);
assert.equal(records[0].replacement, 'reviewer');
});
test('reservedKeysInTheBagAreIgnoredNotWarned', () => {
const hostile = JSON.parse('{"__proto__": {"polluted": true}, "constructor": 1, "prototype": 2, "reapplyCommand": "x"}');
const cap = runtimeCapWithHostBehaviors(hostile);
let records;
try {
records = collectReviewerWarningRecords(cap);
} catch (err) {
assert.fail(`collectReviewerWarningRecords threw: ${err && err.message}`);
}
assert.deepEqual(records, [], 'reserved names are skipped, not reported as unknown behaviors');
assert.equal({}.polluted, undefined, 'Object.prototype must not be polluted');
});
});
// ─── M. Diagnostics are bounded and control-safe (#2801 review findings) ─────
//
// Both loops iterate MANIFEST-SUPPLIED keys. An installed third-party manifest
// is attacker-controlled and bounded only by MANIFEST_MAX_BYTES (8MB), so the
// record count and each key's rendered length must both have a ceiling, and a
// key must not be able to carry terminal escapes or a forged newline into
// stderr / OverlayMeta.warnings.
describe('M. Diagnostics are bounded and control-safe (#2801)', () => {
function manyUnknownHostBehaviors(n) {
const hb = {};
for (let i = 0; i < n; i += 1) hb['undeclaredKey' + i] = true;
return runtimeCapWithHostBehaviors(hb);
}
test('unknownHostBehaviorRecordsAreCappedWithASummary', () => {
const n = MAX_REPORTED_UNKNOWN_KEYS + 25;
const records = collectReviewerWarningRecords(manyUnknownHostBehaviors(n));
assert.equal(
records.length, MAX_REPORTED_UNKNOWN_KEYS + 1,
`expected ${MAX_REPORTED_UNKNOWN_KEYS} records plus one summary, got ${records.length}`,
);
const summary = records[records.length - 1];
assert.equal(summary.truncated, true);
assert.equal(summary.omittedCount, 25);
assert.equal(summary.field, 'runtime.hostBehaviors');
});
test('exactlyAtTheCapThereIsNoSummaryRecord', () => {
// limit-1 / limit / limit+1 around the ceiling.
const below = collectReviewerWarningRecords(manyUnknownHostBehaviors(MAX_REPORTED_UNKNOWN_KEYS - 1));
assert.equal(below.length, MAX_REPORTED_UNKNOWN_KEYS - 1);
assert.equal(below.some((rec) => rec.truncated), false);
const at = collectReviewerWarningRecords(manyUnknownHostBehaviors(MAX_REPORTED_UNKNOWN_KEYS));
assert.equal(at.length, MAX_REPORTED_UNKNOWN_KEYS);
assert.equal(at.some((rec) => rec.truncated), false, 'no summary when nothing was omitted');
const above = collectReviewerWarningRecords(manyUnknownHostBehaviors(MAX_REPORTED_UNKNOWN_KEYS + 1));
assert.equal(above.length, MAX_REPORTED_UNKNOWN_KEYS + 1);
assert.equal(above[above.length - 1].omittedCount, 1);
});
test('unknownReviewerFieldRecordsAreCappedTheSameWay', () => {
const lane = validLane();
for (let i = 0; i < MAX_REPORTED_UNKNOWN_KEYS + 5; i += 1) lane['futureField' + i] = 1;
const records = collectReviewerWarningRecords({ id: 'cap-x', reviewer: lane });
assert.equal(records.length, MAX_REPORTED_UNKNOWN_KEYS + 1);
assert.equal(records[records.length - 1].truncated, true);
assert.equal(records[records.length - 1].omittedCount, 5);
});
test('controlCharactersInAKeyNeverReachTheDiagnostic', () => {
// ESC-based colour sequence, a CR overwrite, and an embedded newline that
// would forge a second log line.
const hostile = '\x1b[31mred\x1b[0m\r\nforged: everything is fine';
for (const cap of [
runtimeCapWithHostBehaviors({ [hostile]: true }),
{ id: 'cap-x', reviewer: { slug: 'x', [hostile]: true } },
]) {
for (const rec of collectReviewerWarningRecords(cap)) {
// eslint-disable-next-line no-control-regex
assert.equal(/[\x00-\x1f\x7f-\x9f]/.test(rec.field), false, `control char survived into field: ${JSON.stringify(rec.field)}`);
// eslint-disable-next-line no-control-regex
assert.equal(/[\x00-\x1f\x7f-\x9f]/.test(rec.message), false, `control char survived into message: ${JSON.stringify(rec.message)}`);
}
}
});
test('anEnormousKeyNameIsClipped', () => {
const huge = 'k'.repeat(5000);
const [record] = collectReviewerWarningRecords(runtimeCapWithHostBehaviors({ [huge]: true }));
assert.ok(record, 'expected a record');
assert.ok(
record.field.length < MAX_REPORTED_KEY_CHARS + 40,
`field must be bounded, got length ${record.field.length}`,
);
assert.ok(record.field.endsWith('…'), 'a clipped key is marked as clipped');
});
test('aDeclaredKeyIsNeverClippedOrAltered', () => {
// The sanitizer must not perturb the ordinary case: declared keys are silent,
// and an undeclared but well-formed key is reported verbatim.
const records = collectReviewerWarningRecords(runtimeCapWithHostBehaviors({ someFutureSwitch: true }));
assert.equal(records.length, 1);
assert.equal(records[0].field, 'runtime.hostBehaviors.someFutureSwitch');
});
});