Skip to content

071: feature-flag decoupling — every flag combo generates a compiling solution#277

Open
StevenTCramer wants to merge 11 commits into
masterfrom
dev
Open

071: feature-flag decoupling — every flag combo generates a compiling solution#277
StevenTCramer wants to merge 11 commits into
masterfrom
dev

Conversation

@StevenTCramer

Copy link
Copy Markdown
Contributor

Summary

Completes task 071 (+ blocker 086). Every dotnet new timewarp-architecture flag 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)

Combo Errors
Default (all-on; counter + eventstream demos present) 0
NoGrpc / NoApi / NoWeb / NoYarp / NoPostgres 0 each
ApiOnly (--web false --grpc false --yarp false) — headless API 0
WebOnly (--api false --grpc false --yarp false --postgres false) 0

Real repo dev build 0/0 throughout.

Highlights

  • 086 root cause: the template engine treats #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).
  • Real bug found (api): web-spa's source-generator ProjectReference sat inside #if(api) — api-off apps lost all [Page]/[StateAccess] generation. Moved outside the conditional.
  • Cross-feature coupling fixed: TableHeader/Cell promoted from the weather-forecast feature to shared components/ (the superhero demo used them cross-feature).
  • Test infra decoupled from the SPA (web): new TestApiService in timewarp-testing implements foundation IApiService over HttpClient with ContractSerializationDefaults; all fixtures use it; the reflection hack into BaseApiService's private transport is deleted; dead IWebServerApiService registration dropped.
  • Security: direct MessagePack 2.5.302 pin — StreamJsonRpc (Aspire AppHost SDK) pulls vulnerable 2.5.192 (NU1902/NU1903) and only Aspire.Hosting.Yarp lifted it; yarp-off graphs need the pin.
  • De-flagged counter/eventstream (maintainer decision): template flags are the architecture axis (api/grpc/web/yarp/postgres); demos ship unconditionally and are removed by hand/agent — see new HowToRemoveDemoFeatures.md. All their #if regions 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

StevenTCramer and others added 11 commits July 8, 2026 10:46
🤖 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant