071: feature-flag decoupling — every flag combo generates a compiling solution#277
Open
StevenTCramer wants to merge 11 commits into
Open
071: feature-flag decoupling — every flag combo generates a compiling solution#277StevenTCramer wants to merge 11 commits into
StevenTCramer wants to merge 11 commits into
Conversation
🤖 Generated with [opencode](https://opencode.ai) Co-Authored-By: opencode <noreply@opencode.ai>
🤖 Generated with [opencode](https://opencode.ai) Co-Authored-By: opencode <noreply@opencode.ai>
🤖 Generated with [opencode](https://opencode.ai) Co-Authored-By: opencode <noreply@opencode.ai>
🤖 Generated with [opencode](https://opencode.ai) Co-Authored-By: opencode <noreply@opencode.ai>
…071) The dotnet template engine runs C# conditional-processing on every generated .cs file and treats `#if`/`#elif`/`#else`/`#endif` as directives even inside comments — stripping from the bogus token to the next `#endif` (or EOF), truncating the file and producing CS1513 in every generated app regardless of which feature flag is toggled. This blocked the 071 per-flag verification loop from running at all. Three files carried the landmine (all introduced 2026-07-02 by the region backfill, after the 071 matrix was last verified): - purpose-region-analyzer.cs (`#if false`) - web-spa/.../user-claims-base.cs (`#if false`) - aspire-app-host/program.cs (`#if blocks …`) Reworded each (comment-only edits; analyzers build 0/0). Verified by regenerating `--postgres false` — all three now come through intact. Rewrote 086 with the real root cause and moved it to done; cleared the blocker note on 071. Follow-up (a guard for this class of bug, allowing the cnd:noEmit escape) captured in done/086. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ng solutions postgres: guard TimeWarp.Architecture.Configuration global using (web-infrastructure) and TimeWarp.Architecture.Persistence global using (web-server) — both namespaces are declared only by postgres-excluded files. counter: guard StyleGuidePage's TriggerException demo (method + button; the razor side uses the @*#if*@ comment form the engine processes and Razor ignores), guard the CounterPage nav link, exclude counter-owned test files, and guard the test project's Counters global using. web-spa-integration-tests now defines api;counter;eventstream symbols so the real (all-on) build keeps guarded regions. EventStreamPage nav link and EventStreams/WeatherForecasts test usings guarded ahead of their flag runs. yarp: guard Aspire.Hosting.Yarp global usings in app-host; exclude CloneStateBehavior tests (yarp fixture) under !yarp; remove the blank line between web/yarp #if blocks in app-host program.cs (IDE2000 when yarp strips). Direct-pin MessagePack 2.5.302 in app-host: StreamJsonRpc (AppHost SDK) pulls vulnerable 2.5.192 (NU1902/NU1903) and only Aspire.Hosting.Yarp lifted it — flag-off graphs need the pin. Verified: dotnet new with each of --postgres/--counter/--yarp false (--foundationPackages false) builds 0 errors; real repo dev build 0/0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Root cause of the 116-error cascade: web-spa's reference to the source-generator project (timewarp-architecture-analyzers) sat INSIDE the #if(api) conditional in web-spa.csproj, so api-off apps lost [Page]/[StateAccess] generation entirely. Moved it outside the conditional (generators serve all of web-spa). Promoted TableHeader/Cell from features/weather-forecast/components to the shared components/ folder (TimeWarp.Architecture.Components): they are generic <th>/<td> wrappers used by BOTH the weather (api) and superhero (grpc) demos — cross-feature coupling, now shared properly. Feature _Imports.razor usings dropped (root _Imports already exposes the shared namespace). Guarded the api-only concrete TestServerApplication (timewarp-testing), the ApiServerApiService construction in YarpTestServerApplication (honest #else: NotSupported when the feature is excluded), and the IApiServerApiService DI registration in AspireSpaTestApplication. Verified: dotnet new --api false (--foundationPackages false) builds 0 errors. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Test infrastructure no longer borrows the SPA's HTTP client stack. New TestApiService in timewarp-testing implements the foundation IApiService over HttpClient with the seam options (ContractSerializationDefaults) and a fixed test bearer token — no ApiServerApiService, MockAccessTokenProvider, or WASM IAccessTokenProvider dependency in always-present test code. WebApiTestService now takes TestApiService and asserts on raw responses via its public GetHttpResponseMessage — deleting the reflection hack into the SPA BaseApiService's private transport (a hidden compile-time web dependency). All four fixtures (api, web, yarp, concrete api TestServerApplication) build their client the same one-line way; the yarp fixture's api-off NotSupported branch is gone. ApiServerTestConvention registers TestApiService against the aspire-launched api-server; its consumer swapped; the dead IWebServerApiService registration dropped. Remaining web guards: WASM-auth/Services/Types global usings in timewarp-testing, the yarp fixture's WebTestServerApplication member/param (conditional comma keeps every web/api combination syntactically valid), SpaTestApplication registration now web&&yarp, and the api test project's stale Architecture.Types using deleted. Verified: dotnet new --web false (--foundationPackages false) builds 0 errors; real repo build 0/0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Value assessment (maintainer decision): template flags are the architecture axis (api/grpc/web/yarp/postgres — which container apps and infra). counter and eventstream were demo-content flags: maximum preprocessor machinery to pre-delete teaching material users remove by reading it. eventstream alone was ~124 errors of guard-work. In an agent-assisted world "generate everything, tell the agent what to remove" beats 2^N preprocessor permutations for content — the compiler is the removal checklist and TWPA analyzers catch conventions. Removed both symbols and their exclude modifiers from template.json; unwrapped every #if(counter)/#if(eventstream) region (template engine evaluates undefined symbols as false — leftover regions would strip the demos, the 086 lesson); cleaned DefineConstants in web-spa and web-spa-integration-tests. Added HowToRemoveDemoFeatures.md (agent-driven removal guide). Filed follow-ups: 088 feature-isolation analyzer (replaces the flag loop as the coupling detector, runs every build), 089 DefineConstants↔template.json agreement analyzer for the surviving flags, 090 BaseApiService request-body seam-options fix (found while writing TestApiService). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <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.
Summary
Completes task 071 (+ blocker 086). Every
dotnet new timewarp-architectureflag combination now generates a compiling solution, verified by a full matrix; and the flag set itself was rationalized to the architecture axis.Verification matrix (pack → install → generate
--foundationPackages false→ build)--web false --grpc false --yarp false) — headless API--api false --grpc false --yarp false --postgres false)Real repo
dev build0/0 throughout.Highlights
#if-family tokens as directives even inside comments, truncating files to EOF (CS1513 in every generated app). Three files reworded; follow-up guard task filed (087).#if(api)— api-off apps lost all[Page]/[StateAccess]generation. Moved outside the conditional.components/(the superhero demo used them cross-feature).TestApiServicein timewarp-testing implements foundationIApiServiceover HttpClient withContractSerializationDefaults; all fixtures use it; the reflection hack intoBaseApiService's private transport is deleted; deadIWebServerApiServiceregistration dropped.Aspire.Hosting.Yarplifted it; yarp-off graphs need the pin.api/grpc/web/yarp/postgres); demos ship unconditionally and are removed by hand/agent — see newHowToRemoveDemoFeatures.md. All their#ifregions unwrapped (undefined symbols evaluate false and would strip the demos).Tests
In-proc integration tests pass (including all rewritten fixtures). 13 aspire-path tests fail only when Docker is unavailable (pre-existing DCP dependency; they die before any changed code runs).
Follow-ups filed
087 (
#if-in-comments template guard) · 088 (feature-isolation analyzer) · 089 (DefineConstants↔template.json agreement) · 090 (BaseApiService body serialization seam options)🤖 Generated with Claude Code