* feat(#1431): runtime capability registry overlay (ADR-1244 Phase 2) Promote the registry from a frozen data file to loadRegistry({includeInstalled}), composing the first-party registry with a validated installed overlay (ADR-1244 D2): - Extract the conformance validator to a shared runtime-callable module (gsd-core/bin/lib/capability-validator.cjs); the generator re-exports it verbatim, guarded by a generative-parity test (no build-time/runtime drift). - capability-loader.cts: loadRegistry({includeInstalled}) composes first-party ∪ validated overlay from $GSD_HOME/.gsd/capabilities (global) and <root>/.gsd/capabilities (project) via the canonical buildRegistry. First-party always wins (id/skill/agent/config/command-family + reserved gsd-/anthropic- prefixes); full merged-set cross-capability validation; engines.gsd load-time re-gate (skip-with-warning); gate-kind capabilities FAIL CLOSED; fragment-path escapes rejected. - semverSatisfies (hand-written, no dep) for the engines.gsd gate, fail-closed. - Wire surface/state + loop to the overlay; loop injects a blocking gate for each skipped gate-kind overlay (fail-closed). - cwd-aware overlay config-key federation: config-loader _federatedConfigSchema(cwd) + config-schema isValidConfigKey(key, cwd) compose the overlay per loadConfig/ config-set call (never eager at module load, never wrong-cwd); first-party path unchanged with no cwd. - run-tests.cjs sandboxes GSD_HOME (idempotent — nested spawns reuse it) for test hermeticity; capability-loader.cjs git+eslint-ignored (tsc artifact); capability-validator.cjs stays linted (#551 migration coverage). Closes #1431 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(#1431): add changeset for runtime capability registry overlay Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(#1431): kill config-schema cwd-aware federation mutants (Stryker ≥52) The cwd-aware overlay config-key federation added to config-schema.cts (_capabilityConfigSchema(cwd) + isCapabilityConfigKey/isValidConfigKey cwd threading) introduced mutable surface uncovered by config-schema's mutation test set, dropping its score to 39.58% (below the 52 break threshold). Add a real-overlay-fixture describe block exercising every branch (cwd guard, overlay loadRegistry, found-branch, first-party fallback, cwd threading); local Stryker score 39.58% -> 77.08%. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -69,6 +69,11 @@ const {
|
||||
|
||||
const { LOOP_HOST_CONTRACT } = require('../gsd-core/bin/lib/loop-host-contract.cjs');
|
||||
|
||||
// ADR-1244 D2: the validator was extracted to a shared runtime-callable module.
|
||||
// The generator must re-export it verbatim — the parity suite below proves no drift.
|
||||
const capValidatorModule = require('../gsd-core/bin/lib/capability-validator.cjs');
|
||||
const generatorModule = require('../scripts/gen-capability-registry.cjs');
|
||||
|
||||
const fc = require('fast-check');
|
||||
|
||||
const ROOT = path.resolve(__dirname, '..');
|
||||
@@ -5035,3 +5040,61 @@ describe('activationKey validation', () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// ─── ADR-1244 D2: validator extraction generative parity ──────────────────────
|
||||
//
|
||||
// The validator now lives in gsd-core/bin/lib/capability-validator.cjs and is
|
||||
// re-exported by the generator. These assertions guarantee the build-time
|
||||
// generator and the runtime overlay share ONE validator implementation — no
|
||||
// divergent copy can drift between them, because the generator re-exports the
|
||||
// very same object references.
|
||||
describe('ADR-1244 D2: validator extraction generative parity', () => {
|
||||
const CORE = [
|
||||
'validateCapability', 'validateCrossCapability', 'validateVersionEnvelope',
|
||||
'validateConsumesGlobal', 'validateAgainstContract', 'validateConfigSliceEntry',
|
||||
'validateRuntimeBody', 'classifyCrossErrors',
|
||||
];
|
||||
|
||||
test('the runtime validator module exposes the full validator surface', () => {
|
||||
for (const sym of [...CORE, 'SEMVER_RE', 'SEMVER_RANGE_RE', 'POINT_ORDER', 'VALID_LOOP_POINTS', 'VALID_TIERS']) {
|
||||
assert.ok(sym in capValidatorModule, `validator module must export ${sym}`);
|
||||
}
|
||||
assert.strictEqual(typeof capValidatorModule.validateCapability, 'function');
|
||||
assert.ok(capValidatorModule.SEMVER_RE instanceof RegExp);
|
||||
});
|
||||
|
||||
test('every generator-re-exported validator symbol is the SAME object as the validator module (no drift)', () => {
|
||||
const shared = Object.keys(capValidatorModule).filter((k) => Object.prototype.hasOwnProperty.call(generatorModule, k));
|
||||
assert.ok(shared.length >= 20, `expected the generator to re-export the validator surface, got ${shared.length}`);
|
||||
for (const k of shared) {
|
||||
assert.strictEqual(
|
||||
generatorModule[k],
|
||||
capValidatorModule[k],
|
||||
`generator export "${k}" must be the SAME reference as the validator module's (drift detected)`,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test('core validators are re-exported identically by the generator', () => {
|
||||
for (const sym of CORE) {
|
||||
assert.strictEqual(
|
||||
generatorModule[sym], capValidatorModule[sym],
|
||||
`${sym} must be re-exported by the generator as the validator module's reference`,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test('the extracted validator runs standalone (no generator/build-time deps required)', () => {
|
||||
// Proves the module is genuinely runtime-callable: a clean require + validate
|
||||
// with no install-profiles/clusters/config-schema machinery present.
|
||||
const { validateCapability } = capValidatorModule;
|
||||
const cap = {
|
||||
id: 'demo', role: 'feature', version: '1.0.0', title: 'Demo', description: 'demo',
|
||||
tier: 'standard', requires: [], runtimeCompat: { supported: ['*'], unsupported: [] },
|
||||
skills: [], agents: [], hooks: [], config: {}, steps: [], contributions: [], gates: [],
|
||||
};
|
||||
assert.deepEqual(validateCapability(cap, 'demo'), []);
|
||||
const { version: _v, ...noVersion } = cap;
|
||||
assert.ok(validateCapability(noVersion, 'demo').some((e) => e.includes('version')));
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user