fix: third-party capability skills surface correctly (#2045)
A skills-only role:feature third-party capability installed 'active' but its skills never reached the runtime surface, capability enable/set rejected it as 'unknown capability', and capability list disagreed with capability state. Three defects, fix shape 1b (teach resolveSurface, no on-disk linking):
D1 (materialization): resolveSurface built the surfaced skills Set only from the on-disk manifest; third-party cap skills live at ~/.gsd/capabilities/<id>/skills/ and never entered the Set -> surfaced:false. Fix: union registry.capabilityClusters values into the Set in the full-profile branch (idempotent for first-party, additive for third-party; prototype-pollution guarded).
D2 (enable/set unknown): setCapabilityState validated the capId against the static first-party registry instead of the composed overlay-aware registry. Fix: validate against loadRegistry({includeInstalled}) like capability-state.cts does.
D3 (list vs state): capability list derived status purely from ledger existence, never surface composition. Fix: add a 'surfaced' field to list rows sourced from the same resolver capability state uses, so list and state agree.
Regression coverage: tests/issue-2045-third-party-skills-surface.test.cjs asserts all five acceptance criteria (resolveSurface union, state surfaced:true, enable/set not-unknown, list/state agreement, first-party + unknown-id regression). gsd-test: 23916/23916 green (linux-node22 + linux-node24).
This commit is contained in:
@@ -151,8 +151,17 @@ function setCapabilityState(
|
||||
const resolvedConfigDir = before.runtimeConfigDir;
|
||||
|
||||
// ── Load registry ─────────────────────────────────────────────────────────
|
||||
// Issue #2045 (DEFECT 2): validate against the COMPOSED overlay-aware registry
|
||||
// (first-party ∪ accepted overlays), mirroring capability-state.cts:547-551.
|
||||
// The frozen capability-registry.cjs only knows first-party ids, so a third-
|
||||
// party cap failed the membership check below → "unknown capability" even
|
||||
// though resolveCapabilityRuntimeState (the `before` snapshot, line 150) already
|
||||
// knew about it. loadRegistry is non-throwing and first-party-wins, so a
|
||||
// malformed overlay is skipped (never crashes the writer); a truly-unknown id
|
||||
// is STILL rejected because it is absent from the composed capabilities map.
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
const registry = require('./capability-registry.cjs') as Record<string, unknown>;
|
||||
const { loadRegistry } = require('./capability-loader.cjs') as { loadRegistry: (opts?: Record<string, unknown>) => Record<string, unknown> };
|
||||
const registry = loadRegistry({ includeInstalled: true, cwd, gsdHome: process.env['GSD_HOME'] });
|
||||
const capabilitiesMap = (
|
||||
registry['capabilities'] && typeof registry['capabilities'] === 'object' && !Array.isArray(registry['capabilities'])
|
||||
? registry['capabilities']
|
||||
|
||||
Reference in New Issue
Block a user