Files
msd-core/tests/registry-axes-parity.test.cjs
Jakub Zych a9a7a328e6 refactor: hard-fork GSD -> MSD (Make Software Done)
Mechanical rename produced by scripts/msd-rename.cjs: gsd/Gsd/GSD -> msd/Msd/MSD
across contents and paths, upstream package/repo coordinates -> @golem15/msd-core
and golem15com/msd-core. Deep links into upstream history, sibling upstream
packages, the GSD-2 import feature, CHANGELOG.md and .changeset/ are kept as-is.

Hand edits on top: MSD block-letter banner and logos, LICENSE copyright line,
package/plugin identity, regenerated lockfile, install-tree fixtures, derived
registries and benchmark baseline; migration checksum baseline re-locked
(MSD keeps its own install state, so no install had applied the old sums);
sort-order and regex-escaped expectations in tests adjusted.
2026-10-06 01:47:40 +02:00

222 lines
10 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.
'use strict';
process.env.MSD_TEST_MODE = '1';
/**
* tests/registry-axes-parity.test.cjs — regression coverage for issue #2810.
*
* `scripts/registry-schema.cjs`'s `AXES` (the eight ADR-1239 negotiated axes
* mirrored into the EoS Registry schema) and `src/host-integration.cts`'s
* `HOST_INTEGRATION_AXES` (the canonical runtime vocabulary, compiled to
* `msd-core/bin/lib/host-integration.cjs`) are two independent, hand-written
* mirrors of the same underlying axis vocabulary. ADR-1239 amendment #2481
* added a ninth axis, `effortSurface`, to the canonical vocabulary but not to
* the registry's closed `AXES` key set — so a registry entry that faithfully
* mirrored its upstream `registry/eos-entry.json` (which DOES carry
* `effortSurface`) was rejected outright by the registry's exact-key-set
* check. `OPTIONAL_AXES` fixes that by adding `effortSurface` as a ninth,
* OPTIONAL registry axis key. This file asserts the two vocabularies stay in
* parity going forward, and pins the boundary behavior of the fix itself.
*
* Entries below are hand-built plain objects — never read from
* `docs/registries/eos.json` on disk, and no source file is read+string
* matched (both would trip `local/no-source-grep` / defeat the point of a
* behavioral regression test).
*/
const { test, describe } = require('node:test');
const assert = require('node:assert/strict');
const path = require('node:path');
const {
AXES,
OPTIONAL_AXES,
AXES_FREE_STRING,
validateEntries,
renderMarkdown,
} = require(path.join(__dirname, '..', 'scripts', 'registry-schema.cjs'));
const { HOST_INTEGRATION_AXES } = require(
path.join(__dirname, '..', 'msd-core', 'bin', 'lib', 'host-integration.cjs'),
);
// ─── Fixtures ─────────────────────────────────────────────────────────────
// A fully-valid eos entry carrying exactly the eight REQUIRED axes keys (no
// `effortSurface`) — same shape as the two real docs/registries/eos.json
// entries (msd-cursor, msd-omp), both published before ADR-1239 amendment
// #2481 and neither carrying `effortSurface`.
function baseEosEntry() {
return {
id: 'my-host-plugin',
name: 'My Host Plugin',
type: 'eos',
repo: 'octocat/my-host-plugin',
description: 'Embeds MSD as an orchestration engine in My Host.',
author: 'Octocat',
license: 'MIT',
enginesMsd: '>=1.6.0 <3.0.0',
install: 'See the My Host plugin marketplace listing.',
uninstall: 'Uninstall via the My Host plugin manager.',
protocolVersion: 1,
interactions: {
interfacePoints: ['command', 'state'],
profile: 'programmatic-cli',
axes: {
embeddingMode: 'imperative',
commandSurface: 'slash-file',
dispatch: 'Supports nested background dispatch up to depth 3.',
modelMode: 'active',
hookBus: 'host',
stateIO: 'filesystem',
transport: 'mcp',
runtime: 'node',
},
},
discussion: 'https://github.com/octocat/my-host-plugin/discussions/2',
};
}
// ─── Parity: registry AXES/OPTIONAL_AXES vs canonical HOST_INTEGRATION_AXES ──
describe('registry-axes-parity: AXES/OPTIONAL_AXES vs HOST_INTEGRATION_AXES', () => {
test('every key shared between (AXES ∪ OPTIONAL_AXES) and HOST_INTEGRATION_AXES has an identical enum array', () => {
const registryAxisKeys = new Set([...Object.keys(AXES), ...Object.keys(OPTIONAL_AXES)]);
const canonicalAxisKeys = new Set(Object.keys(HOST_INTEGRATION_AXES));
const shared = [...registryAxisKeys].filter((k) => canonicalAxisKeys.has(k));
// Sanity: the shared set must be non-empty, or the loop below would pass vacuously.
assert.ok(shared.length > 0, 'expected at least one axis key shared between the two vocabularies');
for (const key of shared) {
const registryValue = AXES[key] !== undefined ? AXES[key] : OPTIONAL_AXES[key];
if (registryValue === AXES_FREE_STRING) continue; // dispatch: asserted separately below
assert.deepEqual(
registryValue,
HOST_INTEGRATION_AXES[key],
`registry axis "${key}" enum must match HOST_INTEGRATION_AXES.${key}`,
);
}
});
test('dispatch is registry-only — absent from HOST_INTEGRATION_AXES — and carries the free-string sentinel', () => {
assert.equal(AXES.dispatch, AXES_FREE_STRING);
assert.equal(Array.isArray(AXES.dispatch), false);
assert.equal(Object.hasOwn(HOST_INTEGRATION_AXES, 'dispatch'), false);
});
test('OPTIONAL_AXES.effortSurface equals the canonical HOST_INTEGRATION_AXES.effortSurface exactly', () => {
assert.deepEqual(OPTIONAL_AXES.effortSurface, ['argv', 'none']);
assert.deepEqual(OPTIONAL_AXES.effortSurface, HOST_INTEGRATION_AXES.effortSurface);
});
// The enum-equality test above compares only keys the two vocabularies ALREADY
// share, so it is blind to the drift mode that actually produced #2810: a new
// canonical axis appears and the registry copy is never told. This test closes
// that hole — every canonical axis must be either modeled here or explicitly
// declared out of scope, so adding a canonical axis fails until someone
// decides which it is.
test('every HOST_INTEGRATION_AXES key is either modeled by the registry or explicitly declared out of scope', () => {
// Deliberately not modeled: both are `dispatch` sub-fields hoisted into the
// flat canonical map (CONTEXT.md's Host-Integration Interface entry lists
// dispatch as `{…, subagentToolkit, isolation}`). This registry collapses all
// of dispatch into one free-form human summary string, so they are covered by
// `AXES.dispatch` rather than carried as separate axes.
const NOT_MODELLED = Object.freeze(['subagentToolkit', 'isolation']);
const modelled = new Set([...Object.keys(AXES), ...Object.keys(OPTIONAL_AXES)]);
const unaccounted = Object.keys(HOST_INTEGRATION_AXES).filter(
(key) => !modelled.has(key) && !NOT_MODELLED.includes(key),
);
assert.deepEqual(
unaccounted,
[],
`HOST_INTEGRATION_AXES gained axis key(s) the EoS registry neither models nor excludes: ${unaccounted.join(', ')}. ` +
'Add each to AXES (required) or OPTIONAL_AXES (optional) in scripts/registry-schema.cjs, ' +
'or list it in this test\'s NOT_MODELLED allowlist with the reason it is out of scope.',
);
// Guard the allowlist itself: an entry that no longer exists canonically is
// stale and would silently widen the exemption for a future same-named axis.
for (const key of NOT_MODELLED) {
assert.ok(
Object.hasOwn(HOST_INTEGRATION_AXES, key),
`NOT_MODELLED lists "${key}", which is no longer a HOST_INTEGRATION_AXES key — remove it`,
);
}
});
});
// ─── Boundary coverage: axes key-count limit-1 / limit / limit+1 ──────────
describe('validateEntries: eos axes key-count boundaries (limit-1 / limit / limit+1)', () => {
test('limit-1: 7 keys (one required key missing) is INVALID and names the missing key', () => {
const entry = baseEosEntry();
delete entry.interactions.axes.runtime;
assert.equal(Object.keys(entry.interactions.axes).length, 7);
const verdict = validateEntries([entry], { type: 'eos' });
assert.equal(verdict.ok, false);
const err = verdict.errors.find((e) => e.field === 'interactions.axes' && /missing/i.test(e.reason));
assert.ok(err, `expected a missing-key error, got: ${JSON.stringify(verdict.errors)}`);
assert.ok(err.reason.includes('runtime'), `expected the error to name "runtime", got: ${err.reason}`);
});
test('limit: 8 keys (exactly the required axes) is VALID', () => {
const entry = baseEosEntry();
assert.equal(Object.keys(entry.interactions.axes).length, 8);
const verdict = validateEntries([entry], { type: 'eos' });
assert.equal(verdict.ok, true);
assert.deepEqual(verdict.errors, []);
});
test('limit+1: 8 required + effortSurface = 9 keys is VALID', () => {
const entry = baseEosEntry();
entry.interactions.axes.effortSurface = 'argv';
assert.equal(Object.keys(entry.interactions.axes).length, 9);
const verdict = validateEntries([entry], { type: 'eos' });
assert.equal(verdict.ok, true);
assert.deepEqual(verdict.errors, []);
});
test('8 required + an unknown 9th key (bogusAxis) is INVALID and names the unknown key', () => {
const entry = baseEosEntry();
entry.interactions.axes.bogusAxis = 'x';
assert.equal(Object.keys(entry.interactions.axes).length, 9);
const verdict = validateEntries([entry], { type: 'eos' });
assert.equal(verdict.ok, false);
const err = verdict.errors.find((e) => e.field === 'interactions.axes' && /unknown/i.test(e.reason));
assert.ok(err, `expected an unknown-key error, got: ${JSON.stringify(verdict.errors)}`);
assert.ok(err.reason.includes('bogusAxis'), `expected the error to name "bogusAxis", got: ${err.reason}`);
});
test('8 required + effortSurface with a value outside the enum ("config-file") is INVALID', () => {
const entry = baseEosEntry();
entry.interactions.axes.effortSurface = 'config-file';
const verdict = validateEntries([entry], { type: 'eos' });
assert.equal(verdict.ok, false);
assert.ok(verdict.errors.some((e) => e.field === 'interactions.axes.effortSurface'));
});
});
// ─── renderMarkdown: effortSurface presence/absence ────────────────────────
describe('renderMarkdown: eos effortSurface rendering', () => {
test('an entry carrying effortSurface renders "effortSurface=argv"', () => {
const entry = baseEosEntry();
entry.interactions.axes.effortSurface = 'argv';
const rendered = renderMarkdown([entry], { type: 'eos', sourceFile: 'eos.json' });
assert.ok(rendered.includes('effortSurface=argv'), rendered);
});
test('an entry that omits effortSurface renders no "effortSurface" substring at all', () => {
const entry = baseEosEntry();
const rendered = renderMarkdown([entry], { type: 'eos', sourceFile: 'eos.json' });
assert.ok(!rendered.includes('effortSurface'), rendered);
});
});