Files
msd-core/tests/non-claude-runtimes-registry-derivation.test.cjs
Tom Boucher a0fafedfa0 feat(#2103): drive VS Code through the Embeddable Orchestration System (ADR-1239)
VS Code is a net-new EoS runtime that — unlike every prior migration — is NOT
CLI-installed (Marketplace/VSIX extension). It has zero runtime==='vscode'
branches in bin/install.js and stays that way (regression-guarded); it is driven
entirely through the negotiated imperative Host-Integration adapter.

Registry + validator (the hard part):
- capabilities/vscode/capability.json (role:runtime): full hostIntegration block
  (imperative / palette / active vscode.lm model / engine hook bus /
  sandboxed-storage / mcp transport / sandboxed-web runtime; dispatch nested,
  maxDepth 5 per VS Code's documented subagent depth).
- capability-validator.cjs extended so a role:runtime capability can legitimately
  declare "extension-distributed, no config directory": new configHome.kind:'none'
  + installSurface:'none' (+ GATE-A pairing + the parity maps), with localConfigDir
  and configHome.name made conditional on kind!=='none'. All 18 runtimes still
  validate; getDirName returns a distinct sentinel (not '.claude') for a no-config
  runtime.
- The add-a-registry-runtime tax: NON_INSTALLABLE_RUNTIMES exemption in the
  runtime-flags drift guard, vscode added to global-config-home SPECIAL_CASED,
  EXPECTED_PROFILES.vscode='ide', and the config-adapter/derivation/pin-count
  guards updated. No golden-install fixture, model-catalog, or CONFIGURATION rows
  (vscode never enters allRuntimes).

Dispatch + extension surface:
- Fixed vscode/extension.js's createHub()-no-args bug (every dispatch was
  UnknownCommand, masked by a vacuous reachability test) — now reuses the shared
  dispatchGsdCommand subprocess-shim (Node/desktop); the reachability test is
  tightened to assert real dispatch.
- Promoted the #1933 host binding to a shipped vscode/host-binding.js; activate()
  now composes the model/hookBus/stateIO seams through it. Corrected the model
  seam to VS Code's real API (vscode.lm.selectChatModels() -> model.sendRequest();
  vscode.lm.sendRequest does not exist) so the binding actually composes on real
  desktop VS Code instead of throwing.
- New vscode/browser.js Web Extension entry with ZERO Node APIs (the engine's
  config/capability loading is Node-bound, so the web entry registers the surface
  and directs full dispatch to the native MCP server — honestly documented).
- UPGRADE 1: GSD skills as native Language Model Tools (contributes.languageModelTools
  + vscode.lm.registerTool), invoke() dispatching through the hub.
- UPGRADE 2: native subagent dispatch wired onto #runSubagent /
  chat.subagents.allowInvocationsFromSubagents (fail-soft on API availability,
  maxDepth 5 enforced).
- vscode/package.json: browser entry, engines.vscode ^1.105, chatParticipants +
  languageModelTools contributions; fixed a stale activationPoints->activationEvents
  manifest key. Added "vscode" to the package files array.

Docs (## vscode matrix section) + changeset (Added).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 23:24:42 -04:00

95 lines
4.5 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';
/**
* Drift-guard: NON_CLAUDE_RUNTIMES must always be derived from the capability
* registry. Verifies:
* 1. The exported constant equals a hardcoded golden expected list — a pinned
* oracle that catches BOTH formula bugs (derived value diverges from golden)
* AND unintended registry drift (adding/removing a runtime forces a
* deliberate golden-list update).
* 2. Every registry entry with role === 'runtime' and id !== 'claude' appears
* in NON_CLAUDE_RUNTIMES — a cross-check from a different angle than the
* production derivation formula.
* 3. Every member of NON_CLAUDE_RUNTIMES has an explicit getDirName branch that
* does not return '.claude' — guards against adding a runtime to the registry
* without teaching getDirName about it (ADR-1239 Phase B, #1679).
*
* Behavioral tests only: assert on returned values, no source-grep.
*/
const { test } = require('node:test');
const assert = require('node:assert/strict');
const conversion = require('../gsd-core/bin/lib/runtime-artifact-conversion.cjs');
const registry = require('../gsd-core/bin/lib/capability-registry.cjs');
const runtimeNamePolicy = require('../gsd-core/bin/lib/runtime-name-policy.cjs');
const { NON_CLAUDE_RUNTIMES } = conversion;
const { getDirName, NO_LOCAL_CONFIG_DIR_SENTINEL } = runtimeNamePolicy;
// Golden oracle: hardcoded sorted known-good list of all non-Claude runtimes.
// A pinned expected value in a TEST is correct — the test IS the oracle.
// EXPECTED is DERIVED from the capability registry (the single source of truth)
// so this guard stays fluid when a runtime is added or removed. The contract
// being pinned is the DERIVATION (NON_CLAUDE_RUNTIMES === registry runtimes −
// claude), not a frozen per-runtime snapshot.
const EXPECTED = Object.keys(registry.runtimes)
.filter((id) => id !== 'claude')
.sort();
test('NON_CLAUDE_RUNTIMES matches the golden expected set (sorted)', () => {
assert.deepEqual(
[...NON_CLAUDE_RUNTIMES],
EXPECTED,
`NON_CLAUDE_RUNTIMES diverged from golden list.\n` +
` actual: [${[...NON_CLAUDE_RUNTIMES].join(', ')}]\n` +
` expected: [${EXPECTED.join(', ')}]`,
);
// Explicit readability assertion: 'claude' must never appear.
assert.ok(
!NON_CLAUDE_RUNTIMES.includes('claude'),
'NON_CLAUDE_RUNTIMES must not contain "claude"',
);
});
test('every registry-declared runtime except claude is present in NON_CLAUDE_RUNTIMES', () => {
// Cross-check from a DIFFERENT angle than the production derivation formula:
// iterate registry entries by their role field rather than by Object.keys().filter().
// This catches a case where a runtime is added to the registry with role==='runtime'
// but is somehow excluded from NON_CLAUDE_RUNTIMES by a formula bug.
for (const [id, entry] of Object.entries(registry.runtimes)) {
if (entry.role === 'runtime' && id !== 'claude') {
assert.ok(
NON_CLAUDE_RUNTIMES.includes(id),
`Registry declares runtime '${id}' (role==='runtime') but it is missing from NON_CLAUDE_RUNTIMES`,
);
}
}
});
// Forward direction (registry → getDirName coverage) is the load-bearing guard:
// the registry is the authoritative runtime source, so every member of
// NON_CLAUDE_RUNTIMES must have an explicit getDirName branch.
test('DRIFT GUARD: every registry-declared non-Claude runtime has an explicit getDirName branch (not .claude)', () => {
for (const rt of NON_CLAUDE_RUNTIMES) {
const dir = getDirName(rt);
assert.notEqual(
dir,
'.claude',
`getDirName('${rt}') returned '.claude' — runtime '${rt}' is in the registry but missing an explicit getDirName branch`,
);
}
});
// #2103: vscode is a registry runtime (role:runtime) whose configHome.kind is
// 'none' — it has NO file-projected config directory at all (Marketplace/VSIX
// extension). It is covered by the generic loop above (its dir must not be
// '.claude'), but that assertion alone would ALSO pass for a plain string
// typo, so this pins the actual documented sentinel value honestly rather
// than riding on the generic "not .claude" check.
test('#2103: getDirName("vscode") returns the documented no-local-config-dir sentinel, not .claude and not a real dot-dir', () => {
assert.ok(NON_CLAUDE_RUNTIMES.includes('vscode'), 'vscode must be a registered non-Claude runtime');
const dir = getDirName('vscode');
assert.equal(dir, NO_LOCAL_CONFIG_DIR_SENTINEL);
assert.notEqual(dir, '.claude');
assert.ok(!dir.startsWith('.'), 'the sentinel must not look like a plausible dot-dir name');
});