Files
msd-core/tests/capability-precedence-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

886 lines
41 KiB
JavaScript
Raw Permalink 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';
/**
* capability-precedence-parity.test.cjs — DEFECT.GENERATIVE-FIX parity gate
*
* Proves that the config-key four-level precedence walk has a single owner
* (capability-activation.cjs) and that loop-resolver.cjs re-exports the same
* functions rather than maintaining local copies.
*
* Two contracts enforced:
* (a) Identity: loop-resolver's _resolveActivationValue, _getNestedConfigValue,
* and _readRawConfigKey are the SAME function objects as
* capability-activation's exports. Fails the instant a local copy is
* re-introduced.
* (b) Behavioral matrix: across a fixture matrix hitting each precedence level
* (loadConfig result, workstream config.json, root config.json, registry
* default, absent, falsy values, prototype-pollution key), asserts that
* _resolveActivationValue(key,config,cwd,registry) ===
* (resolveConfigKey(key,{config,cwd,registry}).found ?
* Boolean(resolveConfigKey(key,{config,cwd,registry}).value) : false).
* Level-2/3 cases use real tmpdir .planning/config.json fixtures.
*
* RULESET: RULESET.TESTS.no-source-grep — no readFileSync + .includes() on source.
* RULESET: RULESET.TESTS.boundary-coverage — covers limit-1/limit/limit+1 (falsy boundary).
*/
process.env.MSD_TEST_MODE = '1';
const { describe, test, before, after } = 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 { cleanup } = require('./helpers.cjs');
// ── Module paths ──────────────────────────────────────────────────────────────
const LIB = path.join(__dirname, '..', 'msd-core', 'bin', 'lib');
const capabilityActivation = require(path.join(LIB, 'capability-activation.cjs'));
const loopResolver = require(path.join(LIB, 'loop-resolver.cjs'));
const capabilityStateMod = require(path.join(LIB, 'capability-state.cjs'));
// ─── (a) Identity guard ───────────────────────────────────────────────────────
describe('capability-precedence-parity: identity guard — loop-resolver re-exports same function objects', () => {
test('_resolveActivationValue is the same function in both modules', () => {
assert.strictEqual(
loopResolver._resolveActivationValue,
capabilityActivation._resolveActivationValue,
'_resolveActivationValue: loop-resolver must re-export the capability-activation.cjs function, not a local copy',
);
});
test('_getNestedConfigValue is the same function in both modules', () => {
assert.strictEqual(
loopResolver._getNestedConfigValue,
capabilityActivation._getNestedConfigValue,
'_getNestedConfigValue: loop-resolver must re-export the capability-activation.cjs function, not a local copy',
);
});
test('_readRawConfigKey is the same function in both modules', () => {
assert.strictEqual(
loopResolver._readRawConfigKey,
capabilityActivation._readRawConfigKey,
'_readRawConfigKey: loop-resolver must re-export the capability-activation.cjs function, not a local copy',
);
});
// #3661
test('_resolvePointGate is the same function in both modules', () => {
assert.strictEqual(
loopResolver._resolvePointGate,
capabilityActivation._resolvePointGate,
'_resolvePointGate: loop-resolver must re-export the capability-activation.cjs function, not a local copy',
);
});
});
// ─── (a2) #3661 — _resolvePointGate pure-function matrix (50-test-matrix.md Section A) ─────
//
// A1-A10: pure, no-I/O behavioral coverage of _resolvePointGate directly (happy /
// boundary / negative / hostile), mirroring the style of the _resolveActivationValue
// matrix below but scoped to the new pointFrom gate.
describe('capability-precedence-parity: _resolvePointGate matrix (#3661, matrix Section A)', () => {
const { _resolvePointGate } = capabilityActivation;
test('A1: resolvePointGateTrueWhenPointFromAbsent — pointFrom undefined → true', () => {
const registry = makeRegistry('workflow.point_key', undefined);
assert.strictEqual(
_resolvePointGate(undefined, 'execute:post', {}, undefined, registry),
true,
);
});
test('A2: resolvePointGateTrueWhenPointFromNull — pointFrom null → true (mirrors undefined)', () => {
const registry = makeRegistry('workflow.point_key', undefined);
assert.strictEqual(
_resolvePointGate(null, 'execute:post', {}, undefined, registry),
true,
);
});
test('A3: resolvePointGateTrueOnExactMatch — config resolves to the SAME value as point → true', () => {
const key = 'workflow.point_key';
const config = { workflow: { point_key: 'execute:wave:post' } };
const registry = makeRegistry(key, undefined);
assert.strictEqual(
_resolvePointGate(key, 'execute:wave:post', config, undefined, registry),
true,
);
});
test('A4: resolvePointGateFalseOnMismatch — config resolves to a DIFFERENT value than point → false', () => {
const key = 'workflow.point_key';
const config = { workflow: { point_key: 'execute:post' } };
const registry = makeRegistry(key, undefined);
assert.strictEqual(
_resolvePointGate(key, 'execute:wave:post', config, undefined, registry),
false,
);
});
test('A5: resolvePointGateFalseWhenKeyUnresolvable — key absent from config and no schema default → false', () => {
const key = 'workflow.nonexistent_point_key';
const registry = makeRegistry(key, undefined);
assert.strictEqual(
_resolvePointGate(key, 'execute:post', {}, undefined, registry),
false,
);
});
test('A6: resolvePointGateFalseOnEmptyString — pointFrom="" → false (malformed, mirrors when)', () => {
const registry = makeRegistry('workflow.point_key', undefined);
assert.strictEqual(
_resolvePointGate('', 'execute:post', {}, undefined, registry),
false,
);
});
test('A7: resolvePointGateFalseOnNonStringType — pointFrom is a number/object/array → false', () => {
const registry = makeRegistry('workflow.point_key', undefined);
for (const malformed of [42, { key: 'workflow.point_key' }, ['workflow.point_key']]) {
assert.strictEqual(
_resolvePointGate(malformed, 'execute:post', {}, undefined, registry),
false,
`pointFrom=${JSON.stringify(malformed)} must resolve to false`,
);
}
});
test('A8: resolvePointGateUsesSchemaDefaultPrecedence — resolves via registry.configSchema default', () => {
const key = 'workflow.point_key';
// No explicit config value — only the schema default, matching point.
const registryMatch = makeRegistry(key, 'execute:post');
assert.strictEqual(
_resolvePointGate(key, 'execute:post', {}, undefined, registryMatch),
true,
'schema default equal to point must match',
);
// Schema default present but NOT equal to point → false.
const registryMismatch = makeRegistry(key, 'execute:wave:post');
assert.strictEqual(
_resolvePointGate(key, 'execute:post', {}, undefined, registryMismatch),
false,
'schema default not equal to point must not match',
);
});
test('A9: resolvePointGateDoesNotMatchOnDoubleEmptyUnlessFound — point="" and unresolved key must not spuriously match', () => {
const key = 'workflow.nonexistent_point_key';
const registry = makeRegistry(key, undefined); // no schema default → found:false
assert.strictEqual(
_resolvePointGate(key, '', {}, undefined, registry),
false,
'an unresolved (found:false) key must never match, even against an empty point string',
);
});
test('A10: resolvePointGateRejectsPrototypePollutionKey — pointFrom colliding with a prototype key segment → false', () => {
const key = 'a.__proto__.polluted';
const config = { a: { real: 1 } };
const registry = makeRegistry(key, undefined); // no schema default for this exact dotted key
assert.strictEqual(
_resolvePointGate(key, 'execute:post', config, undefined, registry),
false,
'__proto__ path segment must be rejected by the shared nested-traversal guard, yielding found:false',
);
});
});
// ─── (b) Behavioral matrix ────────────────────────────────────────────────────
/**
* Build a minimal registry with a configSchema entry for a dotted key.
* def=undefined means no default (absent). def=<value> provides a default.
*/
function makeRegistry(dotKey, def) {
const registry = {};
if (def !== undefined) {
registry.configSchema = {
[dotKey]: { default: def },
};
} else {
registry.configSchema = {};
}
return registry;
}
/**
* Helper: call resolveConfigKey and assert it equals
* _resolveActivationValue(key,config,cwd,registry) on the boolean coercion.
*/
function assertParity(key, config, cwd, registry, label) {
const { _resolveActivationValue, resolveConfigKey } = capabilityActivation;
const boolResult = _resolveActivationValue(key, config, cwd, registry);
const rawResult = resolveConfigKey(key, { config, cwd, registry });
const expectedBool = rawResult.found ? Boolean(rawResult.value) : false;
assert.strictEqual(
boolResult,
expectedBool,
`parity check failed for ${label}: _resolveActivationValue=${boolResult} but resolveConfigKey gives found=${rawResult.found} value=${JSON.stringify(rawResult.value)} → expectedBool=${expectedBool}`,
);
return { boolResult, rawResult };
}
describe('capability-precedence-parity: behavioral matrix', () => {
let tmpDir;
before(() => {
// Create a hermetic tmpdir for file-based fixtures (levels 2+3).
// Clear env vars that redirect planningDir/planningRoot.
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'parity-test-'));
});
after(() => {
// Remove the tmpdir (helpers.cleanup carries the Windows-EBUSY retry budget).
cleanup(tmpDir);
});
// Env vars that planningDir/planningRoot read — save/restore around each test.
function withCleanEnv(fn) {
const saved = {
MSD_WORKSTREAM: process.env.MSD_WORKSTREAM,
MSD_PROJECT: process.env.MSD_PROJECT,
CLAUDE_CONFIG_DIR: process.env.CLAUDE_CONFIG_DIR,
};
delete process.env.MSD_WORKSTREAM;
delete process.env.MSD_PROJECT;
delete process.env.CLAUDE_CONFIG_DIR;
try {
return fn();
} finally {
if (saved.MSD_WORKSTREAM !== undefined) process.env.MSD_WORKSTREAM = saved.MSD_WORKSTREAM;
else delete process.env.MSD_WORKSTREAM;
if (saved.MSD_PROJECT !== undefined) process.env.MSD_PROJECT = saved.MSD_PROJECT;
else delete process.env.MSD_PROJECT;
if (saved.CLAUDE_CONFIG_DIR !== undefined) process.env.CLAUDE_CONFIG_DIR = saved.CLAUDE_CONFIG_DIR;
else delete process.env.CLAUDE_CONFIG_DIR;
}
}
// ── Level 1: loadConfig result (config arg has the key) ─────────────────────
test('level-1 truthy: config arg has key=true → resolved true, parity holds', () => {
const key = 'feature.enabled';
const config = { feature: { enabled: true } };
const registry = makeRegistry(key, undefined);
const { boolResult, rawResult } = assertParity(key, config, undefined, registry, 'level-1 truthy');
assert.strictEqual(boolResult, true);
assert.strictEqual(rawResult.found, true);
assert.strictEqual(rawResult.value, true);
});
test('level-1 falsy (false): config arg has key=false → resolved false, parity holds', () => {
// BVA: falsy value false — boundary case
const key = 'feature.enabled';
const config = { feature: { enabled: false } };
const registry = makeRegistry(key, true); // default true but level-1 wins
const { boolResult, rawResult } = assertParity(key, config, undefined, registry, 'level-1 falsy=false');
assert.strictEqual(boolResult, false);
assert.strictEqual(rawResult.found, true);
assert.strictEqual(rawResult.value, false); // raw value preserved
});
test('level-1 falsy (0): config arg has key=0 → resolved false, parity holds', () => {
// BVA: falsy value 0 — boundary case
const key = 'feature.level';
const config = { feature: { level: 0 } };
const registry = makeRegistry(key, 2); // default 2 but level-1 wins
const { boolResult, rawResult } = assertParity(key, config, undefined, registry, 'level-1 falsy=0');
assert.strictEqual(boolResult, false);
assert.strictEqual(rawResult.found, true);
assert.strictEqual(rawResult.value, 0); // raw value 0 preserved
});
test('level-1 truthy (nonzero): config arg has key=2 → resolved true, parity holds', () => {
// BVA: truthy numeric — boundary-adjacent to 0
const key = 'feature.level';
const config = { feature: { level: 2 } };
const registry = makeRegistry(key, undefined);
const { boolResult, rawResult } = assertParity(key, config, undefined, registry, 'level-1 truthy=2');
assert.strictEqual(boolResult, true);
assert.strictEqual(rawResult.found, true);
assert.strictEqual(rawResult.value, 2);
});
// ── Level 2: workstream config.json ─────────────────────────────────────────
test('level-2: workstream config.json has key → resolved, level-1 empty, parity holds', () => {
withCleanEnv(() => {
// Set up a .planning/config.json in tmpDir
const planningDir = path.join(tmpDir, 'ws-l2', '.planning');
fs.mkdirSync(planningDir, { recursive: true });
fs.writeFileSync(
path.join(planningDir, 'config.json'),
JSON.stringify({ graphify: { enabled: true } }),
'utf8',
);
const key = 'graphify.enabled';
const config = {}; // level-1 miss
const registry = makeRegistry(key, undefined);
const cwd = path.join(tmpDir, 'ws-l2');
const { boolResult, rawResult } = assertParity(key, config, cwd, registry, 'level-2 workstream');
assert.strictEqual(boolResult, true);
assert.strictEqual(rawResult.found, true);
assert.strictEqual(rawResult.value, true);
});
});
test('level-2 falsy: workstream config.json has key=false → false, parity holds', () => {
withCleanEnv(() => {
const planningDir = path.join(tmpDir, 'ws-l2-false', '.planning');
fs.mkdirSync(planningDir, { recursive: true });
fs.writeFileSync(
path.join(planningDir, 'config.json'),
JSON.stringify({ graphify: { enabled: false } }),
'utf8',
);
const key = 'graphify.enabled';
const config = {};
const registry = makeRegistry(key, true); // default true, but level-2 wins
const cwd = path.join(tmpDir, 'ws-l2-false');
const { boolResult, rawResult } = assertParity(key, config, cwd, registry, 'level-2 falsy');
assert.strictEqual(boolResult, false);
assert.strictEqual(rawResult.found, true);
assert.strictEqual(rawResult.value, false);
});
});
// ── Level 3: root config.json (only when paths differ) ──────────────────────
// Without MSD_WORKSTREAM, planningDir === planningRoot, so levels 2 and 3 point
// to the same file. Level-3 distinction is only exercisable by setting MSD_WORKSTREAM.
// We test the "paths same → no double-read" path (covered by level-2 test above)
// and the "paths differ" path via MSD_WORKSTREAM.
test('level-3: root config.json read when workstream path differs, parity holds', () => {
// Use MSD_WORKSTREAM to force planningDir to differ from planningRoot.
withCleanEnv(() => {
const projectRoot = path.join(tmpDir, 'ws-l3-project');
// Root planning dir = projectRoot/.planning
const rootPlanningDir = path.join(projectRoot, '.planning');
// Workstream planning dir = projectRoot/.planning/workstreams/mystream
const wsName = 'mystream';
const wsPlanningDir = path.join(rootPlanningDir, 'workstreams', wsName);
fs.mkdirSync(rootPlanningDir, { recursive: true });
fs.mkdirSync(wsPlanningDir, { recursive: true });
// Write ONLY the root config.json (no ws config.json)
fs.writeFileSync(
path.join(rootPlanningDir, 'config.json'),
JSON.stringify({ intel: { enabled: true } }),
'utf8',
);
// Activate workstream so planningDir → wsPlanningDir, planningRoot → rootPlanningDir
process.env.MSD_WORKSTREAM = wsName;
const key = 'intel.enabled';
const config = {};
const registry = makeRegistry(key, undefined);
const { boolResult, rawResult } = assertParity(key, config, projectRoot, registry, 'level-3 root config');
// ws config.json absent → level-2 miss; root config.json present → level-3 hit
assert.strictEqual(boolResult, true);
assert.strictEqual(rawResult.found, true);
assert.strictEqual(rawResult.value, true);
});
});
// ── Level 4: registry configSchema default ───────────────────────────────────
test('level-4: registry default=true, no config or file → true, parity holds', () => {
withCleanEnv(() => {
const key = 'workflow.some_feature';
const config = {};
const registry = makeRegistry(key, true);
// cwd points to an empty dir — no .planning/config.json files
const cwd = path.join(tmpDir, 'l4-default-true');
fs.mkdirSync(cwd, { recursive: true });
const { boolResult, rawResult } = assertParity(key, config, cwd, registry, 'level-4 default=true');
assert.strictEqual(boolResult, true);
assert.strictEqual(rawResult.found, true);
assert.strictEqual(rawResult.value, true);
});
});
test('level-4: registry default=false → false (BVA: falsy default), parity holds', () => {
withCleanEnv(() => {
const key = 'workflow.some_feature';
const config = {};
const registry = makeRegistry(key, false);
const cwd = path.join(tmpDir, 'l4-default-false');
fs.mkdirSync(cwd, { recursive: true });
const { boolResult, rawResult } = assertParity(key, config, cwd, registry, 'level-4 default=false');
assert.strictEqual(boolResult, false);
assert.strictEqual(rawResult.found, true);
assert.strictEqual(rawResult.value, false); // raw false preserved
});
});
test('level-4: registry default=0 → false (BVA: numeric zero default), parity holds', () => {
withCleanEnv(() => {
const key = 'feature.level';
const config = {};
const registry = makeRegistry(key, 0);
const cwd = path.join(tmpDir, 'l4-default-0');
fs.mkdirSync(cwd, { recursive: true });
const { boolResult, rawResult } = assertParity(key, config, cwd, registry, 'level-4 default=0');
assert.strictEqual(boolResult, false);
assert.strictEqual(rawResult.found, true);
assert.strictEqual(rawResult.value, 0); // raw 0 preserved
});
});
// ── Level 5: absent ──────────────────────────────────────────────────────────
test('level-5: key absent everywhere → false, found=false, parity holds', () => {
withCleanEnv(() => {
const key = 'nonexistent.key';
const config = {};
const registry = makeRegistry(key, undefined); // no default
const cwd = path.join(tmpDir, 'l5-absent');
fs.mkdirSync(cwd, { recursive: true });
const { boolResult, rawResult } = assertParity(key, config, cwd, registry, 'level-5 absent');
assert.strictEqual(boolResult, false);
assert.strictEqual(rawResult.found, false);
assert.strictEqual(rawResult.value, undefined);
});
});
test('level-5: cwd=undefined, absent key → false, found=false, parity holds', () => {
const key = 'nonexistent.key';
const config = {};
const registry = makeRegistry(key, undefined);
const { boolResult, rawResult } = assertParity(key, config, undefined, registry, 'level-5 absent cwd=undefined');
assert.strictEqual(boolResult, false);
assert.strictEqual(rawResult.found, false);
});
// ── Prototype-pollution guard (nested-traversal sink, levels 1–3) ──────────────
//
// The guard protects the NESTED config-object traversal (_getNestedConfigValue) —
// the only prototype-pollution sink. A `__proto__`/`constructor`/`prototype`
// SEGMENT encountered mid-path is rejected → found=false. Level 4 is a flat
// single-key lookup of the whole dotted string against registry.configSchema (no
// nested traversal, not a pollution sink), so we deliberately seed NO level-4
// default here — otherwise that flat lookup would legitimately match the literal
// dotted key and the segment guard would not be the thing under test.
test('prototype-pollution: __proto__ mid-path segment → found=false, boolResult=false, parity holds', () => {
const key = 'a.__proto__.polluted';
const config = { a: { real: 1 } }; // 'a' resolves, then the '__proto__' segment must be rejected
const registry = makeRegistry(key, undefined); // no level-4 default
const { boolResult, rawResult } = assertParity(key, config, undefined, registry, 'proto-pollution __proto__ mid-path');
assert.strictEqual(rawResult.found, false, '__proto__ path segment must be rejected by the nested-traversal guard');
assert.strictEqual(boolResult, false);
});
test('prototype-pollution: constructor mid-path segment → found=false, boolResult=false, parity holds', () => {
const key = 'a.constructor.polluted';
const config = { a: { real: 1 } };
const registry = makeRegistry(key, undefined); // no level-4 default
const { boolResult, rawResult } = assertParity(key, config, undefined, registry, 'proto-pollution constructor mid-path');
assert.strictEqual(rawResult.found, false, 'constructor path segment must be rejected by the nested-traversal guard');
assert.strictEqual(boolResult, false);
});
// ── Level-1 precedence over level-4 ─────────────────────────────────────────
test('level-1 wins over level-4 default: config=false beats registry default=true', () => {
// BVA: this is the critical boundary — level-1 falsy beats level-4 truthy
const key = 'graphify.enabled';
const config = { graphify: { enabled: false } };
const registry = makeRegistry(key, true);
const { boolResult, rawResult } = assertParity(key, config, undefined, registry, 'level-1 false beats level-4 true default');
assert.strictEqual(boolResult, false, 'config value false must win over registry default true');
assert.strictEqual(rawResult.found, true);
assert.strictEqual(rawResult.value, false);
});
});
// ─── (c) resolveConfigKey identity guard (FIX 2 — loop-resolver re-exports resolveConfigKey) ────
//
// resolveLoopHooks.resolveConfigValues uses resolveConfigKey internally to build
// hook configValues. Exporting resolveConfigKey from loop-resolver and asserting
// identity here ensures that if someone ever introduces a local copy, this test
// will catch it immediately (same mechanism as the _resolveActivationValue guard
// above, applied to the raw-value consumer).
describe('capability-precedence-parity: identity guard — loop-resolver.resolveConfigKey is same fn as capability-activation.resolveConfigKey', () => {
test('resolveConfigKey is the same function in both modules', () => {
assert.ok(
typeof loopResolver.resolveConfigKey === 'function',
'loop-resolver must export resolveConfigKey',
);
assert.strictEqual(
loopResolver.resolveConfigKey,
capabilityActivation.resolveConfigKey,
'resolveConfigKey: loop-resolver must re-export the capability-activation.cjs function, not a local copy',
);
});
});
// ─── (d) Behavioral: loop-resolver resolveConfigValues uses the shared engine ────────────────────
//
// Proves that resolveLoopHooks's resolveConfigValues closure (the RAW-value consumer)
// produces the SAME result as calling capabilityActivation.resolveConfigKey directly
// for the same (dotKey, config, cwd, registry) arguments.
//
// This test FAILS if resolveConfigValues is ever replaced with a divergent copy that
// does not delegate to resolveConfigKey — fulfilling the DEFECT.GENERATIVE-FIX mandate.
//
// Coverage: three cases required:
// (1) Level-1 config hit — key present in config arg
// (2) Level-4 registry default — key absent from config, present in schema default
// (3) Absent key — key absent everywhere (omitted from resolved output)
describe('capability-precedence-parity: behavioral — loop-resolver resolveConfigValues uses shared resolveConfigKey engine', () => {
const { resolveLoopHooks } = loopResolver;
const { resolveConfigKey } = capabilityActivation;
const { CANONICAL_POINTS_FALLBACK } = loopResolver;
/**
* Build a minimal registry that has:
* - one contribution hook at the given point with configValues map
* - one capability that owns that hook (capId = 'test-cap', active=true implied)
* - configSchema entries for level-4 defaults
*/
function makeLoopRegistry({ point, configValues, schemaDefaults = {} }) {
const byLoopPoint = {};
for (const p of CANONICAL_POINTS_FALLBACK) {
byLoopPoint[p] = { steps: [], contributions: [], gates: [] };
}
byLoopPoint[point].contributions = [
{
capId: 'test-cap',
into: 'test-section',
fragment: { inline: 'test fragment' },
configValues,
},
];
const configSchema = {};
for (const [dotKey, def] of Object.entries(schemaDefaults)) {
configSchema[dotKey] = { default: def };
}
return { byLoopPoint, configSchema };
}
/** capabilityStatesById with test-cap active=true */
function activeCapMap() {
return new Map([['test-cap', { enabled: true, active: true }]]);
}
test('(1) level-1 config hit: resolveConfigValues matches resolveConfigKey for present key', () => {
// Hook declares configValues: { secLevel: 'security.asvs_level' }
// Config has security.asvs_level=2 → level-1 hit
const dotKey = 'security.asvs_level';
const alias = 'secLevel';
const config = { security: { asvs_level: 2 } };
const registry = makeLoopRegistry({
point: 'plan:pre',
configValues: { [alias]: dotKey },
});
const resolved = resolveLoopHooks({
point: 'plan:pre',
registry,
config,
cwd: undefined,
capabilityStatesById: activeCapMap(),
});
assert.strictEqual(resolved.activeHooks.length, 1, 'One active contribution hook expected');
const hook = resolved.activeHooks[0];
assert.ok(hook.configValues, 'configValues must be present on the resolved hook');
// resolveConfigKey direct result for the same args
const directResult = resolveConfigKey(dotKey, { config, cwd: undefined, registry });
assert.strictEqual(directResult.found, true, 'direct resolveConfigKey must find the level-1 key');
assert.strictEqual(
hook.configValues[alias],
directResult.value,
`resolved hook configValues[${alias}] must equal resolveConfigKey(...).value for level-1 hit (both=${JSON.stringify(directResult.value)})`,
);
assert.strictEqual(hook.configValues[alias], 2, 'level-1 numeric value 2 must be preserved (not coerced to boolean)');
});
test('(2) level-4 registry default hit: resolveConfigValues matches resolveConfigKey for schema default', () => {
// Hook declares configValues: { blockOn: 'security.block_on' }
// Config is empty; schema default = 'medium'
const dotKey = 'security.block_on';
const alias = 'blockOn';
const schemaDefault = 'medium';
const config = {};
const registry = makeLoopRegistry({
point: 'execute:pre',
configValues: { [alias]: dotKey },
schemaDefaults: { [dotKey]: schemaDefault },
});
const resolved = resolveLoopHooks({
point: 'execute:pre',
registry,
config,
cwd: undefined,
capabilityStatesById: activeCapMap(),
});
assert.strictEqual(resolved.activeHooks.length, 1, 'One active contribution hook expected');
const hook = resolved.activeHooks[0];
assert.ok(hook.configValues, 'configValues must be present on the resolved hook (schema default found)');
const directResult = resolveConfigKey(dotKey, { config, cwd: undefined, registry });
assert.strictEqual(directResult.found, true, 'direct resolveConfigKey must find the level-4 schema default');
assert.strictEqual(
hook.configValues[alias],
directResult.value,
`resolved hook configValues[${alias}] must equal resolveConfigKey(...).value for level-4 hit (both=${JSON.stringify(directResult.value)})`,
);
assert.strictEqual(hook.configValues[alias], 'medium', 'level-4 string default must be preserved as raw string');
});
test('(3a) mixed hook: one resolvable alias + one absent alias → configValues present, contains only the resolvable alias', () => {
// Hook declares configValues: { present: 'feature.on', absent: 'nope.missing' }
// Config has feature.on=true (level-1 hit); no registry default for nope.missing.
// Expected: resolved hook.configValues is defined, has 'present'=true, does NOT have 'absent'.
const presentDotKey = 'feature.on';
const absentDotKey = 'nope.missing';
const config = { feature: { on: true } };
const registry = makeLoopRegistry({
point: 'verify:post',
configValues: { present: presentDotKey, absent: absentDotKey },
// No schema default for absentDotKey — it must be absent everywhere
});
const resolved = resolveLoopHooks({
point: 'verify:post',
registry,
config,
cwd: undefined,
capabilityStatesById: activeCapMap(),
});
assert.strictEqual(resolved.activeHooks.length, 1, 'One active contribution hook expected');
const hook = resolved.activeHooks[0];
// The 'present' alias must resolve via level-1 config hit
assert.ok(hook.configValues !== undefined, 'configValues must be defined (at least one alias resolved)');
assert.strictEqual(
hook.configValues['present'],
true,
"hook.configValues['present'] must equal true (level-1 hit for feature.on)",
);
// The 'absent' alias must NOT appear in configValues at all (omit-when-absent contract)
assert.ok(
!Object.prototype.hasOwnProperty.call(hook.configValues, 'absent'),
"Absent key 'nope.missing' must not appear in hook.configValues (found=false → omit, not include as undefined)",
);
});
test('(3b) all-absent hook: all configValues aliases absent everywhere → hook.configValues === undefined', () => {
// Hook declares configValues: { missingAlias: 'does.not.exist' }
// Key absent from config, no schema default, no config files.
// resolveConfigValues must return undefined (empty resolved map → omitted entirely).
const dotKey = 'does.not.exist';
const alias = 'missingAlias';
const config = {};
const registry = makeLoopRegistry({
point: 'ship:pre',
configValues: { [alias]: dotKey },
// No schema default for this key
});
const resolved = resolveLoopHooks({
point: 'ship:pre',
registry,
config,
cwd: undefined,
capabilityStatesById: activeCapMap(),
});
assert.strictEqual(resolved.activeHooks.length, 1, 'One active contribution hook expected');
const hook = resolved.activeHooks[0];
// resolveConfigKey direct result must confirm absent
const directResult = resolveConfigKey(dotKey, { config, cwd: undefined, registry });
assert.strictEqual(directResult.found, false, 'resolveConfigKey must return found=false for absent key');
assert.strictEqual(directResult.value, undefined);
// When ALL aliases are absent, resolveConfigValues returns undefined → hook.configValues must be undefined
assert.strictEqual(
hook.configValues,
undefined,
'hook.configValues must be undefined when all aliases are absent (omit-when-empty contract)',
);
});
});
// ─── (e) #3661 — loop-resolver and capability-state agree on pointFrom selection ──
// (50-test-matrix.md Section D)
//
// A single synthetic two-point capability fixture is run through BOTH resolvers
// (resolveLoopHooks / resolveCapabilityState) sharing the same enum config key,
// proving the two consumers of _resolvePointGate can never diverge.
describe('capability-precedence-parity: loop-resolver + capability-state agree on pointFrom selection (#3661, matrix Section D)', () => {
const { resolveCapabilityState } = capabilityStateMod;
const { resolveLoopHooks, CANONICAL_POINTS_FALLBACK } = loopResolver;
/**
* Build one synthetic capability declared twice (point A / point B) sharing one
* enum `pointFrom` key, plus a third control step at point A with NO `pointFrom`
* (governed by `when` alone). Returns both a loop-resolver-shaped registry
* (byLoopPoint) and a capability-state-shaped registry (capabilities), built from
* the SAME step objects, so both resolvers see byte-identical hook declarations.
*/
function makeTwoPointFixture() {
const capId = 'test-two-point-cap';
const pointA = 'execute:post';
const pointB = 'execute:wave:post';
const enumKey = 'workflow.test_point_select';
const whenAKey = 'workflow.test_step_a_enabled';
const whenBKey = 'workflow.test_step_b_enabled';
const whenControlKey = 'workflow.test_control_enabled';
const stepA = {
capId, point: pointA, ref: { skill: 'test-skill' },
produces: [], consumes: [], when: whenAKey, pointFrom: enumKey, onError: 'skip',
};
const stepB = {
capId, point: pointB, ref: { skill: 'test-skill' },
produces: [], consumes: [], when: whenBKey, pointFrom: enumKey, onError: 'skip',
};
// Control: no pointFrom at all — must be unaffected by the enum's value (D4).
const controlStep = {
capId, point: pointA, ref: { skill: 'control-skill' },
produces: [], consumes: [], when: whenControlKey, onError: 'skip',
};
const configSchema = {
[enumKey]: { default: pointA },
[whenAKey]: { default: true },
[whenBKey]: { default: true },
[whenControlKey]: { default: true },
};
const byLoopPoint = {};
for (const p of CANONICAL_POINTS_FALLBACK) {
byLoopPoint[p] = { steps: [], contributions: [], gates: [] };
}
byLoopPoint[pointA].steps = [stepA, controlStep];
byLoopPoint[pointB].steps = [stepB];
const loopRegistry = { byLoopPoint, configSchema };
const capStateRegistry = {
capabilities: {
[capId]: {
id: capId, tier: 'standard', skills: [], steps: [stepA, stepB, controlStep],
gates: [], contributions: [], config: {},
},
},
configSchema,
};
const capabilityStatesById = new Map([[capId, { enabled: true, active: true }]]);
return {
capId, pointA, pointB, enumKey, whenAKey, whenBKey, whenControlKey,
loopRegistry, capStateRegistry, capabilityStatesById,
};
}
function capStateHooks(fixture, config) {
const result = resolveCapabilityState({
registry: fixture.capStateRegistry,
installedSkills: '*',
surfacedSkills: new Set(),
config,
});
assert.strictEqual(result.capabilities.length, 1, 'fixture declares exactly one capability');
return result.capabilities[0].hooks;
}
test('D1: loopResolverAndCapabilityStateAgreeOnDefaultPointFromSelection', () => {
const f = makeTwoPointFixture();
const config = {}; // everything resolves via schema default: enumKey -> pointA
const resultA = resolveLoopHooks({ point: f.pointA, registry: f.loopRegistry, config, capabilityStatesById: f.capabilityStatesById });
const resultB = resolveLoopHooks({ point: f.pointB, registry: f.loopRegistry, config, capabilityStatesById: f.capabilityStatesById });
const loopStepAActive = resultA.activeHooks.some((h) => h.when === f.whenAKey);
const loopStepBActive = resultB.activeHooks.some((h) => h.when === f.whenBKey);
assert.strictEqual(loopStepAActive, true, 'loop-resolver: point A step must be active by default');
assert.strictEqual(loopStepBActive, false, 'loop-resolver: point B step must be inactive by default');
const hooks = capStateHooks(f, config);
const stateStepA = hooks.find((h) => h.when === f.whenAKey);
const stateStepB = hooks.find((h) => h.when === f.whenBKey);
assert.strictEqual(stateStepA.active, true, 'capability-state: point A step must be active by default');
assert.strictEqual(stateStepB.active, false, 'capability-state: point B step must be inactive by default');
assert.strictEqual(loopStepAActive, stateStepA.active, 'resolvers must agree on point A');
assert.strictEqual(loopStepBActive, stateStepB.active, 'resolvers must agree on point B');
});
test('D2: loopResolverAndCapabilityStateAgreeOnFlippedPointFromSelection', () => {
const f = makeTwoPointFixture();
const config = { workflow: { test_point_select: f.pointB } };
const resultA = resolveLoopHooks({ point: f.pointA, registry: f.loopRegistry, config, capabilityStatesById: f.capabilityStatesById });
const resultB = resolveLoopHooks({ point: f.pointB, registry: f.loopRegistry, config, capabilityStatesById: f.capabilityStatesById });
const loopStepAActive = resultA.activeHooks.some((h) => h.when === f.whenAKey);
const loopStepBActive = resultB.activeHooks.some((h) => h.when === f.whenBKey);
assert.strictEqual(loopStepAActive, false, 'loop-resolver: point A step must flip to inactive');
assert.strictEqual(loopStepBActive, true, 'loop-resolver: point B step must flip to active');
const hooks = capStateHooks(f, config);
const stateStepA = hooks.find((h) => h.when === f.whenAKey);
const stateStepB = hooks.find((h) => h.when === f.whenBKey);
assert.strictEqual(stateStepA.active, false, 'capability-state: point A step must flip to inactive');
assert.strictEqual(stateStepB.active, true, 'capability-state: point B step must flip to active');
assert.strictEqual(loopStepAActive, stateStepA.active, 'resolvers must agree on point A after flip');
assert.strictEqual(loopStepBActive, stateStepB.active, 'resolvers must agree on point B after flip');
});
test('D3: loopResolverAndCapabilityStateAgreeOnOutOfEnumPointFromValue', () => {
const f = makeTwoPointFixture();
const config = { workflow: { test_point_select: 'bogus' } };
const resultA = resolveLoopHooks({ point: f.pointA, registry: f.loopRegistry, config, capabilityStatesById: f.capabilityStatesById });
const resultB = resolveLoopHooks({ point: f.pointB, registry: f.loopRegistry, config, capabilityStatesById: f.capabilityStatesById });
const loopStepAActive = resultA.activeHooks.some((h) => h.when === f.whenAKey);
const loopStepBActive = resultB.activeHooks.some((h) => h.when === f.whenBKey);
assert.strictEqual(loopStepAActive, false, 'loop-resolver: point A step must be inactive on an out-of-enum value');
assert.strictEqual(loopStepBActive, false, 'loop-resolver: point B step must be inactive on an out-of-enum value');
const hooks = capStateHooks(f, config);
const stateStepA = hooks.find((h) => h.when === f.whenAKey);
const stateStepB = hooks.find((h) => h.when === f.whenBKey);
assert.strictEqual(stateStepA.active, false, 'capability-state: point A step must be inactive on an out-of-enum value');
assert.strictEqual(stateStepB.active, false, 'capability-state: point B step must be inactive on an out-of-enum value');
assert.strictEqual(loopStepAActive, stateStepA.active, 'resolvers must agree: both points inactive');
assert.strictEqual(loopStepBActive, stateStepB.active, 'resolvers must agree: both points inactive');
});
test('D4: loopResolverAndCapabilityStateAgreeWhenPointFromAbsent', () => {
// Reuse D3's out-of-enum config — the strongest proof that the control step
// (no pointFrom at all) is UNAFFECTED by the enum's value in either resolver.
const f = makeTwoPointFixture();
const config = { workflow: { test_point_select: 'bogus' } };
const resultA = resolveLoopHooks({ point: f.pointA, registry: f.loopRegistry, config, capabilityStatesById: f.capabilityStatesById });
const loopControlActive = resultA.activeHooks.some((h) => h.when === f.whenControlKey);
assert.strictEqual(loopControlActive, true, 'loop-resolver: control step (no pointFrom) must remain active, governed by when alone');
const hooks = capStateHooks(f, config);
const stateControl = hooks.find((h) => h.when === f.whenControlKey);
assert.strictEqual(stateControl.active, true, 'capability-state: control step (no pointFrom) must remain active, governed by when alone');
assert.strictEqual(loopControlActive, stateControl.active, 'resolvers must agree the control step is unaffected');
});
});