Skip to content

test(arch): match build symbols and reject inactive source - #1056

Merged
mforce merged 2 commits into
mainfrom
test/1052-guard-parse-options
Oct 4, 2026
Merged

mforce merged 2 commits into
mainfrom
test/1052-guard-parse-options

Conversation

@mforce

@mforce mforce commented Oct 4, 2026 •

Copy link
Copy Markdown
Owner

Code inside active framework conditionals escaped several source guards. The guards now share ModuleLedgerScanner.ParseOptions, with DEBUG selected only in Debug and RELEASE selected only when the compiler defines it. A new real-source tripwire rejects every region the shared parse leaves inactive, so choosing one configuration cannot hide an alternate branch.

Closes #1052

Change map

  • Architecture/ModuleLedgerScanner.cs: retain one shared symbol list and match its configuration symbol to the test build. MSBuild reports TRACE;DEBUG for Debug and TRACE;RELEASE for Release.
  • Architecture/SourcePreprocessorTests.cs: RealSourceTree_HasNoInactiveCode walks every source file using the existing exclusions and 400-file floor, then rejects Roslyn DisabledTextTrivia with file and line. Fixtures cover custom symbols, negation, else, both elif positions, an active framework region, and compiler-selected configuration.
  • Architecture/ModuleLedgerTests.cs: the existing DEBUG-only edge fixture now expects an edge only in a Debug build.
  • The original eight parse sites remain fixed: TenantBypass/GuardScanner.cs has three passes; Architecture/InsightsReadOnlyTests.cs has two; Architecture/ExportSnapshotSourceTests.cs, Eggs/EggLotLockSqlTests.cs, and Sales/TransactionDelegateShapeTests.cs have one each. Their scanner-specific conditional regressions remain.
  • docs/decisions/843-adapter-tiers.md and 846-adapter-reach-ratchet.md: replace the waived conditional-compilation limitation with the new configuration and inactive-region checks.

Parser inventory

The inventory came from git grep -n 'ParseText(' -- tests. All 16 calls use the shared options, including the two new preprocessor-test calls. There is one WithPreprocessorSymbols declaration and no copied list. The existing architecture scanners and FarmDirectoryCallerTests already used shared options.

ExportSnapshotSourceTests.FindViolations reads real ExportQueries.cs. Insights' semantic parser reads real Insights files and BusinessRecordOrdering.cs, alongside generated usings and fixture strings. Neither is fixture-only. The generated-string-only parser left alone is CompatibilityExceptionScanner's appended implicitUsings call, which already used shared options. The new configuration-branch fixture also uses them; no default-options fixture parser was found.

Mutations

All twelve scratch source mutations compiled in Release with zero warnings and errors, then were restored. The eight original mutants passed the exact base 7014566c tests built in Release and still failed their own assertions with both revised Debug and Release test assemblies. The tripwire was not substituted for those assertions.

Mutation Named guard assertion main Release revised Debug / Release Quoted failure
Handler queries EggLots.IgnoreQueryFilters() TenantBypassRealTreeTests.RealSourceTree_AllBypassesAreAllowListed green red / red unexcused bypass [IgnoreQueryFilters]
Existing allowlisted FIFO lock drops its AccountId predicate Same assertion, raw-SQL predicate pass green red / red raw-SQL row lock missing an AccountId predicate (M4)
Handler queries db.Users.ToList() without tenant comparison TenantBypassRealTreeTests.RealSourceTree_FilterFreeSetSitesAreStableAndClassified green red / red unclassified filter-free-set candidates with GuardProbeHandler.Handle(), db.Users, 9f5b2b0a
Insights read method calls db.SaveChangesAsync(ct) InsightsReadOnlyTests.ReadSession_UsesOnlyApprovedPersistenceOperations green red / red Microsoft.EntityFrameworkCore.DbContext.SaveChangesAsync(System.Threading.CancellationToken)
Account endpoint takes IReportQueries InsightsReadOnlyTests.Endpoints_DoNotUseTheReadSessionPortsDirectly green red / red Assert.False(NamesReadSessionPort(...), file) fails with src/Cluckwork.Api/Endpoints/Accounts/AccountEndpoints.cs
FIFO lock drops the Id tie-breaker EggLotLockSqlTests.EveryEggLotsForUpdate_OrdersByProductionDateThenId green red / red FOR UPDATE over EggLots must end ORDER BY "ProductionDate", "Id"
Add-order-item delegate returns false after its inner save TransactionDelegateShapeTests.NoExitBelowTheInnerSaveInAnyExecuteInTransactionDelegate green red / red exits the `ExecuteInTransactionAsync` delegate **after** its inner `SaveChangesAsync` , reported at AddOrderItemHandler.cs:231
Customers export arm reads requestDb.Customers ExportSnapshotSourceTests.EveryDatasetArm_NamesTheSnapshotFieldDirectly green red / red "customers" reads through [requestDb], not only activeDb

The eight original conditional fixtures failed first with old parser options. The new configuration fixture also failed first in Release with Expected: ["ReleaseOnly"], Actual: ["DebugOnly"].

Review regression probes

Before the fix, a scratch reflection test confirmed that the Release Infrastructure DLL contained the #if !DEBUG query while RealSourceTree_FilterFreeSetSitesAreStableAndClassified passed. All four probes below also passed that registry assertion at reviewed head 11a178be in Release.

Region around db.Users.ToList() main Release registry revised Debug revised Release
#if !DEBUG red tripwire red registry red
#else of #if DEBUG, with a safe nonempty Debug arm red tripwire red registry and tripwire red
#if FOO, product compiled with -p:DefineConstants=FOO green tripwire red tripwire red
#if RELEASE green tripwire red registry red

Registry failures quote unclassified filter-free-set candidates. Tripwire failures are on SourcePreprocessorTests.RealSourceTree_HasNoInactiveCode and quote source guards cannot inspect inactive C# regions, naming src/Cluckwork.Infrastructure/Repositories/GuardProbeHandler.cs and the inactive line. A configuration with no hidden region keeps the tripwire green while the visible bad query fails the registry guard. Fixtures also prove disabled text in both elif positions.

Registry and validation

No registry row or query-token hash changed. Both tenant registry files remain byte-identical to 7014566c. No real-source violation surfaced; the unmutated source tree has zero inactive regions in both configurations. Every scratch source edit and binary-presence fixture was removed.

Measured reviewed-head baseline: Application 712/712 in Debug and 712/712 in Release. Final full Application suites: 720/720 in Debug and 720/720 in Release. After restoring all mutations, the seven original real-source assertions plus eight preprocessor cases passed again, 15/15 per configuration.

dotnet build Cluckwork.sln -c Release passed with zero warnings and errors. All test commands set DOTNET_USE_POLLING_FILE_WATCHER=1 and ran sequentially. One custom-symbol control invocation had an internal CLR runner error; its repeated control and probe completed cleanly. git diff --check passed.

The initial revision also passed Domain 495/495, AppHost 10/10, and nine targeted Postgres integration cases through sg docker -c. Those unchanged suites were not repeated for this test-only follow-up; the full integration suite was not run.

Deslop and deferred cleanup

The complete diff was reviewed with deslop, thermo-nuclear review, and ponytail-review. The follow-up adds no symbol copy, parser wrapper, registry exception, dependency, or production change. No file crosses 1,000 lines. The original extraction removed the unused skipped-delegate list and redundant file alias.

Opus' optional shared parser helper and relocation of the policy from ModuleLedgerScanner are deferred. Migrating every parser and reshaping fixture setup would broaden this fix; the single options value and real-source tripwire provide the required behavior directly. The optional transaction-scan result reshaping is also deferred. Existing scanner-specific regressions remain because they prove all eight original call sites.

Updated reproduction scripts, full assertion text, diffs, TRX results, parser inventory, and local review evidence remain at /home/mforce/.cluckwork-slices/reviews/1052/revision2/; the original evidence is in its parent directory.

@mforce

mforce commented Oct 4, 2026

Copy link
Copy Markdown
Owner Author

Independent security + code-quality review (Claude Opus 5.5, high: security-review, thermo-nuclear, ponytail) at head 11a178be. Posted by the coordinator.

PR #1056 security review (Opus): head 11a178be, base 7014566c

Reviewer: Opus security seat, independent of Codex Astra. Checkout: /home/mforce/.cluckwork-slices/review-1056-opus, detached at 11a178be. Every mutation below was restored, and the tree was clean before the checkout was removed.

Bottom line

  • Product or security defect in shipped code: none. All 7 changed files are tests. src/ has no #if, #elif or #define today. No DefineConstants exist in any MSBuild file. Neither registry file changed (tenant-bypass-allowlist.json and filter-free-set-sites.tsv are not in the diff). No guard newly sees a real violation.
  • Guard defect: one, P1, CONFIRMED. The PR closes the #if NET10_0 hole. It also opens a new hole for code that only a Release build compiles, and production is built in Release.

Findings

F1. P1. The tenant guard is newly blind to #if !DEBUG and the #else of #if DEBUG, which is code that only production compiles. CONFIRMED

  • Where: tests/Cluckwork.Application.Tests/TenantBypass/GuardScanner.cs:158, :381, :817, plus the four other converted guards: EggLotLockSqlTests.cs:52, TransactionDelegateShapeTests.cs:140, InsightsReadOnlyTests.cs:83/:136, and ExportSnapshotSourceTests.cs:64. The root cause is the symbol list at Architecture/ModuleLedgerScanner.cs:41-43. It defines DEBUG and lacks RELEASE.

  • Defect: ModuleLedgerScanner.ParseOptions describes a Debug build. Production is built in Release: src/Cluckwork.Api/Dockerfile:44 runs dotnet publish -c Release, and CI builds and tests with --configuration Release. dotnet msbuild src/Cluckwork.Infrastructure -getProperty:DefineConstants returns TRACE;RELEASE for Release and TRACE;DEBUG for Debug. Before this PR, the five guards parsed with no symbols, so !DEBUG was true and the #else arm was active. Both were walked. After this PR, both are disabled trivia. The PR moved the blind spot; it did not remove it, and it moved it onto the code that ships.

  • Scenario: A developer adds a Release-only fast path in a repository, #if !DEBUG public object X() => db.Accounts.IgnoreQueryFilters()...; #endif. The production image contains an unallow-listed cross-tenant read. TenantBypassRealTreeTests stays green. The same applies to a raw-SQL FOR UPDATE without AccountId, a filter-free db.Users read, an Insights SaveChangesAsync, and the other guards.

  • Evidence: I inserted public object Leak() => db.Accounts.IgnoreQueryFilters().ToList(); into src/Cluckwork.Infrastructure/Identity/CredentialEpochVerifier.cs under each wrapper. For each, I ran the TenantBypass, ModuleLedger, AdapterTier and AdapterReach filters, 217 tests:

    Wrapper Scanner at main 7014566c Scanner at PR head 11a178be
    #if NET10_0 green (the test: source guards parse without preprocessor symbols, so #if code escapes them (incl. tenant-bypass) #1052 hole) RED, RealSourceTree_AllBypassesAreAllowListed
    #if CLUCKWORK_NOPE / #elif NET10_0_OR_GREATER (not run) RED (#elif handled)
    #if !DEBUG RED green: escapes
    #if DEBUG / #else RED green: escapes
    #if RELEASE green green: escapes (pre-existing)

    No other guard catches these cases. AdapterTierScanner's UndeclaredDefineConstants reads only MSBuild DefineConstants. RELEASE and DEBUG are SDK-implicit, so it never sees them.

  • Why this is not "retains current configuration": The PR body says the DEBUG selection is "unchanged". That is true of the list. It is not true of the five guards, which now lose visibility they had on main. The body does not say this.

  • Fix (boring, about 15 lines, closes the whole class): A single symbol set cannot be correct, because #if X … #else … always hides one arm. Fail closed instead. In the shared parse path, or as one real-tree test beside RealSourceTree_AllBypassesAreAllowListed, fail when any src tree parsed with ParseOptions contains SyntaxKind.DisabledTextTrivia. Today that is zero files, so the test costs nothing. It catches RELEASE, !DEBUG, #else, custom symbols and any future framework moniker, and the failure message can say "add the symbol or remove the conditional". Mutation-prove it with the #if !DEBUG and #if RELEASE cases above. A smaller alternative is to drop DEBUG and add RELEASE so the parse matches production. That fixes !DEBUG and #if RELEASE, but it still hides #if DEBUG arms and custom symbols. I do not recommend it alone.

  • Scope note: The same Debug-view blind spot already existed on main in ModuleLedgerScanner, AdapterReachScanner, AdapterTierScanner, CompatibilityExceptionScanner and FarmDirectoryCallerTests, which refactor(accounts): move the cross-farm account reads behind IFarmDirectory #1043 converted. Those cases are pre-existing and outside this slice. The disabled-trivia check fixes them too if it lives in shared code.

F2. P3. The fixtures pin only the NET10_0 slice. CONFIRMED

TenantBypassAllowListTests.cs:84-117, FilterFreeSetIdentityTests.cs:33-49 and the three other fixtures each prove that #if NET10_0 is seen. None proves that an inactive arm fails. This is why F1 passed review. Once F1's fail-closed check exists, one fixture with an inactive region covers every guard.

Checks that came back clean

  • Parse-options mismatch between the scan and the registry hash: none. All three GuardScanner walks (:158, :381, :817) use the same options. QuerySignature hashes NormalizeQueryText(scope) over DescendantTokens() of the tree that was just parsed, and disabled text is trivia, so it does not enter the hash. Nothing reparses for hashing. The registries are byte-identical and the real-tree classification tests are green.
  • All guards that parse real src: grep -rn -A2 "ParseText\|ParseSyntaxTree\|ParseCompilationUnit\|SyntaxTree.Create" tests/ tools/ lists 15 call sites. Every one passes ModuleLedgerScanner.ParseOptions. No other Roslyn parse API is used. No .cs files are tracked outside src/ and tests/.
  • #elif: handled (table above).
  • In-file #define X / #if X: handled natively by Roslyn.
  • Custom symbol from .csproj DefineConstants: caught by the existing AdapterTierScanner undeclared-constant check ([A] #514 slice 2: adapter-tier rows for the MCP and OpenIddict surfaces #843).
  • Files the scanner never walks: EnumerateSourceFiles skips only bin, obj and node_modules. Source-generator output is in-memory or under obj, and is not walked. The generators in use, GeneratedRegex and LoggerMessage-style, emit no EF queries, so I see no realistic path for generated tenant code. Accepted, not a finding.
  • The PR's mutations, reproduced by stripping the options from one GuardScanner call site at a time and rebuilding:
    • M1 :158 → UnlistedBypassInAnActiveFrameworkConditional_Fails RED.
    • M2 :381 → RawSqlLockInAnActiveFrameworkConditional_RequiresAnAccountPredicate RED.
    • M3 :817 → FilterFreeQueryInAnActiveFrameworkConditional_IsSeen RED.
    • Each mutation reddens exactly one test, so each call site has its own causal test.
  • Baseline: 154/154 green across TenantBypass, ExportSnapshot, InsightsReadOnly, EggLotLockSql, TransactionDelegateShape, ModuleLedger and AdapterTier. Application suite only, DOTNET_USE_POLLING_FILE_WATCHER=1.

security-review skill output

The skill's own git context resolved to the primary worktree at 05d7e7a7 and showed an empty diff. The brief forbids subagents, so I applied the skill's method by hand to the real diff (1056-11a178be.diff).

  • Result: no HIGH or MEDIUM vulnerabilities. All seven files are under tests/, so the skill's hard exclusion 11 applies: files used only for tests. The diff adds no production code path, input handling, auth logic, SQL or secrets.
  • The only security-relevant effect is indirect: the soundness of the tenant-isolation guard. That is F1, a weakened control rather than an exploitable defect today, because src/ has no conditional regions.

pstack:thermo-nuclear-code-quality-review (P3, slice only)

  • Q1. P3. Code-judo: one parse helper instead of 15 copies of the same decision. Every guard now repeats CSharpSyntaxTree.ParseText(text, ModuleLedgerScanner.ParseOptions, path). The PR fixes the bug by editing each copy, and each copy needs its own fixture to prove it, which is why there are five near-identical …InAnActiveFrameworkConditional… tests. Every guard already calls GuardScanner.EnumerateSourceFiles. Add a sibling, for example GuardScanner.ParseSource(text, path), that applies the options and the F1 disabled-trivia check. The parser policy then lives in one place, and one fixture proves it. That removes most of the per-guard fixtures and makes F1's fix land everywhere at once.
  • Q2. P3. Wrong home for the parse policy. ParseOptions belongs to ModuleLedgerScanner, so TenantBypass/GuardScanner.cs, Eggs/EggLotLockSqlTests.cs and Sales/TransactionDelegateShapeTests.cs now import Cluckwork.Application.Tests.Architecture for a parser constant. Move it next to EnumerateSourceFiles, or into a small shared SourceParse type.
  • Q3. P3. Mixed helper. TransactionDelegateShapeTests.FindViolations (:130-219) asserts parse errors and site-shape failures internally, returns violations, and passes inScope back through an out parameter. Return a record (Violations, InScope, ParseErrors, SiteShapeFailures) and assert in the callers.
  • File sizes: GuardScanner.cs goes from 1424 to 1425 lines. It was already over 1k, and this PR is not the cause. Every other file stays well under 1k.
  • No structural regression otherwise. Extracting the scan bodies into FindLockStatements and FindViolations is reasonable. Removing the unused skipped list and the RelativePath alias is a net win.

ponytail:ponytail-review (P3, slice only)

  • GuardScanner.cs:158,381,817 + 5 test files: reuse: 10 hand-repeated ParseText(…, ModuleLedgerScanner.ParseOptions, …) calls in this diff. Replace with one GuardScanner.ParseSource beside EnumerateSourceFiles.
  • TenantBypassAllowListTests.cs:83-117, FilterFreeSetIdentityTests.cs:33-49, EggLotLockSqlTests.cs:34-47, TransactionDelegateShapeTests.cs:108-129, ExportSnapshotSourceTests.cs:39-60, InsightsReadOnlyTests.cs:66-78: delete: once parsing goes through one helper, the six per-guard conditional fixtures collapse to one helper fixture plus one inactive-region fixture.
  • TransactionDelegateShapeTests.cs:130-131: shrink: out List<InScopeDelegate>. Return a tuple instead.
  • net: about -70 lines possible.

Verdict

Fix first: add a fail-closed check for disabled trivia in src, so that #if !DEBUG and #else, which only Release builds compile, cannot escape the tenant guard.

@mforce

mforce commented Oct 4, 2026

Copy link
Copy Markdown
Owner Author

Review of record (Codex Astra, xhigh) at head 11a178be. Posted by the coordinator.

PR #1056 review of record

Reviewed 11a178be3776c5eac1e407be2acbcded6553d146 against 7014566c, the supplied diff, #1052, #632's registry rules, #407's guard rules, and the #1043 m10 review and evidence. All findings refer to the reviewed head. I performed the review without delegation.

No current product behavior defect or reachable cross-farm disclosure was found. There is one confirmed regression in a security guard: Release-only queries that the base detects become invisible after this change.

Finding

F1. P2, CONFIRMED: preserve Release branch coverage when adopting the shared options

Location: tests/Cluckwork.Application.Tests/TenantBypass/GuardScanner.cs:817. The relevant shared value is at tests/Cluckwork.Application.Tests/Architecture/ModuleLedgerScanner.cs:41–43.

The newly supplied options unconditionally define DEBUG, including when the tests build in Release. Consequently, ScanFilterFreeSet now ignores a query under #if !DEBUG, or an equivalent #else branch. The compiler includes that query in Release. Previously, this parser had no symbols and therefore inspected !DEBUG. CI runs the Application tests in Release at .github/workflows/ci.yml:219, and the product Dockerfile publishes Release at src/Cluckwork.Api/Dockerfile:44.

Scenario: a handler adds public static object Handle(AppDbContext db) => db.Users.ToList(); under #if !DEBUG. The query has no AccountId predicate and no classification. It compiles into the Release application, yet RealSourceTree_FilterFreeSetSitesAreStableAndClassified passes. No alias, reflection-based query, or registry exception is needed.

Evidence: I inserted that method into src/Cluckwork.Infrastructure/Repositories/ReviewPreprocessorProbeHandler.cs. A scratch Application test confirmed that the Release DLL contained the method. Both that binary-presence test and the registry guard passed at head. Removing only "DEBUG", from the shared options made the registry guard fail with the probe's unclassified db.Users identity, signature 9f5b2b0a; the binary-presence test still passed. Restoring all seven changed test files to exact base 7014566c also made the registry guard reject the same Release-only query. All mutations were restored.

The fixed DEBUG selection predates this PR and is documented as an architecture-walk limitation in #846. The PR also acknowledges it. However, adopting it in the tenant scanner newly removes coverage that this security guard already had. This is a changed-path regression, not merely a request to broaden an unchanged architecture scanner.

Keep the shared options, but make their DEBUG selection match the build configuration and add Release regression coverage. Do not introduce a second symbol list.

Evidence: probe source, Release binary-presence test, head passes, removing DEBUG exposes the query, exact base rejects it, Release reproduction script, base-control script.

Parser coverage

I searched the whole checkout for ParseText( and ParseSyntaxTree, then checked CSharpParseOptions, SyntaxFactory, and ParseCompilationUnit references. There are 14 C# ParseText call sites, all using the shared options, and no ParseSyntaxTree calls. There is one WithPreprocessorSymbols declaration and no copied symbol list.

Parser Calls Input and disposition
GuardScanner 3 Real source; all three fixed
InsightsReadOnlyTests 2 Real endpoints and semantic compilation; both fixed
ExportSnapshotSourceTests 1 Real ExportQueries plus fixtures; fixed
EggLotLockSqlTests 1 Real source plus fixture; fixed
TransactionDelegateShapeTests 1 Real source plus fixture; fixed
ModuleLedgerScanner 1 Real source; already shared
AdapterReachScanner 1 Real source and fixtures; already shared
AdapterTierScanner 1 Real source and fixtures; already shared
CompatibilityExceptionScanner 2 Real source plus a separate generated-usings string; already shared
FarmDirectoryCallerTests 1 Real source and fixture; already shared

No default-options fixture parser was left behind. CompatibilityExceptionScanner's generated-usings call is the only wholly generated-string call. Insights' parser also reads real BusinessRecordOrdering.cs, so correctly participates in this fix. Inventory.

Independent mutations

I reproduced three of the PR's eight real-source mutations. The control preserves the new tests and extractions but removes exactly the eight added ParseOptions arguments. Each mutated production project compiled successfully.

Mutation No-options control Head
db.Users.ToList() under #if NET10_0 Guard passes Unclassified filter-free query fails
Existing FIFO lock drops AccountId under #if NET10_0 Guard passes Named raw-SQL predicate assertion fails with M4
AddOrderItem returns false after its inner save under #if NET10_0 Guard passes Named transaction-exit assertion fails

Three additional branch probes used the same filter-free query. #if !NET10_0 stays invisible at head as required. #if DEBUG is visible at head even with a Release-built product. #if !DEBUG is invisible at head despite compiling in Release, producing F1. The exact-base Release comparison was performed separately from the no-options control.

Reproduction script, results and assertion text, SQL mutation, transaction mutation. I inspected the author's remaining mutation evidence without claiming to have independently repeated those five cases.

Registry stability and fixture quality

Both tenant-bypass-allowlist.json and filter-free-set-sites.tsv are byte-identical between base and head. All 38 classified query identities remain valid, including their token hashes. The four TenantBypassRealTreeTests pass with both the old parser behavior and the head behavior on the unmodified tree. The 518 tracked C# files under src contain no conditional directives, so enabling these symbols exposes no previously invisible real code in the current tree. Registry hashes and counts, clean-tree inventory, old registry run, head registry run.

All eight new fixture cases fail after removing the options and pass with them. The selected run contains 28 cases: the eight new cases and 20 existing Insights write cases. The latter remain green in the control. Failures occur at the intended assertions, not compilation errors. In particular, the SQL fixture requires the M4 failure, so a stale allowlist row alone cannot satisfy it. Export and transaction fixtures retain safe alternate branches; filter-free and lock fixtures require actual occurrences. None passes vacuously. Fixtures without options, fixtures at head.

Code quality

The strict maintainability pass found no structural regression or file crossing 1,000 lines. Scanner extractions exercise the same implementation as the real-tree tests and retain their existing floors and shape assertions. GuardScanner was already 1,424 lines and grows by one. Reusing the shared configuration is the right structure; F1 concerns its value.

ponytail:ponytail-review: Lean already. Ship. This is the complexity-only result; F1 blocks the overall verdict. Net: -0 lines possible. Quality summary, details.

Nits

None.

Validation and cleanup

The clean head passed all 712 Application tests in Debug before mutation and all 712 in Release after restoration. Builds completed without warnings or errors. Every test command set DOTNET_USE_POLLING_FILE_WATCHER=1. No integration, Domain, or AppHost tests were run. Debug baseline, final Release run.

All review edits were restored; git status was clean and git diff --check passed. No commits, pushes, GitHub comments, or edits to another checkout were made. The assigned detached checkout was removed with the requested git worktree remove command; the report and evidence remain in the reviews directory.

Verdict: REQUEST CHANGES for F1, a confirmed P2 Release-only tenant-guard regression.

@mforce mforce changed the title test(arch): parse source guards with the build's preprocessor symbols test(arch): match build symbols and reject inactive source Oct 4, 2026
@mforce

mforce commented Oct 4, 2026

Copy link
Copy Markdown
Owner Author

Reply to Astra F1.

The fix is prepared in 0a99a44f; I am pushing immediately after these replies. The single ModuleLedgerScanner.ParseOptions value now selects DEBUG only in Debug and RELEASE only when the test compiler defines it. MSBuild confirms TRACE;DEBUG and TRACE;RELEASE for the two builds.

I reproduced your Release DLL presence proof with the old options: the unclassified method existed while the registry assertion passed. With the fix, the same #if !DEBUG query fails RealSourceTree_FilterFreeSetSitesAreStableAndClassified in Release. The new configuration fixture also failed first with Expected: ["ReleaseOnly"], Actual: ["DebugOnly"].

The full Application suite passes 720/720 in both Debug and Release. The original eight mutations still fail their own assertions in both builds. A new inactive-region tripwire rejects hidden branches and custom symbols. No registry row changed. The PR body has the updated mutation tables; reproduction scripts and TRX evidence are in /home/mforce/.cluckwork-slices/reviews/1052/revision2/.

@mforce

mforce commented Oct 4, 2026

Copy link
Copy Markdown
Owner Author

Reply to Opus F1 and F2.

The fix is prepared in 0a99a44f; I am pushing immediately after these replies. SourcePreprocessorTests.RealSourceTree_HasNoInactiveCode parses every source file under the shared options and fails on any DisabledTextTrivia, with file and line. It uses the existing walker and 400-file floor. The shared configuration symbol also matches the test build, including SDK-defined RELEASE.

Real-source probes for !DEBUG, the else arm of DEBUG, FOO, and RELEASE are now rejected in both configurations by the registry, the tripwire, or both. FOO was compiled into the product with a build argument. Fixtures prove inactive negation, else, both elif positions, and an unknown symbol, with an active-framework positive control. The eight original guard mutations still fail their own assertions in Debug and Release. Both full Application suites pass 720/720; the Release solution builds without warnings. The decision records now describe the stronger checks, and no registry changed.

The optional parser-helper migration and policy relocation are deferred and listed in the PR body. They would broaden the parser and fixture changes; this fix keeps one symbol list and adds the shared real-source tripwire directly. The optional transaction result reshaping is also deferred. Evidence is in /home/mforce/.cluckwork-slices/reviews/1052/revision2/.

@mforce

mforce commented Oct 4, 2026

Copy link
Copy Markdown
Owner Author

PR #1056 round 2 review of record

Reviewed head 0a99a44f4bfa4b453e9554aa539bd7e224d03750, the fix-only diff against 11a178be, and the complete PR diff. I read the worker's Astra reply and Opus reply. Review and mutations were performed independently, without delegation.

The original F1 is fixed. No PRODUCT defect or currently reachable cross-farm disclosure remains identified. One nonblocking P3 inventory limitation remains below; it does not reopen the original Release regression.

Reproduced mutations

Each source probe compiled in Release. I ran TenantBypassRealTreeTests and SourcePreprocessorTests separately against both Release and Debug test assemblies, one class per invocation. For FOO and the nested custom-symbol cases, the production project compiled with -p:DefineConstants=FOO, while the guards retained their normal configuration.

“Registry” means the named filter-free classification assertion rejected the unclassified query. “Tripwire” means RealSourceTree_HasNoInactiveCode rejected disabled text and named its file and line.

Query location Release rejection Debug rejection
#if !DEBUG Registry Tripwire
#else of #if DEBUG, with a nonempty safe Debug arm Registry and tripwire Tripwire
#if FOO Tripwire Tripwire
#if RELEASE Registry Tripwire
Active #elif NET10_0 following FOO Registry and tripwire Registry and tripwire
Inactive #elif FOO following NET10_0 Tripwire Tripwire
NET10_0 outer region, FOO inner region Tripwire Tripwire
FOO outer region, NET10_0 inner region Tripwire Tripwire
#if NET10_0 positive control Registry Registry

These were nine real-source probes and 36 separately checked class runs. An active bad query does not need the tripwire to fail: the original tenant assertion still rejects it. No unexpected failures occurred.

Configuration and coverage

ModuleLedgerScanner.cs:41–48 uses C# compile-time #if DEBUG / #elif RELEASE. It does not infer configuration from an output path or environment variable, and has no fallback to DEBUG. MSBuild reported TRACE;DEBUG, TRACE;RELEASE, and TRACE;REVIEW for the three tested configurations. I also built the Application tests in Review and ran the eight preprocessor cases: the fixture selected OtherConfiguration, confirming that neither standard symbol is invented.

All 16 ParseText calls use the shared options, and there is still only one WithPreprocessorSymbols declaration. No ParseSyntaxTree call was found. Every current real-source parser input is under src/, including Insights' BusinessRecordOrdering.cs companion. Other inputs are generated strings or temporary fixtures. There are no tracked C# files outside src/ and tests/, and no current external Compile includes.

The nested and elif probes confirm that DescendantTrivia finds their DisabledTextTrivia. Both registry files remain byte-identical to 7014566c. The restored tree has 518 tracked source files and passes the tripwire in both configurations.

Nonblocking finding

N1. P3, CONFIRMED. The tripwire inherits a narrower directory inventory than the transaction guard.

Location: tests/Cluckwork.Application.Tests/Architecture/SourcePreprocessorTests.cs:15. Its shared walker excludes node_modules at TenantBypass/GuardScanner.cs:1387, while the transaction guard's independent walker excludes only bin and obj at Sales/TransactionDelegateShapeTests.cs:57.

Scenario and evidence: I placed a scratch transaction handler under src/Cluckwork.Application/node_modules/. Its delegate returns false after SaveChangesAsync inside #if FOO, with a safe return in the alternate branch. The SDK's evaluated Compile items included the file, and a Release build with FOO succeeded. The transaction guard parsed the safe branch and passed; the tripwire skipped the file and passed. Removing the conditional while retaining the bad return made the transaction guard fail and name that file. The file and directory were removed afterward.

This is an inherited enumeration limit, not a newly lost protection. The directory is gitignored and contains no tracked product source, so it is nonblocking for this slice. Align the inventories if compiled C# is allowed there. The tripwire should be described as covering its enumerated source set, not every possible compiled file. No current product defect follows from this finding.

Code quality, P3 and slice-scoped

The thermo-nuclear review found no structural regression. The fix adds a 73-line test class, uses Roslyn directly, and retains one symbol policy. No file crosses 1,000 lines. A broad parser-wrapper migration, policy relocation, or transaction-result refactor is unnecessary for this fix; the existing caller-specific regressions still prove their individual parse sites. N1 is the only scope note.

Ponytail-review: Lean already. Ship. Net: -0 lines possible. No additional nits.

Validation and evidence

After restoring every mutation, 11 selected Application classes passed 146/146 in Release and 146/146 in Debug. The separate Review-configuration check passed 8/8, for 300 clean selected cases. These cover the new tripwire, module ledger, tenant registry and fixtures, export snapshot, Insights, lock SQL, transaction shape, adapter tiers, and farm-directory caller guard. Every invocation selected one class and set DOTNET_USE_POLLING_FILE_WATCHER=1. Builds had zero warnings and errors. No integration, Domain, or AppHost tests were run.

Evidence remains at /home/mforce/.cluckwork-slices/reviews/1056-astra-r2-evidence/: reproduce.py, results.json, individual mutation sources/logs/TRX files, scope-probe.py and its compiled-source control, validate.py, validation.json, inventory.json, and the layered quality reports under quality/. The review file is /home/mforce/.cluckwork-slices/reviews/1056-0a99a44f-astra.md.

All review edits were restored, git status is clean, and git diff --check passed. No product change, commit, or push was made. The assigned checkout will be removed after posting this comment.

Verdict: APPROVE with nonblocking P3 note N1; the original Release-only guard regression is fixed, and no PRODUCT defect is identified.

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.

test: source guards parse without preprocessor symbols, so #if code escapes them (incl. tenant-bypass)

1 participant