You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Host selection picks the active host (Terminal/IDE/CI) through the same reflection family we just removed from the CLI in #392, one layer down in the build engine:
Assembly scan — AvailableTypes scans AppDomain.CurrentDomain.GetAssemblies().SelectMany(GetTypes) for Host subclasses (Host.Activation.cs:23-27).
Untyped construction — Activator.CreateInstance(hostType, nonPublic: true) (Host.Activation.cs:39).
The convention is also duplicated in tests (tests/Fallout.Common.Tests/CITest.cs:117-120 re-implements the same IsRunning{Name} reflection) — the tell-tale sign of a missing first-class contract.
Note
Hard constraint discovered while scoping:Host/Terminal compile into Fallout.Build, but the 11 CI hosts compile into Fallout.Common, which references Fallout.Build (Fallout.Common.csproj:13). The dependency runs Fallout.Common → Fallout.Build, so Hostcannot reference its CI subclasses at compile time. This is why the assembly scan exists, and it rules out a single central registry listing every host (would be a circular reference). The scan stays; only the name-convention reflection and untyped construction go.
Outcome
A typed, push-based registry replaces by-name reflection — the engine-side analog of #392's IFalloutCommand/dispatcher. No DI container (the build engine has none today, and host selection has no dependency graph — a container would be disproportionate); the registry is the "typed contract + registration" essence of #392, and HostRegistry.Register(...) doubles as a plugin-registration primitive for milestone #6.
internalsealedrecordHostRegistration(stringName,intPriority,// higher wins; base Terminal = 0 (guaranteed fallback)Func<bool>IsRunning,// detection BEFORE construction — no env side effectsFunc<Host>Create);// typed, compile-checked constructioninternalstaticclassHostRegistry{internalstaticvoidRegister(HostRegistrationregistration);internalstaticIReadOnlyList<HostRegistration>Registrations{get;}internalstaticIEnumerable<string>Names=>Registrations.Select(x =>x.Name);}
Each assembly registers its own hosts via a [ModuleInitializer] (Terminal/IDE in Fallout.Build; CI hosts in Fallout.Common), reusing the existing IsRunning* predicates as the Func<bool> (referenced, not reflected). Host.Default collapses to pure LINQ over the registry, preserving today's ordering (CI > IDE-terminal > base Terminal):
The [TypeConverter] for --host <name> (Host.Activation.cs:42-65) resolves by registered Name instead of Type.FullName.EndsWith, preserving the string-matching CLI surface.
This is non-breaking → targets main / target/2026 (unlike #392, no experimental/breaking-change). The entire public surface is preserved: Host, all subclasses, public new static T Instance, the public Terminal.IsRunningTerminal, the Host parameter, and --host <name> behavior. Everything changed is internal (AvailableTypes, Default, the reflection helpers).
Acceptance criteria
HostRegistration + HostRegistry replace AvailableTypes reflection, IsRunning(Type) by-name lookup, and Activator.CreateInstance in Host.Default/TypeConverter
Every built-in host (4 in Fallout.Build, 11 CI in Fallout.Common) is registered via per-assembly [ModuleInitializer]; no IsRunning{Name} reflection remains in the activation path
Host-selection ordering, --host <name> spellings, and the public surface (Host, subclasses, *.Instance, Terminal.IsRunningTerminal, Host parameter) are unchanged
FalloutBuild.HostNames + SchemaUtility read HostRegistry.Names; CITest.cs detection migrates off its duplicated reflection onto the registry
Incremental, mirroring the #392 stack — each PR builds green and diffs only against its parent:
Foundation — add HostRegistration/HostRegistry; register the 4 Fallout.Build hosts; rewrite Host.Default + TypeConverter to consult the registry with a transitional fallback to the old reflection for not-yet-registered types (the DelegateCommand analog). Add HostRegistryTests.
Register CI hosts — Fallout.Common[ModuleInitializer] registers all 11; every host now registry-resolved.
Delete the reflection — remove AvailableTypes, IsRunning(Type), CreateHost, and the transitional fallback; repoint HostNames/SchemaUtility to HostRegistry.Names.
Open decision (needs a call before coding): push-based registry (above, recommended — zero reflection, least host churn, plugin-registration primitive) vs. scan-discovered internal interface IHostDetector { bool IsRunning; int Priority; Host Create(); } (keeps the assembly scan, drop-in detectors, but ~15 new types and still Activator-constructs the detectors). Module initializers are new to this repo; the IHostDetector variant avoids them if that's preferred.
Sibling of #392 (CLI dispatch); same "reflection → typed contract" refactor applied to the engine's host activation.
Problem
Hostselection picks the active host (Terminal/IDE/CI) through the same reflection family we just removed from the CLI in #392, one layer down in the build engine:Host.DefaultcallsIsRunning(Type), which doeshostType.GetProperty($"IsRunning{hostType.Name}")and.NotNull()s the result (src/Fallout.Build/Host.Activation.cs:31-33). A renamed/missingIsRunning{Name}predicate is a runtime failure, not a compile error — the exact stringly-typed brittleness #Refactor CLI command dispatch: replace reflection-based partialProgramgod-class withIFalloutCommandtypes #392 eliminated.AvailableTypesscansAppDomain.CurrentDomain.GetAssemblies().SelectMany(GetTypes)forHostsubclasses (Host.Activation.cs:23-27).Activator.CreateInstance(hostType, nonPublic: true)(Host.Activation.cs:39).The convention is also duplicated in tests (
tests/Fallout.Common.Tests/CITest.cs:117-120re-implements the sameIsRunning{Name}reflection) — the tell-tale sign of a missing first-class contract.Note
Hard constraint discovered while scoping:
Host/Terminalcompile intoFallout.Build, but the 11 CI hosts compile intoFallout.Common, which referencesFallout.Build(Fallout.Common.csproj:13). The dependency runsFallout.Common → Fallout.Build, soHostcannot reference its CI subclasses at compile time. This is why the assembly scan exists, and it rules out a single central registry listing every host (would be a circular reference). The scan stays; only the name-convention reflection and untyped construction go.Outcome
A typed, push-based registry replaces by-name reflection — the engine-side analog of #392's
IFalloutCommand/dispatcher. No DI container (the build engine has none today, and host selection has no dependency graph — a container would be disproportionate); the registry is the "typed contract + registration" essence of #392, andHostRegistry.Register(...)doubles as a plugin-registration primitive for milestone #6.Each assembly registers its own hosts via a
[ModuleInitializer](Terminal/IDE inFallout.Build; CI hosts inFallout.Common), reusing the existingIsRunning*predicates as theFunc<bool>(referenced, not reflected).Host.Defaultcollapses to pure LINQ over the registry, preserving today's ordering (CI > IDE-terminal > baseTerminal):The
[TypeConverter]for--host <name>(Host.Activation.cs:42-65) resolves by registeredNameinstead ofType.FullName.EndsWith, preserving the string-matching CLI surface.This is non-breaking → targets
main/target/2026(unlike #392, noexperimental/breaking-change). The entire public surface is preserved:Host, all subclasses,public new static T Instance, the publicTerminal.IsRunningTerminal, theHostparameter, and--host <name>behavior. Everything changed isinternal(AvailableTypes,Default, the reflection helpers).Acceptance criteria
HostRegistration+HostRegistryreplaceAvailableTypesreflection,IsRunning(Type)by-name lookup, andActivator.CreateInstanceinHost.Default/TypeConverterFallout.Build, 11 CI inFallout.Common) is registered via per-assembly[ModuleInitializer]; noIsRunning{Name}reflection remains in the activation path--host <name>spellings, and the public surface (Host, subclasses,*.Instance,Terminal.IsRunningTerminal,Hostparameter) are unchangedFalloutBuild.HostNames+SchemaUtilityreadHostRegistry.Names;CITest.csdetection migrates off its duplicated reflection onto the registryHostRegistryTests: priority ordering, single-running detection,Terminalfallback,--hostresolution, unknown-name, dedup)Approach
Incremental, mirroring the #392 stack — each PR builds green and diffs only against its parent:
HostRegistration/HostRegistry; register the 4Fallout.Buildhosts; rewriteHost.Default+TypeConverterto consult the registry with a transitional fallback to the old reflection for not-yet-registered types (theDelegateCommandanalog). AddHostRegistryTests.Fallout.Common[ModuleInitializer]registers all 11; every host now registry-resolved.AvailableTypes,IsRunning(Type),CreateHost, and the transitional fallback; repointHostNames/SchemaUtilitytoHostRegistry.Names.CITest.cs:117-120ontoHostRegistry, killing the duplicated convention.Open decision (needs a call before coding): push-based registry (above, recommended — zero reflection, least host churn, plugin-registration primitive) vs. scan-discovered
internal interface IHostDetector { bool IsRunning; int Priority; Host Create(); }(keeps the assembly scan, drop-in detectors, but ~15 new types and stillActivator-constructs the detectors). Module initializers are new to this repo; theIHostDetectorvariant avoids them if that's preferred.Sibling of #392 (CLI dispatch); same "reflection → typed contract" refactor applied to the engine's host activation.