refactor(availability): launch gate on the shared probe; typed IAdapterRegistry surface (#435, parts 3-4) - #443
Merged
Merged
Conversation
…erRegistry surface (#435, parts 3-4) Part 3 - checkLaunchToolchain is now a thin wrapper over probeLanguageEntry with a synthetic entry (installed: true, empty disabledSet, which provably neutralize the probe's notInstalled/disabled short-circuits - disabled languages are refused upstream at create_debug_session). The gate's answer and reason text are now literally computeModeAvailability's launch mode, so the gate can no longer drift from list_supported_languages the way the server/doctor loops used to. Direct unit tests pin the full fail-open contract, and a drift-fence matrix asserts gate === probe.modes.launch across every registry shape (the gate-side analogue of the doctor parity test). Part 4 - IAdapterRegistry gains the typed discovery surface the server was reaching through `as unknown as` casts: required listLanguages / listAvailableAdapters / getFactory / getFactoryMetadata (plus register aligned to Promise<void>), and optional getFactoryResult. All four cast sites (server.ts x3, session-manager-operations.ts) are removed; a rename on the concrete AdapterRegistry now breaks the build instead of silently degrading every language to fail-open. Doctor's duplicate DoctorRegistry structural type becomes a Pick of the interface. getFactoryResult fixes the swallowed-loader-error hole: AdapterRegistry's `catch { return undefined }` collapsed a missing package, a broken export, and dynamic-loading-disabled into one undefined. getFactory now delegates to getFactoryResult (contract unchanged, one code path), the availability probe prefers getFactoryResult when present, and doctor finally reports the loader's real error message instead of "(the registry returned no factory)". The two remaining discovery-fallback swallows now log warnings. New shared types: AdapterManifestEntry (the loader manifest shape, disambiguated from the factory-declared AdapterMetadata), FactoryLoadResult. Test fixtures: the central IAdapterRegistry mock now provides fail-open- neutral defaults for the required members (mirroring the pre-typed fallback paths); the WithErrors/WithLanguages builders override the discovery members consistently. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
Review findings on parts 3-4, fixed: - AdapterRegistry no longer creates a per-instance winston logger (which piped every HTTP-session registry into the process-lifetime shared transport with no detach path - the issue-#404 leak class, and it bypassed --log-level/--log-file). AdapterRegistryConfig gains an injected `logger` sink, wired from the container logger in createProductionDependencies; absent -> warnings dropped. - the drift-fence matrix was vacuous: its describe-scope vi.fn fixtures were wiped by the global afterEach vi.resetAllMocks before any fence test ran, so both sides always failed open and true===true passed. Registries are now built per-test via thunks and each shape carries an expectAvailable pin, so silent vacuity cannot recur. - checkLaunchToolchain warns with the real load failure carried in probe.factoryLoadError instead of discarding the breadcrumb the getFactoryResult plumbing exists to deliver; its fallback sentence moves to ErrorMessages.modeUnavailableReason.launchFallback (rule 5), and the docstring's "cannot drift" claim is rescoped to the toolchain axis. - the probe's getFactoryResult option type derives from the shared FactoryLoadResult (Omit + widened factory) so a field rename there is a build break, not silent drift; the resolution is optional-chained so an untyped registry resolving undefined lands in the honest no-factory branch instead of a bogus corrupt-adapter diagnosis. - listAvailableAdapters survives a registered factory whose getMetadata throws (attach falls back to 'none' with a warn) - one bad plain-JS factory no longer kills every doctor verdict. - the attach-'none' gate warn-logs when a registry double lacks getFactoryMetadata instead of silently self-disabling; the coverage suite's registry double gains the member so its ~20 attachToProcess tests actually exercise the gate. - isAdapterRegistry now checks the four new required members (a legacy three-method registry is no longer certified), with first guard tests. - the parity fence's fake registry offers getFactoryResult, so the fence covers the branch production actually runs; the central mock keeps it deliberately absent (a default would shadow per-test getFactory overrides) and its discovery defaults are implementations that survive mock resets. - DoctorDependencies.adapterRegistry is typed DoctorRegistry (cast gone; runtime guard kept as defense against override casts). Not addressed (deliberate): per-launch getMetadata warns for broken factories are undeduped - they only fire for already-broken adapters and aid diagnosis. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Parts 3–4 of #435, completing the issue (part 1: #437; part 2: #442).
Part 3 — launch gate on the shared probe
checkLaunchToolchain(the gate behindcreate_debug_sessionandstart_debugging, issue #360) was the last hand-rolled getFactory→validate→fail-open path. It is now a thin wrapper overprobeLanguageEntrywith a synthetic entry —installed: trueplus an emptydisabledSetprovably neutralize the probe's notInstalled/disabled short-circuits, so the gate keeps gating on the toolchain only (disabled languages are already refused upstream atcreate_debug_session). The gate's availability answer and reason text are now literallycomputeModeAvailability's launch mode, so it can no longer drift fromlist_supported_languagesthe way the server/doctor loops used to before part 1.The full fail-open contract was pinned with direct unit tests before the rewrite (missing registry, throwing getFactory, missing validate, throwing validate, load errors, the empty-errors fallback sentence, one-validate-per-cache-window), and a drift-fence matrix asserts
checkLaunchToolchain(...) ≡ probeLanguageEntry(synthetic).modes.launchacross every registry shape — the gate-side analogue of the doctor parity test. The existing launch-gate and #360 carve-out suites pass unmodified.Part 4 — typed registry surface + loader-error surfacing
IAdapterRegistrygains the members the server was reaching throughas unknown asduck-typing: requiredlistLanguages/listAvailableAdapters/getFactory/getFactoryMetadata(plusregisteraligned to its actualPromise<void>), and optionalgetFactoryResult. All four cast sites are gone (server.ts×3,session-manager-operations.ts×1; runtimetypeofguards stay for partial test doubles) — a rename on the concreteAdapterRegistrynow breaks the build instead of silently degrading every language to fail-open. Doctor's duplicateDoctorRegistrystructural type becomes aPickof the interface.getFactoryResultcloses the swallowed-loader-error hole:AdapterRegistry.getFactory'scatch { return undefined }collapsed a corrupt package, a broken export, and dynamic-loading-disabled into oneundefined, soprobe.factoryLoadErrorcould never fire. NowgetFactorydelegates togetFactoryResult(fail-open contract unchanged, one code path), the availability probe prefersgetFactoryResultwhen the registry offers it, and doctor reports the loader's real error message instead of "(the registry returned no factory)". The two remaining discovery-fallback swallows log warnings. New shared types:AdapterManifestEntry(the loader-manifest shape, finally disambiguated from the factory-declaredAdapterMetadata) andFactoryLoadResult.Test fixtures: the central
IAdapterRegistrymock provides fail-open-neutral defaults for the required members (mirroring the pre-typed fallback paths); the WithErrors/WithLanguages builders override the discovery members consistently.AdapterRegistry.getFactory/getFactoryResultget their first direct unit tests (registered / cached / loader-throws / dynamic-disabled).Deferred (noted on #435 at close)
Consolidating the three near-identical doctor fake-registry builders; doctor wording for
dynamicLoadingDisabled(production always enables dynamic loading); switching the #360 carve-out site togetFactoryMetadata; extending theisAdapterRegistryruntime guard to the new members.Verified locally: full build, lint, 222-file/4048-test unit suite, and a real
mcp-debugger doctor --jsonrun (the real registry now feedsgetFactoryResultthrough the shared probe).Closes #435.
🤖 Generated with Claude Code