test(mcp): contract drift-guard — pen-core builder exists for every tool

Pairs with the apps/web shim drift-guard (SUPPORTED_EMBEDDED_ELEMENT_TOOLS
⇔ ELEMENT_TOOL_NAMES). This one fires from the server side: every
add_*_v0 registered in pen-mcp must have a matching pen-core
buildX export. Catches the case where a new handler ships without
the canonical pen-core builder (which would mean the embedded
shim + Nitro SERVER_BUILDERS can't cover it, silently degrading
AI generation under the flag).

Failure message maps each missing tool to the expected buildX
name so the fix is mechanical. pen-mcp tests: 8 → 9.
This commit is contained in:
Fini 2026-04-22 00:36:17 +08:00
parent 05cfa53795
commit 2bca3e2abf

View file

@ -131,6 +131,33 @@ describe('element tools — v0-MUST contract', () => {
expect(props?.variant, `${name} must NOT have variant union`).toBeUndefined();
}
});
it('drift-guard: every add_*_v0 has a matching pen-core builder export', async () => {
// Pairs with the apps/web `SUPPORTED_EMBEDDED_ELEMENT_TOOLS`
// drift-guard — from the server side, verify each pen-mcp tool
// name maps to a pen-core `buildX` export. Catches the case
// where a handler is added/renamed but the pen-core builder
// (the drift-free source) isn't.
const penCore = await import('@zseven-w/pen-core');
const missing: string[] = [];
for (const name of ELEMENT_TOOL_NAMES) {
// add_xxx_v0 → buildXxx (camelCase)
const stem = name.replace(/^add_/, '').replace(/_v\d+$/, '');
const builderKey = `build${stem
.split('_')
.map((p) => p.charAt(0).toUpperCase() + p.slice(1))
.join('')}`;
if (typeof (penCore as Record<string, unknown>)[builderKey] !== 'function') {
missing.push(`${name} → ${builderKey}`);
}
}
expect(
missing,
`Element tools without a pen-core builder — add the buildX export to ` +
`packages/pen-core/src/element-builders/ (and wire it through the index.ts + ` +
`src/index.ts barrel): ${missing.join(', ')}`,
).toEqual([]);
});
});
// Regression test for the post-insert verification path in