fix(quickfiler): call CreateCancellationToken first in QfcHomeController.Init (#839) - #876
Merged
drmoisan merged 16 commits intoSep 13, 2026
Merged
Conversation
…cellationToken defect
…d 2024-09-27 last-call-site finding
…ckFiler.Test baseline Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… direct spans (D16) The three Command Reference labels CMD-TEST-SCOPED, CMD-TEST-FULL and CMD-COVERAGE passed scripts/vscode/TaskMaster.cli.runsettings inline. That file's entire content is an MSTest parallelisation element (Workers 0, ClassLevel), and class-level parallelism fails three QfcInitEmailQueueZeroBatch tests by poisoning a cached Deedle static initializer. Removing the switch is exact CI parity: the repository MSTest workflow passes no settings file to vstest. New Decision D16 records the correction and its bounds, and the runsettings citation line is rewritten so the plan's no-TRX property rests on D6 rather than on a file the plan no longer passes. No acceptance condition, assertion, threshold or task ordering changed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ler.Init (#839) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
policy-audit PASS with the C# coverage row carrying an explicit FAIL verdict that is procedural and pre-existing (canonical artifact absent per issue 671; modified file below the 80 percent floor both before and after) and dispositioned non-blocking, since changed-line coverage is 100 percent and the per-file figure rose from 77.91 to 77.99. code-review PASS with 4 Low and 3 Info findings and no blocking finding. feature-audit PASS with all twelve acceptance criteria evaluated PASS against the anchored diff and the evidence artifacts. Co-Authored-By: Claude Opus 5 (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.
Suggested title
fix(quickfiler): call CreateCancellationToken first in QfcHomeController.Init (#839)
Summary
QfcHomeController.Init()now callsCreateCancellationToken()as its first statement, so the datamodel loader, the queue loader and the form-controller loader all receive a real, cancellable token instead ofdefault(CancellationToken)and a null source.Init_CreatesTokenSourceBeforeAnyLoaderObservesIt, that pins the ordering rule rather than merely the end state.Why
QfcHomeControllerhas two initialization paths. The asynchronous path (LaunchAsyncthenInitAsync) creates aCancellationTokenSourceand assigns both_tokenand_tokenSourcebefore any loader runs. The synchronous path (public constructor, thenInit(), thenRun()) never assigned either field. The factory that would do so,internal void CreateCancellationToken(), existed in the file with zero production callers.The consequence on the synchronous path was silent:
Init()handeddefault(CancellationToken)to the datamodel and queue loaders and a nullCancellationTokenSourceto the form-controller loader, soQfcFormController.LoadItemsreturned early on its null-source guard and nothing loaded, with no log line and no exception.The insertion point is load-bearing and is not simply "before the form-controller loader".
Init()passesthis.Tokento the datamodel loader and to the queue loader before it reaches the form-controller loader. An unassigned token field isdefault(CancellationToken), whoseCanBeCanceledis false, so inserting the call anywhere after the first statement would leave the datamodel and queue holding a token that can never be cancelled — a quieter version of the same defect. The regression test assertsCanBeCanceledon both of those tokens precisely to reject that weaker fix.Scope note: the path is currently unreachable in production.
RibbonController.LoadQuickFiler()is its only production caller, and a word-bounded search shows that method has no callers repository-wide. This PR repairs the defect where it lives; removing the dead path altogether is tracked separately (see Follow-ups).What Changed
Core fix (1 file, +1/-2)
QuickFiler/Controllers/QfcHomeController.cs: addedCreateCancellationToken();as the first statement ofInit(); deleted the dead//public QfcFormViewer FormViewer { get => _formViewer; }comment and its adjacent blank line. No executable line was removed and no statement was reordered.Tests (1 file, +71/-0)
QuickFiler.Test/Controllers/QfcHomeControllerTests.cs: addedInit_CreatesTokenSourceBeforeAnyLoaderObservesIt. It replaces all five loader delegates with Moq fakes, captures the source and the three tokens the loaders observe, and asserts with FluentAssertions that the captured source is non-null, that all three tokens equal that source'sToken, thatTokenSourceis the same instance, and that the datamodel and queue tokens haveCanBeCanceledtrue. It callsCleanup()afterwards to dispose the source. The existingInit_InitializesCorrectlyis byte-identical; the diff is a pure insertion with zero deleted lines.Docs and evidence
docs/features/active/2026-09-09-createcancellationtoken-has-no-production-caller-839/: issue record, spec, user story, research note, atomic plan, 30 evidence projections, and the three review artifacts.Architecture / How It Fits Together
No architectural change.
CreateCancellationToken()already existed as the single point that constructs theCancellationTokenSourceand caches itsToken; the asynchronous path andEfcHomeControllerboth already call it. This change makes the synchronous path follow the same convention, so both initialization paths now establish the cancellation source before any collaborator observes it.Disposal ownership was already correct and is unchanged:
Cleanup()disposes and nulls the source, andCleanupis passed to the form controller asparentCleanup, whichQfcFormController.Cleanup()invokes under afinally. No new disposal code was required. No interface member was added, removed or changed, and none of the 25 construction sites ofQfcHomeControllerchanged, because the fix acts inInit()rather than in a constructor.Verification
Completed
check: exit 0, 1624 files, zero unformatted.msbuild /t:Rebuild ... /p:EnableNETAnalyzers=true /p:EnforceCodeStyleInBuild=true: exit 0,0 Error(s).msbuild /t:Rebuild ... /p:TreatWarningsAsErrors=true: exit 0, zeroCS86xxdiagnostics.dotnet-coverage: exit 0, Total 1394, Passed 1394, Failed 0.Rebuildtarget and recorded 18Csctask invocations, which demonstrates the compiler actually ran rather than being skipped by MSBuild's up-to-date check, so neither diagnostic gate passed vacuously.spec.mdare checked off against evidence.Recommended
dotnet tool run csharpier check .msbuild TaskMaster.sln /t:Rebuild /m /p:Configuration=Debug "/p:Platform=Any CPU" /p:EnableNETAnalyzers=true /p:EnforceCodeStyleInBuild=truemsbuild TaskMaster.sln /t:Rebuild /m /p:Configuration=Debug "/p:Platform=Any CPU" /p:TreatWarningsAsErrors=trueBackward Compatibility / Migration Notes
None. No public API changed, no member was added or removed from any interface, and no file was renamed or moved. The only behavioural change is on a code path that has no reachable production caller, so no shipped behaviour changes.
Risks and Mitigations
Init()on the same controller would allocate a newCancellationTokenSourcewithout disposing the first. Mitigation: this mirrors the pre-existing pattern inInitAsyncandEfcHomeControllerand is confined to a path with no production caller; it is recorded as a Low finding in the code review rather than fixed here, to keep the diff minimal.Cleanup()outside afinally, so an assertion failure leaks the source and viewer for that test run. Mitigation: Low severity, test-only, and it disappears when the dead path is removed.3b6cd70b; the source change is one added line and two removed lines across two files.Review Guide
QuickFiler/Controllers/QfcHomeController.cs— three lines. Confirm the call is the first statement ofInit(), before theQfcDataModelLoadercall. This is the whole fix.QuickFiler.Test/Controllers/QfcHomeControllerTests.cs— the new test method. The assertions ondataModelToken.CanBeCanceledandqueueToken.CanBeCanceledare the ones that make the ordering, not just the existence, of the source observable.evidence/qa-gates/toolchain-final-pass.mdis the one-page index of the toolchain results.Mechanical noise: none. There are no renames, no moves and no generated files in this diff.
Follow-ups
RibbonController.LoadQuickFiler(),QfcHomeController.Init(),IQfcHomeController.Init(),CreateCancellationToken()andInit_InitializesCorrectly. It is deliberately not filed from this branch. When filing it, note that two files are namedIQfcHomeController.cs; the declaring one isQuickFiler/Controllers/IQfcHomeController.cs.scripts/vscode/TaskMaster.cli.runsettingsenables MSTest class-level parallelism (Workers0,ScopeClassLevel), which fails threeQfcInitEmailQueueZeroBatchTestsby poisoning a cached Deedle static initializer. CI passes no settings file and is unaffected, so the defect is invisible to CI while breaking the repository's own documented local coverage command. Both that file andscripts/vscode/Invoke-MSTestWithCoverage.ps1are outside this change's scope and were not modified; this warrants its own issue.GitHub Auto-close
Issue #839 was verified OPEN on GitHub before this bullet was emitted. Issue #810 appears in the context bundle's author-asserted list only because acceptance criterion AC6 names a test introduced by that issue; #810 is a distinct, already-closed bug and is deliberately not closed here. The bundle's third candidate,
#COMMIT-1, is not an issue reference at all — it is a false positive parsed from aCOMMIT-1-SHA:field in an evidence artifact — and is deliberately omitted.🤖 Generated with Claude Code