diff --git a/QuickFiler.Test/QuickFiler.Test.csproj b/QuickFiler.Test/QuickFiler.Test.csproj index db9a1488e..ad3ddafda 100644 --- a/QuickFiler.Test/QuickFiler.Test.csproj +++ b/QuickFiler.Test/QuickFiler.Test.csproj @@ -96,6 +96,7 @@ + @@ -225,6 +226,7 @@ + diff --git a/QuickFiler.Test/TestSupport/DedicatedWorkerThread.cs b/QuickFiler.Test/TestSupport/DedicatedWorkerThread.cs new file mode 100644 index 000000000..c3afc4237 --- /dev/null +++ b/QuickFiler.Test/TestSupport/DedicatedWorkerThread.cs @@ -0,0 +1,48 @@ +using System; +using System.Threading; + +namespace QuickFiler.Test.TestSupport +{ + /// + /// Runs a delegate on a dedicated, joined background thread for tests that must exercise a + /// thread-identity guard from a thread that is provably not the calling thread. + /// + /// + /// Issue #900 and issue #931: a Task.Run work item is not guaranteed to run on a + /// thread other than the caller's, so it cannot stand in for a different thread in a + /// thread-identity test. A thread this method constructs is + /// distinct from every live thread by construction. The untimed Join() is a + /// completion wait on one bounded synchronous call, not a sleep or a wall-clock wait, and + /// the waiting thread and the waited-for thread are never both thread-pool workers, so + /// the wait cannot starve the pool under parallel execution. The helper asserts nothing + /// itself: each test states its own distinctness precondition inside its delegate so that + /// a failure names the guard under test rather than the helper. + /// + internal static class DedicatedWorkerThread + { + /// + /// Runs on a dedicated background thread, joins it, and + /// returns the exception it threw, or when it completed + /// normally. + /// + internal static Exception Run(Action action) + { + Exception captured = null; + var thread = new Thread(() => + { + try + { + action(); + } + catch (Exception error) + { + captured = error; + } + }); + thread.IsBackground = true; + thread.Start(); + thread.Join(); + return captured; + } + } +} diff --git a/QuickFiler.Test/Viewers/BreadcrumbPopupBoundaryCoverageTests.cs b/QuickFiler.Test/Viewers/BreadcrumbPopupBoundaryCoverageTests.cs index c44830f19..b9038e24c 100644 --- a/QuickFiler.Test/Viewers/BreadcrumbPopupBoundaryCoverageTests.cs +++ b/QuickFiler.Test/Viewers/BreadcrumbPopupBoundaryCoverageTests.cs @@ -9,6 +9,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.Web.WebView2.Core; using Moq; +using QuickFiler.Test.TestSupport; using QuickFiler.Viewers; namespace QuickFiler.Test.Viewers @@ -49,14 +50,38 @@ public void Dispatcher_NullInputsAndThrowingSink_AreHandledByContract() .NotThrow(); } + /// + /// An owner-only dispatcher (null context) reached from a thread that is not its owner + /// must report a marshalling failure and must not run the action. + /// + /// + /// Issue #931: the worker is a dedicated thread created by + /// DedicatedWorkerThread.Run, never a Task.Run work item. A blocking wait on + /// a pool work item queued from a pool thread can run the delegate inline on the owner + /// thread, in which case the owner-thread-id branch of IsCurrentBoundary() admits + /// the call, the action runs, and the test fails spuriously. The delegate asserts it is + /// off the owner thread before it dispatches. The rejection path reports and returns a + /// completed task synchronously, so no task wait is needed. + /// [TestMethod] public void Dispatcher_OwnerOnlyWorker_ReportsWithoutRunningAction() { var errors = new List(); + int ownerThreadId = Environment.CurrentManagedThreadId; BreadcrumbUiDispatcher dispatcher = CreateOwnerOnlyDispatcher(errors.Add); int executions = 0; - Task dispatch = Task.Run(() => dispatcher.Dispatch(() => executions++)); - dispatch.GetAwaiter().GetResult(); + Exception captured = DedicatedWorkerThread.Run(() => + { + Environment + .CurrentManagedThreadId.Should() + .NotBe( + ownerThreadId, + "the dedicated worker thread must not be the owner thread the dispatcher " + + "was built for, or the rejection path would never be reached" + ); + dispatcher.Dispatch(() => executions++); + }); + captured.Should().BeNull(); executions.Should().Be(0); errors.Should().ContainSingle().Which.Message.Should().Contain("cannot marshal"); } diff --git a/QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.Part2.cs b/QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.Part2.cs new file mode 100644 index 000000000..0228268bc --- /dev/null +++ b/QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.Part2.cs @@ -0,0 +1,202 @@ +using System; +using System.Drawing; +using System.Reflection; +using System.Windows.Threading; +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using QuickFiler.Test.TestSupport; +using QuickFiler.Viewers; +using UtilitiesCS.OutlookObjects.Folder; + +namespace QuickFiler.Test.Viewers +{ + /// + /// Continuation partial of holding the + /// three cross-thread cases, each of which runs its guarded call on a dedicated thread + /// created by . The owner-thread admission cases, the + /// shared InertOperations factory and the nested helper types live in the primary + /// partial so that each file stays under the 500-line limit (issue #931). + /// + public sealed partial class ItemViewerBreadcrumbThreadAffinityTests + { + /// + /// A genuine cross-thread call must still fail fast with a diagnostic naming the operation, + /// and must not be an . + /// + /// + /// Issue #900: the worker is a dedicated thread created by DedicatedWorkerThread.Run, + /// never a Task.Run work item. A work item queued from a thread-pool thread lands on + /// that thread's local queue, and a blocking wait on it can run the delegate inline on the + /// constructing thread, in which case Dispatcher.CheckAccess() is true and the guard + /// never throws. A thread object this test constructs is never the object that constructed + /// the viewer, so the precondition asserted inside the delegate holds by construction under + /// any scheduler, including the Workers=0 class-level parallel run. The helper's + /// untimed Thread.Join() is a completion wait for one synchronous call on a dedicated + /// non-pool thread; unlike the previous blocking GetResult() shape it never parks a + /// thread-pool slot waiting on another thread-pool slot, so it adds no starvation risk under + /// parallel execution. BeOfType is an exact-type check, so the derived + /// is excluded by it as well as by the explicit + /// NotBeOfType that documents the intent. + /// + [TestMethod] + public void InitializeBreadcrumbPipeline_WorkerThread_ThrowsBoundaryDiagnostic() + { + // Arrange + using (var scope = new ViewerScope()) + { + BreadcrumbPopupUiOperations operations = InertOperations(); + var provider = new Mock(MockBehavior.Strict); + + // Act + Exception captured = DedicatedWorkerThread.Run(() => + { + bool isOwnerThread = scope.Viewer.UiDispatcher.CheckAccess(); + isOwnerThread + .Should() + .BeFalse( + "the dedicated worker thread must not be the thread that constructed " + + "the viewer, or the boundary assertion would pass vacuously" + ); + scope.Viewer.InitializeBreadcrumbPipeline(provider.Object, operations); + }); + + // Assert + captured + .Should() + .NotBeNull( + "a worker thread is not the thread that constructed the viewer, so the " + + "guard must throw rather than admit the call" + ); + captured.Should().BeOfType(); + captured.Message.Should().Contain("InitializeBreadcrumbPipeline"); + captured.Should().NotBeOfType(); + } + } + + /// + /// The same cross-thread contract on the three-argument ConfigureBreadcrumbDropDown + /// overload, whose guard is its first statement and therefore throws before any argument + /// check or control access. + /// + /// + /// Issue #900: the worker is a dedicated thread created by DedicatedWorkerThread.Run + /// rather than a Task.Run work item, for the reason given on + /// InitializeBreadcrumbPipeline_WorkerThread_ThrowsBoundaryDiagnostic: a pool work + /// item can be inlined onto the constructing thread, and a thread this test creates cannot. + /// The precondition inside the delegate proves the call is off the owning thread before the + /// guarded member runs. The helper's untimed Thread.Join() waits for one synchronous + /// call on a non-pool thread and parks no thread-pool slot, so it is safe under the + /// Workers=0 class-level parallel run. + /// + [TestMethod] + public void ConfigureBreadcrumbDropDown_WorkerThread_ThrowsBoundaryDiagnostic() + { + // Arrange + using (var scope = new ViewerScope()) + { + var host = new InertDropDownHost(); + + // Act + Exception captured = DedicatedWorkerThread.Run(() => + { + bool isOwnerThread = scope.Viewer.UiDispatcher.CheckAccess(); + isOwnerThread + .Should() + .BeFalse( + "the dedicated worker thread must not be the thread that constructed " + + "the viewer, or the boundary assertion would pass vacuously" + ); + scope.Viewer.ConfigureBreadcrumbDropDown( + host, + () => new Rectangle(0, 0, 10, 10), + () => new Rectangle(0, 0, 1920, 1040) + ); + }); + + // Assert + captured + .Should() + .NotBeNull( + "a worker thread is not the thread that constructed the viewer, so the " + + "guard must throw rather than admit the call" + ); + captured.Should().BeOfType(); + captured.Message.Should().Contain("ConfigureBreadcrumbDropDown"); + captured.Should().NotBeOfType(); + } + } + + /// + /// A viewer with no owning dispatcher stays inert, which is what keeps + /// FormatterServices.GetUninitializedObject-built viewers in other test files from + /// throwing. This is the only test covering the null-owner escape. + /// + /// + /// Issue #931: the guarded call is made from a dedicated thread created by + /// DedicatedWorkerThread.Run, and the delegate asserts through the owner captured + /// before the dispatcher is cleared that it is not on the owner thread. The call is + /// therefore off the owner thread unconditionally, so the test discriminates against the + /// pre-#781 context-reference guard: that guard would read the non-null captured context, + /// find the worker's null ambient context different from it, and reject the call, whereas + /// the null-owner escape admits it. Seeding first and repeating the same provider are + /// still required: a first-time initialization under a null ambient context would throw + /// at BreadcrumbUiDispatcher.CaptureCurrent() regardless of the guard, and only the + /// already-initialized early return can witness the escape. + /// + [TestMethod] + public void InitializeBreadcrumbPipeline_NullOwningDispatcher_DoesNotThrow() + { + // Arrange + using (var scope = new ViewerScope()) + { + BreadcrumbPopupUiOperations operations = InertOperations(); + var provider = new Mock(MockBehavior.Strict); + scope.Viewer.InitializeBreadcrumbPipeline(provider.Object, operations); + object before = scope.Viewer.BreadcrumbCoordinator; + Dispatcher owner = scope.Viewer.UiDispatcher; + owner.Should().NotBeNull("the viewer must own a dispatcher before it is cleared"); + ClearViewerDispatcher(scope.Viewer); + + // Act + Exception captured = DedicatedWorkerThread.Run(() => + { + bool isOwnerThread = owner.CheckAccess(); + isOwnerThread + .Should() + .BeFalse( + "the dedicated worker thread must not be the owner thread, or the " + + "null-owner escape would be witnessed on the owner thread and " + + "the test would pass vacuously" + ); + scope.Viewer.InitializeBreadcrumbPipeline(provider.Object, operations); + }); + + // Assert + captured + .Should() + .BeNull( + "a viewer with no owning dispatcher has no boundary to enforce and must " + + "stay inert" + ); + scope.Viewer.BreadcrumbCoordinator.Should().BeSameAs(before); + } + } + + /// + /// Assigns to the viewer's private owning-dispatcher field, asserting + /// the field still exists so a rename fails the test loudly rather than silently. + /// + private static void ClearViewerDispatcher(QuickFiler.ItemViewer viewer) + { + FieldInfo field = typeof(QuickFiler.ItemViewer).GetField( + "_uiDispatcher", + BindingFlags.Instance | BindingFlags.NonPublic + ); + field + .Should() + .NotBeNull("ItemViewer must still declare the private _uiDispatcher field"); + field.SetValue(viewer, null); + } + } +} diff --git a/QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.cs b/QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.cs index fe3094d7f..ca4003ba6 100644 --- a/QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.cs +++ b/QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Drawing; -using System.Reflection; using System.Threading; using System.Threading.Tasks; using System.Windows.Threading; @@ -25,9 +24,11 @@ namespace QuickFiler.Test.Viewers /// to the test's own thread and restored in a finally. Only the same-thread /// Dispatcher.Invoke(Action) fast path is used, which runs its callback inline at /// and needs no message pump. + /// The three cross-thread cases and ClearViewerDispatcher live in the continuation + /// partial file (issue #931). /// [TestClass] - public sealed class ItemViewerBreadcrumbThreadAffinityTests + public sealed partial class ItemViewerBreadcrumbThreadAffinityTests { /// /// The production shape of issue #781: the viewer is constructed inside a dispatcher @@ -196,155 +197,6 @@ public void ConfigureBreadcrumbDropDown_OwningThreadInsideDispatcherOperation_Do } } - /// - /// A genuine cross-thread call must still fail fast with a diagnostic naming the operation, - /// and must not be an . - /// - /// - /// Issue #900: the worker is a dedicated thread created by RunOnDedicatedWorkerThread, - /// never a Task.Run work item. A work item queued from a thread-pool thread lands on - /// that thread's local queue, and a blocking wait on it can run the delegate inline on the - /// constructing thread, in which case Dispatcher.CheckAccess() is true and the guard - /// never throws. A thread object this test constructs is never the object that constructed - /// the viewer, so the precondition asserted inside the delegate holds by construction under - /// any scheduler, including the Workers=0 class-level parallel run. The helper's - /// untimed Thread.Join() is a completion wait for one synchronous call on a dedicated - /// non-pool thread; unlike the previous blocking GetResult() shape it never parks a - /// thread-pool slot waiting on another thread-pool slot, so it adds no starvation risk under - /// parallel execution. BeOfType is an exact-type check, so the derived - /// is excluded by it as well as by the explicit - /// NotBeOfType that documents the intent. - /// - [TestMethod] - public void InitializeBreadcrumbPipeline_WorkerThread_ThrowsBoundaryDiagnostic() - { - // Arrange - using (var scope = new ViewerScope()) - { - BreadcrumbPopupUiOperations operations = InertOperations(); - var provider = new Mock(MockBehavior.Strict); - - // Act - Exception captured = RunOnDedicatedWorkerThread(() => - { - bool isOwnerThread = scope.Viewer.UiDispatcher.CheckAccess(); - isOwnerThread - .Should() - .BeFalse( - "the dedicated worker thread must not be the thread that constructed " - + "the viewer, or the boundary assertion would pass vacuously" - ); - scope.Viewer.InitializeBreadcrumbPipeline(provider.Object, operations); - }); - - // Assert - captured - .Should() - .NotBeNull( - "a worker thread is not the thread that constructed the viewer, so the " - + "guard must throw rather than admit the call" - ); - captured.Should().BeOfType(); - captured.Message.Should().Contain("InitializeBreadcrumbPipeline"); - captured.Should().NotBeOfType(); - } - } - - /// - /// The same cross-thread contract on the three-argument ConfigureBreadcrumbDropDown - /// overload, whose guard is its first statement and therefore throws before any argument - /// check or control access. - /// - /// - /// Issue #900: the worker is a dedicated thread created by RunOnDedicatedWorkerThread - /// rather than a Task.Run work item, for the reason given on - /// InitializeBreadcrumbPipeline_WorkerThread_ThrowsBoundaryDiagnostic: a pool work - /// item can be inlined onto the constructing thread, and a thread this test creates cannot. - /// The precondition inside the delegate proves the call is off the owning thread before the - /// guarded member runs. The helper's untimed Thread.Join() waits for one synchronous - /// call on a non-pool thread and parks no thread-pool slot, so it is safe under the - /// Workers=0 class-level parallel run. - /// - [TestMethod] - public void ConfigureBreadcrumbDropDown_WorkerThread_ThrowsBoundaryDiagnostic() - { - // Arrange - using (var scope = new ViewerScope()) - { - var host = new InertDropDownHost(); - - // Act - Exception captured = RunOnDedicatedWorkerThread(() => - { - bool isOwnerThread = scope.Viewer.UiDispatcher.CheckAccess(); - isOwnerThread - .Should() - .BeFalse( - "the dedicated worker thread must not be the thread that constructed " - + "the viewer, or the boundary assertion would pass vacuously" - ); - scope.Viewer.ConfigureBreadcrumbDropDown( - host, - () => new Rectangle(0, 0, 10, 10), - () => new Rectangle(0, 0, 1920, 1040) - ); - }); - - // Assert - captured - .Should() - .NotBeNull( - "a worker thread is not the thread that constructed the viewer, so the " - + "guard must throw rather than admit the call" - ); - captured.Should().BeOfType(); - captured.Message.Should().Contain("ConfigureBreadcrumbDropDown"); - captured.Should().NotBeOfType(); - } - } - - /// - /// A viewer with no owning dispatcher stays inert, which is what keeps - /// FormatterServices.GetUninitializedObject-built viewers in other test files from - /// throwing. This is the only test covering the null-owner escape. - /// - /// - /// Seeding first and repeating the same provider are both required: a worker thread's - /// ambient context is null, so a first-time initialization would throw at - /// BreadcrumbUiDispatcher.CaptureCurrent() regardless of the guard, and only the - /// already-initialized early return can witness the escape. It still discriminates, because - /// the pre-fix guard reads the non-null captured context and rejects the worker-thread call. - /// - [TestMethod] - public void InitializeBreadcrumbPipeline_NullOwningDispatcher_DoesNotThrow() - { - // Arrange - using (var scope = new ViewerScope()) - { - BreadcrumbPopupUiOperations operations = InertOperations(); - var provider = new Mock(MockBehavior.Strict); - scope.Viewer.InitializeBreadcrumbPipeline(provider.Object, operations); - object before = scope.Viewer.BreadcrumbCoordinator; - ClearViewerDispatcher(scope.Viewer); - - // Act - Action act = () => - Task.Run(() => - scope.Viewer.InitializeBreadcrumbPipeline(provider.Object, operations) - ) - .GetAwaiter() - .GetResult(); - - // Assert - act.Should() - .NotThrow( - "a viewer with no owning dispatcher has no boundary to enforce and must " - + "stay inert" - ); - scope.Viewer.BreadcrumbCoordinator.Should().BeSameAs(before); - } - } - /// /// Builds injected breadcrumb operations over a queue that is never installed as the ambient /// context, so posted work stays queued on this thread and nothing escapes the test. @@ -354,54 +206,6 @@ private static BreadcrumbPopupUiOperations InertOperations() => new BreadcrumbUiDispatcher(new DrainableSynchronizationContext(), _ => { }) ); - /// - /// Assigns to the viewer's private owning-dispatcher field, asserting - /// the field still exists so a rename fails the test loudly rather than silently. - /// - private static void ClearViewerDispatcher(QuickFiler.ItemViewer viewer) - { - FieldInfo field = typeof(QuickFiler.ItemViewer).GetField( - "_uiDispatcher", - BindingFlags.Instance | BindingFlags.NonPublic - ); - field - .Should() - .NotBeNull("ItemViewer must still declare the private _uiDispatcher field"); - field.SetValue(viewer, null); - } - - /// - /// Runs on a dedicated background thread, joins it, and returns - /// the exception it threw, or when it completed normally. - /// - /// - /// Issue #900: a Task.Run work item is not guaranteed to run on a thread other than - /// the caller's, so it cannot stand in for a different thread in a thread-identity test. A - /// thread this method constructs is distinct from every live thread by construction. The - /// untimed Join() is a completion wait on one bounded synchronous call, not a sleep or - /// a wall-clock wait, and the waiting thread and the waited-for thread are never both - /// thread-pool workers, so the wait cannot starve the pool under parallel execution. - /// - private static Exception RunOnDedicatedWorkerThread(Action action) - { - Exception captured = null; - var thread = new Thread(() => - { - try - { - action(); - } - catch (Exception error) - { - captured = error; - } - }); - thread.IsBackground = true; - thread.Start(); - thread.Join(); - return captured; - } - /// A drop-down host that records nothing and does nothing. private sealed class InertDropDownHost : IBreadcrumbDropDownHost { diff --git a/UtilitiesCS.Test/HelperClasses/FileInfoWrapper_Tests.cs b/UtilitiesCS.Test/HelperClasses/FileInfoWrapper_Tests.cs index eca477a3b..1aeb73590 100644 --- a/UtilitiesCS.Test/HelperClasses/FileInfoWrapper_Tests.cs +++ b/UtilitiesCS.Test/HelperClasses/FileInfoWrapper_Tests.cs @@ -12,6 +12,11 @@ namespace UtilitiesCS.Test.HelperClasses [TestClass] public class FileInfoWrapper_Tests { + // Issue #931: a rooted literal that is not expected to exist and does not point into the + // repository. The three metadata tests assert only path computations and Exists, so no + // file handle is opened and the outcome does not depend on any other process. + private const string FixturePath = @"C:\Repo\fixture.sln"; + [TestMethod] public void Constructor_WhenFileInfoIsNull_ThrowsArgumentNullException() { @@ -26,7 +31,7 @@ public void Constructor_WhenFileInfoIsNull_ThrowsArgumentNullException() public void Properties_ShouldMirrorWrappedFileInfo() { // Arrange - var file = GetSolutionFile(); + var file = new FileInfo(FixturePath); var wrapper = new FileInfoWrapper(file); // Assert @@ -42,7 +47,7 @@ public void Properties_ShouldMirrorWrappedFileInfo() public void ExplicitDirectoryCast_ShouldReturnWrappedContainingDirectory() { // Arrange - var wrapper = new FileInfoWrapper(GetSolutionFile()); + var wrapper = new FileInfoWrapper(new FileInfo(FixturePath)); // Act var directoryWrapper = (DirectoryInfoWrapper)wrapper; @@ -55,13 +60,26 @@ public void ExplicitDirectoryCast_ShouldReturnWrappedContainingDirectory() [TestMethod] public void OpenRead_ShouldReturnReadableStreamForWrappedFile() { - // Arrange - var wrapper = new FileInfoWrapper(GetSolutionFile()); + // Arrange: the sentinel is a stream this test opens and owns over the running test + // host's own loaded assembly image, read-only with read-write sharing, so no other + // process's handle can deny the open and no repository or temporary file is involved + // (issue #931). The seam's OpenRead member returns the concrete FileStream type, so a + // MemoryStream cannot stand in for it through the seam. + using var sentinel = new FileStream( + typeof(FileInfoWrapper_Tests).Assembly.Location, + FileMode.Open, + FileAccess.Read, + FileShare.ReadWrite + ); + var fileInfo = new Mock(MockBehavior.Strict); + fileInfo.Setup(x => x.OpenRead()).Returns(sentinel); + var wrapper = new FileInfoWrapper(fileInfo.Object); // Act - using var stream = wrapper.OpenRead(); + FileStream stream = wrapper.OpenRead(); // Assert + stream.Should().BeSameAs(sentinel); stream.CanRead.Should().BeTrue(); stream.Length.Should().BeGreaterThan(0); } @@ -70,7 +88,7 @@ public void OpenRead_ShouldReturnReadableStreamForWrappedFile() public void ToString_ShouldDelegateToWrappedFileInfo() { // Arrange - var file = GetSolutionFile(); + var file = new FileInfo(FixturePath); var wrapper = new FileInfoWrapper(file); // Act @@ -335,25 +353,5 @@ public void AccessControlAndLifecycleMethods_ShouldDelegateToWrappedIFileInfo() wrapper.SetAccessControl(fileSecurity); wrapper.ToString().Should().Be("wrapped-file"); } - - private static FileInfo GetSolutionFile() - { - var current = new DirectoryInfo(AppDomain.CurrentDomain.BaseDirectory); - - while (current is not null) - { - var solutionPath = Path.Combine(current.FullName, "TaskMaster.sln"); - if (File.Exists(solutionPath)) - { - return new FileInfo(solutionPath); - } - - current = current.Parent; - } - - throw new InvalidOperationException( - "The TaskMaster solution file could not be located from the test assembly path." - ); - } } } diff --git a/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/code-review.2026-09-29T20-15.md b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/code-review.2026-09-29T20-15.md new file mode 100644 index 000000000..c085c484a --- /dev/null +++ b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/code-review.2026-09-29T20-15.md @@ -0,0 +1,69 @@ +# Code Review: tests-depend-on-uncontrolled-environment (Issue #931) + +- Branch: bug/tests-depend-on-uncontrolled-environment-931 +- Head under review: ce744bbba3db4701e070785fd782c8b45289f2da +- Base: origin/main (c4ff0e2be0bc9c51acc43dacd2cc5954a448676c, merged at 55a50e922) +- Review timestamp: 2026-09-29T20-15 +- Files reviewed (full current content read; diff against origin/main read in full): `QuickFiler.Test/TestSupport/DedicatedWorkerThread.cs`, `QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.cs`, `QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.Part2.cs`, `QuickFiler.Test/Viewers/BreadcrumbPopupBoundaryCoverageTests.cs`, `QuickFiler.Test/QuickFiler.Test.csproj`, `UtilitiesCS.Test/HelperClasses/FileInfoWrapper_Tests.cs` +- Total blocking findings in this artifact: 0 +- Total non-blocking findings in this artifact: 8 (1 Low, 7 Informational) + +## Executive Summary + +Verdict: PASS. Zero blocking findings. + +The change is a disciplined test-only fix. Both affected `Task.Run` sites are replaced by a dedicated joined `Thread` whose delegate first asserts, inside the worker, that it is not the owner thread and only then exercises the guard; the returned exception is asserted null so a precondition failure cannot be masked. The helper's body is identical to the private helper PR #904 introduced, now shared from `QuickFiler.Test.TestSupport`, and all four dedicated-thread tests call it. The 490-line affinity class is split into two partials that preserve every test name, keep private helpers reachable across the partial declarations, and stay well under 500 lines. The file-handle tests no longer locate or open `TaskMaster.sln`: three use a rooted literal that is never opened, and the `OpenRead` test goes through the existing internal `IFileInfo` seam with a strict Moq mock returning a stream the test itself owns. + +Four negative controls demonstrate that each rewritten assertion is live. The code follows MSTest, Moq and FluentAssertions conventions, carries ``/`` on every changed member, and introduces no sleep, delay, timeout, retry or parallelism change. + +The findings below are observations for the record; none requires a change on this branch. CR-1 (Low) notes that the shared helper deliberately carries no null guard for its delegate, a trade the spec and plan D-1 made explicitly to keep the body identical and assertion-free. + +## Findings Table + +| Severity | File | Location | Finding | Recommendation | Rationale | Evidence | +|---|---|---|---|---|---|---| +| Low (Non-blocking) | `QuickFiler.Test/TestSupport/DedicatedWorkerThread.cs` | `Run(Action action)`, lines 28-46 | No `ArgumentNullException` guard on `action`. A null delegate would surface as a captured `NullReferenceException` returned to the caller rather than a fail-fast at the call site. | No change on this branch: spec Proposed Fix item 1 and plan D-1 require the body to be identical to the removed private helper and AC7 requires no assertion in the helper. If the helper is revised later, add `if (action == null) throw new ArgumentNullException(nameof(action));` before the thread is constructed. | The general policy prefers validating preconditions at entry; the deviation is deliberate, disclosed, and confined to an `internal` test helper whose four call sites all pass lambdas. | Plan D-1; spec AC7; all four call sites read. | +| Informational (Non-blocking) | `QuickFiler.Test/TestSupport/DedicatedWorkerThread.cs` | lines 33-40 | `catch (Exception error)` on the worker thread. | None. | The broad catch is the capture mechanism that returns the delegate's exception to the calling test for assertion; it is documented in the `` and is the only way an assertion thrown on a non-pool thread reaches the test. An unhandled exception on a dedicated thread would terminate the test host. | File read; M2 and M3 projections show the captured exception surfacing in the test's own assertion message. | +| Informational (Non-blocking) | `QuickFiler.Test/Viewers/BreadcrumbPopupBoundaryCoverageTests.cs` | `Dispatcher_OwnerOnlyWorker_ReportsWithoutRunningAction`, lines 66-87 | No `// Arrange` / `// Act` / `// Assert` markers. | None. | The file's other tests use the same compact unmarked style; the rewritten test keeps the file's existing style, which the general policy prefers over introducing a second style in one file. The Part2 tests, whose file uses markers, carry them. | File read. | +| Informational (Non-blocking) | `UtilitiesCS.Test/HelperClasses/FileInfoWrapper_Tests.cs` | `OpenRead_ShouldReturnReadableStreamForWrappedFile`, lines 68-73 | The sentinel is an OS file handle on the test host's own loaded assembly image (`typeof(FileInfoWrapper_Tests).Assembly.Location`), opened `FileMode.Open`, `FileAccess.Read`, `FileShare.ReadWrite`. | None. | The spec evaluated and rejected `MemoryStream` (the seam's `OpenRead` returns the concrete `FileStream` type) and a handle-free alternative (pipes are non-seekable, so `Length` throws). The chosen shape is read-shared, pre-existing for the whole run, creates and deletes nothing, and already occurs six times in the same file (lines 207-242). `Assembly.Location` being a readable on-disk path is an assumption the existing tests already rely on. | Spec "Chosen stream design and justification"; file read. | +| Informational (Non-blocking) | `UtilitiesCS.Test/HelperClasses/FileInfoWrapper_Tests.cs` | `FixturePath`, line 18 | Rooted Windows literal `C:\Repo\fixture.sln`. | None. | Tests are net48 / Windows-only and the same rooted style already appears at lines 105, 131 and 150. The leading comment states it is not expected to exist; the assertions mirror `file.Exists` and never read `Length`, so the outcome is identical whether or not the path exists. | File read; plan D-14. | +| Informational (Non-blocking) | `QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.Part2.cs` | `InitializeBreadcrumbPipeline_NullOwningDispatcher_DoesNotThrow`, line 158 | `owner.Should().NotBeNull(...)` is an Arrange-phase precondition beyond the spec's item 3 text. | None. | The added assertion makes a vacuous pass impossible if the viewer ever stops owning a dispatcher before the clear; it strengthens, not weakens, the test and is consistent with AC2. | File read. | +| Informational (Non-blocking) | `QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.Part2.cs` | lines 71-73 and 124-126 (pre-existing text, relocated) | `captured.Should().BeOfType()` followed by `captured.Should().NotBeOfType()` is logically redundant (`BeOfType` is an exact-type check). | None. | Pre-existing since PR #904 and documented in the `` as intent-declaring; relocation without change is what AC4 requires. | Diff read (removed and added hunks are identical apart from the helper name). | +| Informational (Non-blocking) | `QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.cs` | `using` block, lines 1-11 | After the split the primary partial still imports `System.Threading` and `System.Threading.Tasks`. | None. | Both remain used (`SynchronizationContext`, `Task.FromResult`); `System.Reflection` was correctly removed with `ClearViewerDispatcher` and re-declared in Part2. No unused directive was found in any of the five `.cs` files; the analyzer rebuild reports 0 warnings. | File read; `p4-t3-msbuild-analyzers` WARNINGS 0. | + +## Design and Correctness Review + +### Dedicated-thread helper + +- A `Thread` constructed by the test is by construction a different object from every live thread, so `Dispatcher.CheckAccess()` (thread-object identity) and `Environment.CurrentManagedThreadId` comparisons are decided independently of the scheduler. The untimed `Join()` is a completion wait on one bounded synchronous call; the waiting thread may be a pool thread but the waited-for thread never is, so the wait cannot starve the pool under Workers=0 / ClassLevel. The helper does not set apartment state, which is correct: no control is touched on the worker in any of the four call sites. +- Visibility of `captured` across threads: the write on the worker happens before `Join()` returns on the caller (`Thread.Join` is a full fence), so the read after `Join()` is well-defined. +- `IsBackground = true` ensures a hung delegate cannot keep the process alive past the test host's own shutdown; this matters only for a hang, which the blame collector would name. + +### Rewritten owner-only dispatcher test + +- `ownerThreadId` is captured at line 70 before `CreateOwnerOnlyDispatcher` at line 71, which passes `Environment.CurrentManagedThreadId` as the owner (line 158), so the precondition compares against the same value the dispatcher holds. The rejection path of `Dispatch` reports through the sink and returns synchronously, so no task wait is needed and the pre-existing `executions == 0` and single "cannot marshal" error assertions remain unchanged. Negative control M1 (`return true;` in the thread-id branch) fails exactly at the `executions` assertion, which confirms the guard is the deciding branch. + +### Rewritten null-owner test + +- Capturing `Dispatcher owner = scope.Viewer.UiDispatcher` before `ClearViewerDispatcher` and asserting `owner.CheckAccess()` is false inside the delegate is the correct way to prove the call is off the owner thread after the field is nulled. The remark now states the discrimination unconditionally and explains why seeding first is still required (first-time initialization throws at `CaptureCurrent()` under a null ambient context). Negative control M2 restores the pre-#781 context-reference throw and the test fails at the `captured` null assertion, confirming it discriminates. + +### File-handle tests + +- The wrapper's `OpenRead` contract is pure delegation, so instance identity (`BeSameAs(sentinel)`) is the complete contract; `CanRead` and `Length > 0` confirm the returned object is the live stream. Negative control M4 (second stream) fails at `BeSameAs`. The three metadata tests keep the public `FileInfoWrapper(FileInfo)` constructor and the explicit `DirectoryInfoWrapper` cast covered (the Cobertura class row for `FileInfoWrapper.cs` reads line-rate 1.000 in every document), which the mocked tests cannot reach. + +### Project file + +- Two `Compile Include` entries added in the neighbouring backslash form at lines 99 and 229. The analyzer `HintPath` version strings (MSTest.Analyzers 4.4.1, Meziantou.Analyzer 3.0.290) changed only through the merge from origin/main; the post-merge analyzer rebuild loaded the merged versions (36 and 32 occurrences in the log). + +### Policy conformance of the test code + +- MSTest / Moq / FluentAssertions: conformant throughout; no MSTest `Assert` introduced. +- Banned APIs in tests: none (`Thread.Sleep`, `Task.Delay`, wall-clock waits, `DateTime.Now` all absent from the diff). +- Parallel regime: no `[DoNotParallelize]`, no Workers/Scope change, no retry; `TaskMaster.runsettings` hash unchanged in every recorded run. +- Temporary files: none created, written or deleted. +- 500-line limit: 48 / 202 / 294 / 386 / 357 lines for the five `.cs` files. +- Documentation: XML `` and `` on the helper and each rewritten test; primary partial's class remark updated to point to the continuation file. + +## Summary + +The implementation matches the spec's Proposed Fix items 1-5 literally, the evidence demonstrates each rewritten assertion is live, and no code-quality issue rises above informational. Blocking findings: 0. Non-blocking findings: 8, none requiring remediation. diff --git a/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/baseline/coverage-baseline.md b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/baseline/coverage-baseline.md new file mode 100644 index 000000000..3287d6931 --- /dev/null +++ b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/baseline/coverage-baseline.md @@ -0,0 +1,82 @@ +# Coverage Baseline (P0-T11): repository-wide test and coverage run before any edit + +Timestamp: 2026-09-29T09-05 +Command: +- Route: COVERAGE-ROUTE: DIRECT (fixed by P0-T9, STALL-PROBE: REPRODUCES) +- Run payload (CMD-COVERAGE-DIRECT, STAGE baseline): dotnet-coverage collect --output coverage\baseline-931.cobertura.xml --output-format cobertura --settings coverage\effective-coverage-931.config -- vstest.console.exe <9 test assemblies> "/Settings:scripts\vscode\TaskMaster.cli.runsettings" /InIsolation "/TestCaseFilter:TestCategory!=LiveOutlook&FullyQualifiedName!~HelperClasses.ShellUtilities_Tests&FullyQualifiedName!~HelperClasses.ShellUtilitiesStatic_Tests&FullyQualifiedName!~HelperClasses.SysImageListHelperTests&FullyQualifiedName!~EmailIntelligence.OSBrowser_Tests" "/ResultsDirectory:coverage\test-results\931\baseline" "/Logger:trx;LogFileName=baseline-931.trx" "/Blame:CollectHangDump;TestTimeout=4min;HangDumpType=None" (vstest.console.exe resolved through vswhere; effective settings derived with ConvertTo-DerivedCoverageSettingsXml from coverage.config) +- Post payload (CMD-COVERAGE-POST, STAGE baseline, RAW True): Get-TrxRunSummary / Format-TrxRunSummary, ConvertTo-KoverageCoberturaXml, Assert-CoberturaLineCoverageThreshold, Assert-CoberturaBranchCoverageThreshold, Get-CoberturaFirstPartyCoverageReport, ConvertTo-JacocoPackageProjection, Assert-JacocoProjectionReconciliation (the runner's own helpers, dot-sourced) +- Filter applied by the route: the LiveOutlook category exclusion plus the four-class shell-icon exclusion shown above +EXIT_CODE: 0 + +Output Summary: +- COVERAGE-ROUTE: DIRECT +- RAW: True +- COLLECT_EXIT_CODE: 0 +- ASSEMBLY_COUNT: 9 +- ASSEMBLY: \QuickFiler.Test\bin\Debug\QuickFiler.Test.dll +- ASSEMBLY: \SVGControl.Test\bin\Debug\SVGControl.Test.dll +- ASSEMBLY: \Tags.Test\bin\Debug\Tags.Test.dll +- ASSEMBLY: \TaskMaster.Test\bin\Debug\TaskMaster.Test.dll +- ASSEMBLY: \TaskTree.Test\bin\Debug\TaskTree.Test.dll +- ASSEMBLY: \TaskVisualization.Test\bin\Debug\TaskVisualization.Test.dll +- ASSEMBLY: \ToDoModel.Test\bin\Debug\ToDoModel.Test.dll +- ASSEMBLY: \UtilitiesCS.Test\bin\Debug\UtilitiesCS.Test.dll +- ASSEMBLY: \VBFunctions.Test\bin\Debug\VBFunctions.Test.dll +- SEQUENCE_FILES: 0 +- TRX_PRESENT: True +- LINE-FLOOR: MET +- BRANCH-FLOOR: MET +- First-party coverage: lines 56085/65737 (85.32%), branches 13595/17052 (79.73%) +- FAILED-SET: (empty; no test failed) +- Branch decided: (a) exit 0 with both floors met; the task completes. + +SUMMARY-BEGIN +Test run outcome: Completed +Total 7320, executed 7320, passed 7320, failed 0. +Skipped 0, derived as total minus executed rather than reported by the test platform. +Figures reported verbatim by the test platform: error 0, timeout 0, aborted 0, notExecuted 0, inconclusive 0. +Failed tests: none +SUMMARY-END + +PROJECTION-BEGIN + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +PROJECTION-END + +coverage\baseline-931.jacoco.xml and coverage\baseline-931.cobertura.xml remain on disk, git-ignored, for P4-T8. The raw Cobertura document and the TRX are not copied into the feature folder. diff --git a/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/baseline/p0-t12-pre-edit-census.2026-09-29T09-05.md b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/baseline/p0-t12-pre-edit-census.2026-09-29T09-05.md new file mode 100644 index 000000000..ae1a54fcd --- /dev/null +++ b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/baseline/p0-t12-pre-edit-census.2026-09-29T09-05.md @@ -0,0 +1,408 @@ +# P0-T12 Pre-Edit Census + +Timestamp: 2026-09-29T09-05 +Command: CMD-CENSUS once per file (pwsh payload; case-sensitive non-overlapping occurrence counts with [regex]::Matches over Get-Content -Raw; LINES = Get-Content line count; SHA256 = Get-FileHash Hash) +EXIT_CODE: 0 + +Output Summary: +- N1: 1 (DISP return true; count) +- N2: 1 (DISP _ownerThreadId.HasValue count; at least 1) +- Every value in the pre column of the Token Census Expectations table matches (AFF LINES 490, Task.Run( 1, RunOnDedicatedWorkerThread 5, partial class 0; BND LINES 361, Task.Run( 1, .GetAwaiter() 4, ownerThreadId 0; FIW LINES 359, GetSolutionFile 5, TaskMaster.sln 1, FileMode. 12, FileAccess. 10, FixturePath 0; CSPROJ INCLUDE-PART2 0 and INCLUDE-HELPER 0; IVB System.Threading.SynchronizationContext.Current 0). +- The four SHA256 values of the existing Write Set files equal the P0-T4 PRE-EDIT-HASH values (AFF 8EBC19F8..., BND 8A8802E4..., FIW F11AA428..., CSPROJ 2AABD5BA...). TREE DIVERGED FROM PLAN did not fire. + +## QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.cs + + FILE QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.cs + TOKEN Task.Run( = 1 + TOKEN .GetAwaiter() = 1 + TOKEN DedicatedWorkerThread.Run( = 0 + TOKEN RunOnDedicatedWorkerThread = 5 + TOKEN ClearViewerDispatcher( = 2 + TOKEN partial class = 0 + TOKEN [TestMethod] = 7 + TOKEN action(); = 1 + TOKEN new Thread( = 1 + TOKEN IsBackground = true = 1 + TOKEN thread.Join(); = 1 + TOKEN Join( = 4 + TOKEN Join() = 4 + TOKEN using System.Reflection; = 1 + TOKEN using QuickFiler.Test.TestSupport; = 0 + TOKEN namespace QuickFiler.Test.TestSupport = 0 + TOKEN internal static class DedicatedWorkerThread = 0 + TOKEN internal static Exception Run(Action action) = 0 + TOKEN .Should() = 23 + TOKEN dedicated worker thread must not be = 2 + TOKEN distinct from every live thread by construction = 1 + TOKEN unconditionally = 0 + TOKEN owner.CheckAccess() = 0 + TOKEN .NotBe( = 0 + TOKEN ownerThreadId = 0 + TOKEN .BeNull( = 0 + TOKEN executions.Should().Be(0) = 0 + TOKEN cannot marshal = 0 + TOKEN GetSolutionFile = 0 + TOKEN TaskMaster.sln = 0 + TOKEN AppDomain = 0 + TOKEN File.Exists( = 0 + TOKEN File.Create = 0 + TOKEN File.WriteAll = 0 + TOKEN File.Delete = 0 + TOKEN Path.GetTemp = 0 + TOKEN FileMode. = 0 + TOKEN FileMode.Open = 0 + TOKEN FileAccess. = 0 + TOKEN FileAccess.Read = 0 + TOKEN FileShare.ReadWrite = 0 + TOKEN Assembly.Location = 0 + TOKEN FixturePath = 0 + TOKEN using var sentinel = new FileStream( = 0 + TOKEN BeSameAs(sentinel) = 0 + TOKEN .Returns(decoy) = 0 + TOKEN wrapper.OpenRead() = 0 + TOKEN stream.CanRead.Should().BeTrue() = 0 + TOKEN stream.Length.Should().BeGreaterThan(0) = 0 + TOKEN return true; = 0 + TOKEN _ownerThreadId.HasValue = 0 + TOKEN System.Threading.SynchronizationContext.Current = 0 + TOKEN Include="Viewers\ItemViewerBreadcrumbThreadAffinityTests.cs" = 0 + TOKEN Include="Viewers\ItemViewerBreadcrumbThreadAffinityTests.Part2.cs" = 0 + TOKEN Include="TestSupport\DedicatedWorkerThread.cs" = 0 + TOKEN Thread.Sleep = 0 + TOKEN Task.Delay = 0 + TOKEN [Timeout = 0 + TOKEN DoNotParallelize = 0 + TOKEN Retry = 0 + LINES = 490 + SHA256 = 8EBC19F829536957BEB0DAEC628929CA8DE3F11E10AA819DEA41362C6FCBB164 + +## QuickFiler.Test/Viewers/BreadcrumbPopupBoundaryCoverageTests.cs + + FILE QuickFiler.Test/Viewers/BreadcrumbPopupBoundaryCoverageTests.cs + TOKEN Task.Run( = 1 + TOKEN .GetAwaiter() = 4 + TOKEN DedicatedWorkerThread.Run( = 0 + TOKEN RunOnDedicatedWorkerThread = 0 + TOKEN ClearViewerDispatcher( = 0 + TOKEN partial class = 1 + TOKEN [TestMethod] = 5 + TOKEN action(); = 1 + TOKEN new Thread( = 0 + TOKEN IsBackground = true = 0 + TOKEN thread.Join(); = 0 + TOKEN Join( = 0 + TOKEN Join() = 0 + TOKEN using System.Reflection; = 1 + TOKEN using QuickFiler.Test.TestSupport; = 0 + TOKEN namespace QuickFiler.Test.TestSupport = 0 + TOKEN internal static class DedicatedWorkerThread = 0 + TOKEN internal static Exception Run(Action action) = 0 + TOKEN .Should() = 32 + TOKEN dedicated worker thread must not be = 0 + TOKEN distinct from every live thread by construction = 0 + TOKEN unconditionally = 0 + TOKEN owner.CheckAccess() = 0 + TOKEN .NotBe( = 0 + TOKEN ownerThreadId = 0 + TOKEN .BeNull( = 0 + TOKEN executions.Should().Be(0) = 1 + TOKEN cannot marshal = 1 + TOKEN GetSolutionFile = 0 + TOKEN TaskMaster.sln = 0 + TOKEN AppDomain = 0 + TOKEN File.Exists( = 0 + TOKEN File.Create = 0 + TOKEN File.WriteAll = 0 + TOKEN File.Delete = 0 + TOKEN Path.GetTemp = 0 + TOKEN FileMode. = 0 + TOKEN FileMode.Open = 0 + TOKEN FileAccess. = 0 + TOKEN FileAccess.Read = 0 + TOKEN FileShare.ReadWrite = 0 + TOKEN Assembly.Location = 0 + TOKEN FixturePath = 0 + TOKEN using var sentinel = new FileStream( = 0 + TOKEN BeSameAs(sentinel) = 0 + TOKEN .Returns(decoy) = 0 + TOKEN wrapper.OpenRead() = 0 + TOKEN stream.CanRead.Should().BeTrue() = 0 + TOKEN stream.Length.Should().BeGreaterThan(0) = 0 + TOKEN return true; = 1 + TOKEN _ownerThreadId.HasValue = 0 + TOKEN System.Threading.SynchronizationContext.Current = 0 + TOKEN Include="Viewers\ItemViewerBreadcrumbThreadAffinityTests.cs" = 0 + TOKEN Include="Viewers\ItemViewerBreadcrumbThreadAffinityTests.Part2.cs" = 0 + TOKEN Include="TestSupport\DedicatedWorkerThread.cs" = 0 + TOKEN Thread.Sleep = 0 + TOKEN Task.Delay = 0 + TOKEN [Timeout = 0 + TOKEN DoNotParallelize = 0 + TOKEN Retry = 0 + LINES = 361 + SHA256 = 8A8802E4855FAFBFC7734F6BA4F0F050E44F5D0B395AC095C236693A2518420C + +## UtilitiesCS.Test/HelperClasses/FileInfoWrapper_Tests.cs + + FILE UtilitiesCS.Test/HelperClasses/FileInfoWrapper_Tests.cs + TOKEN Task.Run( = 0 + TOKEN .GetAwaiter() = 0 + TOKEN DedicatedWorkerThread.Run( = 0 + TOKEN RunOnDedicatedWorkerThread = 0 + TOKEN ClearViewerDispatcher( = 0 + TOKEN partial class = 0 + TOKEN [TestMethod] = 8 + TOKEN action(); = 0 + TOKEN new Thread( = 0 + TOKEN IsBackground = true = 0 + TOKEN thread.Join(); = 0 + TOKEN Join( = 0 + TOKEN Join() = 0 + TOKEN using System.Reflection; = 0 + TOKEN using QuickFiler.Test.TestSupport; = 0 + TOKEN namespace QuickFiler.Test.TestSupport = 0 + TOKEN internal static class DedicatedWorkerThread = 0 + TOKEN internal static Exception Run(Action action) = 0 + TOKEN .Should() = 52 + TOKEN dedicated worker thread must not be = 0 + TOKEN distinct from every live thread by construction = 0 + TOKEN unconditionally = 0 + TOKEN owner.CheckAccess() = 0 + TOKEN .NotBe( = 0 + TOKEN ownerThreadId = 0 + TOKEN .BeNull( = 0 + TOKEN executions.Should().Be(0) = 0 + TOKEN cannot marshal = 0 + TOKEN GetSolutionFile = 5 + TOKEN TaskMaster.sln = 1 + TOKEN AppDomain = 1 + TOKEN File.Exists( = 1 + TOKEN File.Create = 0 + TOKEN File.WriteAll = 0 + TOKEN File.Delete = 0 + TOKEN Path.GetTemp = 0 + TOKEN FileMode. = 12 + TOKEN FileMode.Open = 12 + TOKEN FileAccess. = 10 + TOKEN FileAccess.Read = 10 + TOKEN FileShare.ReadWrite = 8 + TOKEN Assembly.Location = 6 + TOKEN FixturePath = 0 + TOKEN using var sentinel = new FileStream( = 0 + TOKEN BeSameAs(sentinel) = 0 + TOKEN .Returns(decoy) = 0 + TOKEN wrapper.OpenRead() = 2 + TOKEN stream.CanRead.Should().BeTrue() = 1 + TOKEN stream.Length.Should().BeGreaterThan(0) = 1 + TOKEN return true; = 0 + TOKEN _ownerThreadId.HasValue = 0 + TOKEN System.Threading.SynchronizationContext.Current = 0 + TOKEN Include="Viewers\ItemViewerBreadcrumbThreadAffinityTests.cs" = 0 + TOKEN Include="Viewers\ItemViewerBreadcrumbThreadAffinityTests.Part2.cs" = 0 + TOKEN Include="TestSupport\DedicatedWorkerThread.cs" = 0 + TOKEN Thread.Sleep = 0 + TOKEN Task.Delay = 0 + TOKEN [Timeout = 0 + TOKEN DoNotParallelize = 0 + TOKEN Retry = 0 + LINES = 359 + SHA256 = F11AA4284D370C78334B20C535F530A343006D9B83406ADC82D39CA1D25A01D3 + +## QuickFiler.Test/QuickFiler.Test.csproj + + FILE QuickFiler.Test/QuickFiler.Test.csproj + TOKEN Task.Run( = 0 + TOKEN .GetAwaiter() = 0 + TOKEN DedicatedWorkerThread.Run( = 0 + TOKEN RunOnDedicatedWorkerThread = 0 + TOKEN ClearViewerDispatcher( = 0 + TOKEN partial class = 0 + TOKEN [TestMethod] = 0 + TOKEN action(); = 0 + TOKEN new Thread( = 0 + TOKEN IsBackground = true = 0 + TOKEN thread.Join(); = 0 + TOKEN Join( = 0 + TOKEN Join() = 0 + TOKEN using System.Reflection; = 0 + TOKEN using QuickFiler.Test.TestSupport; = 0 + TOKEN namespace QuickFiler.Test.TestSupport = 0 + TOKEN internal static class DedicatedWorkerThread = 0 + TOKEN internal static Exception Run(Action action) = 0 + TOKEN .Should() = 0 + TOKEN dedicated worker thread must not be = 0 + TOKEN distinct from every live thread by construction = 0 + TOKEN unconditionally = 0 + TOKEN owner.CheckAccess() = 0 + TOKEN .NotBe( = 0 + TOKEN ownerThreadId = 0 + TOKEN .BeNull( = 0 + TOKEN executions.Should().Be(0) = 0 + TOKEN cannot marshal = 0 + TOKEN GetSolutionFile = 0 + TOKEN TaskMaster.sln = 0 + TOKEN AppDomain = 0 + TOKEN File.Exists( = 0 + TOKEN File.Create = 0 + TOKEN File.WriteAll = 0 + TOKEN File.Delete = 0 + TOKEN Path.GetTemp = 0 + TOKEN FileMode. = 0 + TOKEN FileMode.Open = 0 + TOKEN FileAccess. = 0 + TOKEN FileAccess.Read = 0 + TOKEN FileShare.ReadWrite = 0 + TOKEN Assembly.Location = 0 + TOKEN FixturePath = 0 + TOKEN using var sentinel = new FileStream( = 0 + TOKEN BeSameAs(sentinel) = 0 + TOKEN .Returns(decoy) = 0 + TOKEN wrapper.OpenRead() = 0 + TOKEN stream.CanRead.Should().BeTrue() = 0 + TOKEN stream.Length.Should().BeGreaterThan(0) = 0 + TOKEN return true; = 0 + TOKEN _ownerThreadId.HasValue = 0 + TOKEN System.Threading.SynchronizationContext.Current = 0 + TOKEN Include="Viewers\ItemViewerBreadcrumbThreadAffinityTests.cs" = 1 + TOKEN Include="Viewers\ItemViewerBreadcrumbThreadAffinityTests.Part2.cs" = 0 + TOKEN Include="TestSupport\DedicatedWorkerThread.cs" = 0 + TOKEN Thread.Sleep = 0 + TOKEN Task.Delay = 0 + TOKEN [Timeout = 0 + TOKEN DoNotParallelize = 0 + TOKEN Retry = 1 + LINES = 568 + SHA256 = 2AABD5BA7DAEE23D0BC656CE160E8298E9E658D8313B6C8FFCB0E31345AE2B4B + +## QuickFiler/Viewers/BreadcrumbUiDispatcher.cs + + FILE QuickFiler/Viewers/BreadcrumbUiDispatcher.cs + TOKEN Task.Run( = 0 + TOKEN .GetAwaiter() = 0 + TOKEN DedicatedWorkerThread.Run( = 0 + TOKEN RunOnDedicatedWorkerThread = 0 + TOKEN ClearViewerDispatcher( = 0 + TOKEN partial class = 0 + TOKEN [TestMethod] = 0 + TOKEN action(); = 2 + TOKEN new Thread( = 0 + TOKEN IsBackground = true = 0 + TOKEN thread.Join(); = 0 + TOKEN Join( = 0 + TOKEN Join() = 0 + TOKEN using System.Reflection; = 0 + TOKEN using QuickFiler.Test.TestSupport; = 0 + TOKEN namespace QuickFiler.Test.TestSupport = 0 + TOKEN internal static class DedicatedWorkerThread = 0 + TOKEN internal static Exception Run(Action action) = 0 + TOKEN .Should() = 0 + TOKEN dedicated worker thread must not be = 0 + TOKEN distinct from every live thread by construction = 0 + TOKEN unconditionally = 0 + TOKEN owner.CheckAccess() = 0 + TOKEN .NotBe( = 0 + TOKEN ownerThreadId = 7 + TOKEN .BeNull( = 0 + TOKEN executions.Should().Be(0) = 0 + TOKEN cannot marshal = 2 + TOKEN GetSolutionFile = 0 + TOKEN TaskMaster.sln = 0 + TOKEN AppDomain = 0 + TOKEN File.Exists( = 0 + TOKEN File.Create = 0 + TOKEN File.WriteAll = 0 + TOKEN File.Delete = 0 + TOKEN Path.GetTemp = 0 + TOKEN FileMode. = 0 + TOKEN FileMode.Open = 0 + TOKEN FileAccess. = 0 + TOKEN FileAccess.Read = 0 + TOKEN FileShare.ReadWrite = 0 + TOKEN Assembly.Location = 0 + TOKEN FixturePath = 0 + TOKEN using var sentinel = new FileStream( = 0 + TOKEN BeSameAs(sentinel) = 0 + TOKEN .Returns(decoy) = 0 + TOKEN wrapper.OpenRead() = 0 + TOKEN stream.CanRead.Should().BeTrue() = 0 + TOKEN stream.Length.Should().BeGreaterThan(0) = 0 + TOKEN return true; = 1 + TOKEN _ownerThreadId.HasValue = 1 + TOKEN System.Threading.SynchronizationContext.Current = 0 + TOKEN Include="Viewers\ItemViewerBreadcrumbThreadAffinityTests.cs" = 0 + TOKEN Include="Viewers\ItemViewerBreadcrumbThreadAffinityTests.Part2.cs" = 0 + TOKEN Include="TestSupport\DedicatedWorkerThread.cs" = 0 + TOKEN Thread.Sleep = 0 + TOKEN Task.Delay = 0 + TOKEN [Timeout = 0 + TOKEN DoNotParallelize = 0 + TOKEN Retry = 0 + LINES = 285 + SHA256 = 0764D49C8747276722853BF30FE32ACA133CB19A3D634A9CDA351217FD49017E + +## QuickFiler/Viewers/ItemViewer.Breadcrumb.cs + + FILE QuickFiler/Viewers/ItemViewer.Breadcrumb.cs + TOKEN Task.Run( = 0 + TOKEN .GetAwaiter() = 0 + TOKEN DedicatedWorkerThread.Run( = 0 + TOKEN RunOnDedicatedWorkerThread = 0 + TOKEN ClearViewerDispatcher( = 0 + TOKEN partial class = 1 + TOKEN [TestMethod] = 0 + TOKEN action(); = 0 + TOKEN new Thread( = 0 + TOKEN IsBackground = true = 0 + TOKEN thread.Join(); = 0 + TOKEN Join( = 0 + TOKEN Join() = 0 + TOKEN using System.Reflection; = 0 + TOKEN using QuickFiler.Test.TestSupport; = 0 + TOKEN namespace QuickFiler.Test.TestSupport = 0 + TOKEN internal static class DedicatedWorkerThread = 0 + TOKEN internal static Exception Run(Action action) = 0 + TOKEN .Should() = 0 + TOKEN dedicated worker thread must not be = 0 + TOKEN distinct from every live thread by construction = 0 + TOKEN unconditionally = 0 + TOKEN owner.CheckAccess() = 0 + TOKEN .NotBe( = 0 + TOKEN ownerThreadId = 0 + TOKEN .BeNull( = 0 + TOKEN executions.Should().Be(0) = 0 + TOKEN cannot marshal = 0 + TOKEN GetSolutionFile = 0 + TOKEN TaskMaster.sln = 0 + TOKEN AppDomain = 0 + TOKEN File.Exists( = 0 + TOKEN File.Create = 0 + TOKEN File.WriteAll = 0 + TOKEN File.Delete = 0 + TOKEN Path.GetTemp = 0 + TOKEN FileMode. = 0 + TOKEN FileMode.Open = 0 + TOKEN FileAccess. = 0 + TOKEN FileAccess.Read = 0 + TOKEN FileShare.ReadWrite = 0 + TOKEN Assembly.Location = 0 + TOKEN FixturePath = 0 + TOKEN using var sentinel = new FileStream( = 0 + TOKEN BeSameAs(sentinel) = 0 + TOKEN .Returns(decoy) = 0 + TOKEN wrapper.OpenRead() = 0 + TOKEN stream.CanRead.Should().BeTrue() = 0 + TOKEN stream.Length.Should().BeGreaterThan(0) = 0 + TOKEN return true; = 0 + TOKEN _ownerThreadId.HasValue = 0 + TOKEN System.Threading.SynchronizationContext.Current = 0 + TOKEN Include="Viewers\ItemViewerBreadcrumbThreadAffinityTests.cs" = 0 + TOKEN Include="Viewers\ItemViewerBreadcrumbThreadAffinityTests.Part2.cs" = 0 + TOKEN Include="TestSupport\DedicatedWorkerThread.cs" = 0 + TOKEN Thread.Sleep = 0 + TOKEN Task.Delay = 0 + TOKEN [Timeout = 0 + TOKEN DoNotParallelize = 0 + TOKEN Retry = 0 + LINES = 460 + SHA256 = 928E466A8C4C0D69E1CDF1577E3BF42BBC7E86BB6AA88C21B06F7A96C2F12CCF + diff --git a/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/baseline/p0-t13-phase0-commit.2026-09-29T09-07.md b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/baseline/p0-t13-phase0-commit.2026-09-29T09-07.md new file mode 100644 index 000000000..81cc0c577 --- /dev/null +++ b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/baseline/p0-t13-phase0-commit.2026-09-29T09-07.md @@ -0,0 +1,14 @@ +# P0-T13 Phase 0 Commit + +Timestamp: 2026-09-29T09-07 +Command: git add -- docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931; git commit -m "docs(931): phase 0 baseline evidence for the uncontrolled-environment test fix" -m "Co-Authored-By: Claude Opus 5.5 noreply@anthropic.com" -m "Claude-Session: https://claude.ai/code/session_01KNZiXntshsLY8vqqCHUvHm" -- docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931; git status --porcelain -- QuickFiler QuickFiler.Test UtilitiesCS UtilitiesCS.Test; git rev-parse HEAD (each a separate invocation, issued as git -C against the worktree) +EXIT_CODE: 0 (scoped to the porcelain span) + +Output Summary: +- COMMIT-EXIT: 0 (13 files changed, 772 insertions, 12 deletions; every staged path under docs/features/active/) +- PHASE0-HEAD: 42c963a139720daa3109b173cf5f99f05f6c5e3a +- BASE-SHA (P0-T3): 3c2fa88fdf31b11aee283c308c6e0c4df4521a34; PHASE0-HEAD differs from it. +- Scoped porcelain output (verbatim): empty (no output) +- No PreToolUse refusal occurred. +- Push: git push origin bug/tests-depend-on-uncontrolled-environment-931 advanced the remote branch 3c2fa88fd..42c963a13. +- This artifact is written after the commit it records and is committed by a later commit. diff --git a/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/baseline/p0-t2-mode-preconditions.2026-09-29T08-54.md b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/baseline/p0-t2-mode-preconditions.2026-09-29T08-54.md new file mode 100644 index 000000000..a565cf685 --- /dev/null +++ b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/baseline/p0-t2-mode-preconditions.2026-09-29T08-54.md @@ -0,0 +1,21 @@ +# P0-T2 Full-Bug Mode Preconditions + +Timestamp: 2026-09-29T08-54 +Command: pwsh -NoProfile -Command (read-only checks over FEATURE/issue.md and FEATURE/spec.md: exact-line match for the work-mode marker and the AC heading, the regex `^- \[[ x]\] AC([1-9]|1[0-9])\. ` over spec.md, Test-Path for FEATURE/user-story.md, and the backticked entries of the spec's `## Write Set` section) +EXIT_CODE: 0 + +Output Summary: +- WORKMODE-LINE: 1 (FEATURE/issue.md contains the exact line `- Work Mode: full-bug`, line 12) +- AC-HEADING: 1 (FEATURE/spec.md contains the exact heading line `## Acceptance Criteria`, line 234) +- AC-INVENTORY: 19 (the box-state-independent inventory regex matches 19 lines, spec lines 236 to 254) +- AC-UNCHECKED: 19 (every inventory line begins `- [ ] `; none is checked before execution) +- USER-STORY-EXISTS: False +- Spec Write Set code and project paths (six, matching this plan's Write Set; no other code path): + - QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.cs + - QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.Part2.cs + - QuickFiler.Test/TestSupport/DedicatedWorkerThread.cs + - QuickFiler.Test/Viewers/BreadcrumbPopupBoundaryCoverageTests.cs + - QuickFiler.Test/QuickFiler.Test.csproj + - UtilitiesCS.Test/HelperClasses/FileInfoWrapper_Tests.cs +- The remaining three Write Set entries are the three evidence directories under the feature folder (not code paths). +- Result: all five preconditions hold; MODE PRECONDITION FAILED did not fire. diff --git a/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/baseline/p0-t3-worktree-context.2026-09-29T08-55.md b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/baseline/p0-t3-worktree-context.2026-09-29T08-55.md new file mode 100644 index 000000000..1ea378419 --- /dev/null +++ b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/baseline/p0-t3-worktree-context.2026-09-29T08-55.md @@ -0,0 +1,47 @@ +# P0-T3 Worktree Context, Diff Anchors, Inherited Paths and Gate Readiness + +Timestamp: 2026-09-29T08-55 +Command: git rev-parse --abbrev-ref HEAD; git rev-parse HEAD; git fetch origin main; git merge-base HEAD origin/main; git diff --name-only MERGE-BASE...HEAD; git status --porcelain --untracked-files=all; git rev-parse --show-toplevel (each issued as git -C against the worktree); Read of artifacts/orchestration/orchestrator-state.json +EXIT_CODE: 0 + +Output Summary: +- BRANCH: bug/tests-depend-on-uncontrolled-environment-931 +- BASE-SHA: 3c2fa88fdf31b11aee283c308c6e0c4df4521a34 +- FETCH-EXIT: 0 +- MERGE-BASE: 177b6d78e1b2408e5aedbd794cef3aad6b7fb372 +- TOPLEVEL CONTAINS FEATURE: YES +- TOPLEVEL LEAF: agent-a36f285b19663c25b + +INHERITED-CLAUSE-A: (union of the anchored name-listing diff and the porcelain span, as observed) +- docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/issue.md (diff) +- docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/plan.2026-09-28T20-01.md (diff; porcelain M) +- docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/research/2026-09-28T20-15-tests-depend-on-uncontrolled-environment-research.md (diff) +- docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/spec.md (diff) +- docs/features/potential/promoted/2026-09-28-tests-depend-on-uncontrolled-environment.md (diff) +- .claude/agent-memory/atomic-executor/MEMORY.md (porcelain M) +- .claude/agent-memory/atomic-planner/MEMORY.md (porcelain M) +- .claude/agent-memory/atomic-planner/project_planner_mcp_validator_not_in_tool_surface.md (porcelain M) +- .claude/agent-memory/orchestrator/MEMORY.md (porcelain M) +- .claude/agent-memory/prd-feature/MEMORY.md (porcelain M) +- .claude/agent-memory/task-researcher/MEMORY.md (porcelain M) +- .claude/agent-memory/atomic-executor/index_artifact_hygiene_and_misc.md (porcelain ??) +- .claude/agent-memory/atomic-executor/project_loop_iteration_vs_single_artifact_glob_and_hash_anchor.md (porcelain ??) +- .claude/agent-memory/atomic-executor/project_outcome_branch_sets_keyed_on_route_miss_derived_flag_cases.md (porcelain ??) +- .claude/agent-memory/atomic-executor/project_pinned_target_source_prose_carries_census_tokens.md (porcelain ??) +- .claude/agent-memory/atomic-planner/project_931_uncontrolled_environment_tests_plan_seams.md (porcelain ??) +- .claude/agent-memory/orchestrator/delegation-prompt-needs-canonical-issue-and-branch-lines.md (porcelain ??) +- .claude/agent-memory/prd-feature/reference_ifileinfo_seam_filestream_sentinel_not_memorystream.md (porcelain ??) +- .claude/agent-memory/task-researcher/project_taskrun_triage_931.md (porcelain ??) +- Observation: the porcelain span also listed the two artifacts this run wrote under FEATURE/evidence/baseline/ in P0-T1 and P0-T2 (phase0-instructions-read.md and p0-t2-mode-preconditions.2026-09-29T08-54.md) and the plan's P0-T1/P0-T2 checkbox edits; those are this run's own feature-folder writes and are listed here only so the recorded porcelain is complete. None of the six Write Set code paths appears in the union. +- WRITE SET ALREADY DIRTY: not fired. + +Checkpoint (artifacts/orchestration/orchestrator-state.json, read-only): +- CHECKPOINT-EXISTS: YES +- CHECKPOINT-ISSUE-NUM: 931 +- CHECKPOINT-FEATURE-FOLDER: docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931 +- CHECKPOINT-ROUTE: large (route_id) +- CHECKPOINT-LIFECYCLE-READY: true +- PRE-IMPLEMENTATION GATE READY: YES +- HOOK-CHECKPOINT-NOTE: the executing PreToolUse hook reads the session-root path, which is absent; Phase 2 is held by the orchestrator. + +Acceptance: branch matches; BASE-SHA and MERGE-BASE are 40-character hexadecimal values; INHERITED-CLAUSE-A is present and lists no Write Set code path; TOPLEVEL CONTAINS FEATURE: YES; PRE-IMPLEMENTATION GATE READY is recorded (YES); the artifact carries no absolute filesystem path. diff --git a/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/baseline/p0-t4-channel-and-toolchain.2026-09-29T08-56.md b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/baseline/p0-t4-channel-and-toolchain.2026-09-29T08-56.md new file mode 100644 index 000000000..cadf6ccbf --- /dev/null +++ b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/baseline/p0-t4-channel-and-toolchain.2026-09-29T08-56.md @@ -0,0 +1,25 @@ +# P0-T4 Command Channel Probe and C# Toolchain Bootstrap + +Timestamp: 2026-09-29T08-56 +Command: pwsh -NoProfile -Command 'Set-Location -LiteralPath "WORKTREE"; Write-Output ("PROBE-OK " + (Test-Path -LiteralPath "TaskMaster.sln"))'; then, inside payloads: Get-FileHash -Algorithm SHA256 for the two runsettings files and the four existing Write Set files; `& pwsh -NoProfile -File (Join-Path (Get-Location).Path "scripts\vscode\Install-RepoDotNetSdk.ps1")`; Test-Path .dotnet-sdk\sdk\8.0.205; dotnet --version; dotnet tool restore; dotnet tool list --local; vswhere resolution of MSBuild.exe and vstest.console.exe; dotnet-coverage --version +EXIT_CODE: 0 + +Output Summary: +- Part 1: `PROBE-OK True` observed. CHANNEL: COMMAND +- RUNSETTINGS-HASH: 199408CA53CE4E12AE1A894FC66A0926124F3AC0D6447BD93B0C121338297FFA +- CLI-RUNSETTINGS-HASH: 98EF03A8D3B0EBB2ED7A765E3B5E1B58E774D20202DF2F294C03A7260B9CEF57 +- PRE-EDIT-HASH-AFF: 8EBC19F829536957BEB0DAEC628929CA8DE3F11E10AA819DEA41362C6FCBB164 +- PRE-EDIT-HASH-BND: 8A8802E4855FAFBFC7734F6BA4F0F050E44F5D0B395AC095C236693A2518420C +- PRE-EDIT-HASH-FIW: F11AA4284D370C78334B20C535F530A343006D9B83406ADC82D39CA1D25A01D3 +- PRE-EDIT-HASH-CSPROJ: 2AABD5BA7DAEE23D0BC656CE160E8298E9E658D8313B6C8FFCB0E31345AE2B4B +- NEW-FILES-ABSENT: True +- Install-RepoDotNetSdk.ps1: exit 0; printed "Installed repo-local .NET SDK 8.0.205 to \.dotnet-sdk." +- SDK-MARKER: YES +- dotnet --version: 8.0.205 (exit 0) +- dotnet tool restore: exit 0 ("Tool 'csharpier' (version '1.2.6') was restored." / "Restore was successful.") +- dotnet tool list --local: row `csharpier 1.2.6 csharpier` (manifest \dotnet-tools.json) +- MSBUILD-RESOLVED: YES (18\Community\MSBuild\Current\Bin\MSBuild.exe) +- VSTEST-RESOLVED: YES (18\Community\Common7\IDE\Extensions\TestPlatform\vstest.console.exe) +- dotnet-coverage --version: 18.10.0+f4cc39224845ffa74bf246c9da2399d50e5d6342 (exit 0; already installed, no install step needed) + +Acceptance: all nine conditions hold. diff --git a/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/baseline/p0-t5-nuget-restore.2026-09-29T08-56.md b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/baseline/p0-t5-nuget-restore.2026-09-29T08-56.md new file mode 100644 index 000000000..f87b3e2e2 --- /dev/null +++ b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/baseline/p0-t5-nuget-restore.2026-09-29T08-56.md @@ -0,0 +1,12 @@ +# P0-T5 NuGet Restore + +Timestamp: 2026-09-29T08-56 +Command: pwsh -NoProfile -File scripts\vscode\Invoke-Restore.ps1 (the payload resolved the absolute script path at run time through `Join-Path (Get-Location).Path`, created coverage\logs first, and set MSBUILDDISABLENODEREUSE to 1 before starting the script; output teed to the git-ignored coverage\logs\p0-t5.restore.log; the console view of the tee was limited to its last lines, which does not alter the log) +EXIT_CODE: 0 + +Output Summary: +- RESTORE_EXIT_CODE: 0 +- Restore log tail: "172 package(s) to packages.config projects"; "Build succeeded."; "0 Warning(s)"; "0 Error(s)" +- PACKAGE-DIR-COUNT: 172 + +Acceptance: EXIT_CODE 0 and PACKAGE-DIR-COUNT at least 1; both hold. diff --git a/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/baseline/p0-t6-csharpier-check.2026-09-29T08-56.md b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/baseline/p0-t6-csharpier-check.2026-09-29T08-56.md new file mode 100644 index 000000000..085c62399 --- /dev/null +++ b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/baseline/p0-t6-csharpier-check.2026-09-29T08-56.md @@ -0,0 +1,10 @@ +# P0-T6 Baseline Formatting State (CSharpier check) + +Timestamp: 2026-09-29T08-56 +Command: dotnet tool run csharpier check . (inside a pwsh payload whose first statement set the location to the worktree root) +EXIT_CODE: 0 + +Output Summary: +- Final summary line (verbatim): Checked 1623 files in 5928ms. +- CHECKED-FILES: 1623 +- No drift reported; FORMAT BASELINE NOT CLEAN did not fire. diff --git a/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/baseline/p0-t7-msbuild-analyzers.2026-09-29T08-59.md b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/baseline/p0-t7-msbuild-analyzers.2026-09-29T08-59.md new file mode 100644 index 000000000..29657bb4d --- /dev/null +++ b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/baseline/p0-t7-msbuild-analyzers.2026-09-29T08-59.md @@ -0,0 +1,23 @@ +# P0-T7 Baseline Analyzer Rebuild + +Timestamp: 2026-09-29T08-59 +Command: msbuild TaskMaster.sln /t:Rebuild /m /p:Configuration=Debug "/p:Platform=Any CPU" /p:EnableNETAnalyzers=true /p:EnforceCodeStyleInBuild=true (resolved through vswhere, plus /nodeReuse:false; CMD-REBUILD with TASKID p0-t7; file log coverage\logs\p0-t7.msbuild.log; console output discarded, the file log is the observed source) +EXIT_CODE: 0 + +Output Summary: +- ANALYZE-BASELINE-EXIT: 0 +- SKIP_CORECOMPILE_LINES: 0 +- QF_TEST_CSC_OUT_LINES: 2 +- UCS_TEST_CSC_OUT_LINES: 2 +- ZERO_ERRORS_LINES: 1 +- WARNINGS: 0 (ANALYZE-BASELINE-WARNINGS: 0; the first `N Warning(s)` match in the log, as CMD-REBUILD defines it) +- ERRORS: 0 +- WRITESET_DIAGNOSTIC_LINES: 0 + +Environment provisioning before the recorded run (git-ignored packages directory only; no tracked file changed): +- First attempt of the same command (log preserved as coverage\logs\p0-t7.attempt1.msbuild.log) exited 1 with 4 errors, all `CSC : error CS0006: Metadata file ... could not be found` for `..\packages\Meziantou.Analyzer.3.0.235\analyzers\dotnet\roslyn5.0\cs\Meziantou.Analyzer.dll` (VBFunctions, UtilitiesCS) and `..\packages\MSTest.Analyzers.4.4.0\analyzers\dotnet\cs\MSTest.Analyzers.dll` / `MSTest.Analyzers.CodeFixes.dll` (SVGControl.Test). +- Cause: pre-existing repository skew. The first-party csproj `` entries name Meziantou.Analyzer 3.0.235 and MSTest.Analyzers 4.4.0, while every packages.config and the csproj restore-check Import name 3.0.290 and 4.4.1, so a fresh worktree restore (P0-T5) installs only the newer folders. No Write Set file and no file this item edits is involved. +- Remedy: `nuget.exe install Meziantou.Analyzer -Version 3.0.235 -OutputDirectory packages -NonInteractive` and `nuget.exe install MSTest.Analyzers -Version 4.4.0 -OutputDirectory packages -NonInteractive` (both exit 0, served from the local NuGet cache). `git status --porcelain -- packages "*.csproj" "*/packages.config"` was empty afterwards. +- The recorded run above is the second invocation, after provisioning. + +Acceptance: EXIT_CODE 0; SKIP_CORECOMPILE_LINES 0; both test-project compiler echoes at least 1; ERRORS 0; WRITESET_DIAGNOSTIC_LINES 0. All five hold. diff --git a/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/baseline/p0-t8-msbuild-nullable.2026-09-29T08-59.md b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/baseline/p0-t8-msbuild-nullable.2026-09-29T08-59.md new file mode 100644 index 000000000..7cd58ea31 --- /dev/null +++ b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/baseline/p0-t8-msbuild-nullable.2026-09-29T08-59.md @@ -0,0 +1,20 @@ +# P0-T8 Baseline Nullable / Type-Check Rebuild + +Timestamp: 2026-09-29T08-59 +Command: msbuild TaskMaster.sln /t:Rebuild /m /p:Configuration=Debug "/p:Platform=Any CPU" /p:TreatWarningsAsErrors=true (resolved through vswhere, plus /nodeReuse:false; CMD-REBUILD with TASKID p0-t8; no Nullable property override, no incremental Build target; file log coverage\logs\p0-t8.msbuild.log; console output discarded, the file log is the observed source) +EXIT_CODE: 0 + +Output Summary: +- NULLABLE-BASELINE-EXIT: 0 +- SKIP_CORECOMPILE_LINES: 0 +- QF_TEST_CSC_OUT_LINES: 2 +- UCS_TEST_CSC_OUT_LINES: 2 +- ZERO_ERRORS_LINES: 1 +- WARNINGS: 0 (NULLABLE-BASELINE-WARNINGS: 0) +- ERRORS: 0 +- WRITESET_DIAGNOSTIC_LINES: 0 +- QF-TEST-DLL-EXISTS: True +- UCS-TEST-DLL-EXISTS: True +- The analyzer packages provisioned in P0-T7 (git-ignored) were present for this run; no provisioning occurred in this task. + +Acceptance: all six conditions hold. diff --git a/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/baseline/p0-t9-stall-probe.2026-09-29T09-00.md b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/baseline/p0-t9-stall-probe.2026-09-29T09-00.md new file mode 100644 index 000000000..fb9e155f1 --- /dev/null +++ b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/baseline/p0-t9-stall-probe.2026-09-29T09-00.md @@ -0,0 +1,23 @@ +# P0-T9 Stall Probe (four UtilitiesCS.Test shell-icon classes) + +Timestamp: 2026-09-29T09-00 +Command: vstest.console.exe UtilitiesCS.Test\bin\Debug\UtilitiesCS.Test.dll "/Settings:TaskMaster.runsettings" /InIsolation "/TestCaseFilter:FullyQualifiedName~HelperClasses.ShellUtilities_Tests|FullyQualifiedName~HelperClasses.ShellUtilitiesStatic_Tests|FullyQualifiedName~HelperClasses.SysImageListHelperTests|FullyQualifiedName~EmailIntelligence.OSBrowser_Tests" "/ResultsDirectory:coverage\test-results\931\p0-t9" "/Logger:trx;LogFileName=p0-t9.trx" "/Blame:CollectHangDump;TestTimeout=4min;HangDumpType=None" (resolved through vswhere; CMD-VSTEST with ASSEMBLY-UCS, FILTER-STALL, NAMES-NONE, TASKID p0-t9; console transcript teed to the git-ignored coverage\logs\p0-t9.vstest.log) +EXIT_CODE: 1 +ExpectedExitCode: 1 + +Output Summary: +- RUNSETTINGS-HASH-NOW: 199408CA53CE4E12AE1A894FC66A0926124F3AC0D6447BD93B0C121338297FFA (equals RUNSETTINGS-HASH from P0-T4) +- VSTEST_EXIT_CODE: 1 +- TRX_PRESENT: True +- SEQUENCE_FILES: 0 +- COLLECTOR_LINES: 0 +- COUNTERS total=23 executed=23 passed=22 failed=1 +- RESULT_COUNT: 23 +- MESSAGE GetFileIcon_WithUseFileType_ShouldReturnIconsForDirectoryAndFileExtension :: Test method UtilitiesCS.Test.HelperClasses.ShellUtilities_Tests.GetFileIcon_WithUseFileType_ShouldReturnIconsForDirectoryAndFileExtension threw exception: System.ArgumentException: Win32 handle that was passed to Icon is not valid or is the wrong type. +- Invocations: one (the probe was not re-run). + +STALL-PROBE: REPRODUCES +UCS-FILTERARG: "/TestCaseFilter:FullyQualifiedName!~HelperClasses.ShellUtilities_Tests&FullyQualifiedName!~HelperClasses.ShellUtilitiesStatic_Tests&FullyQualifiedName!~HelperClasses.SysImageListHelperTests&FullyQualifiedName!~EmailIntelligence.OSBrowser_Tests" +COVERAGE-ROUTE: DIRECT + +Derivation: the rule records CLEAR only when EXIT_CODE is 0, failed is 0 and SEQUENCE_FILES is 0; this run exited 1 with failed=1, so the value is REPRODUCES. On this run the four classes completed without a stall, and one shell-icon test failed on an invalid Win32 icon handle; either outcome is the environment-dependent shell-icon behaviour the rule excludes. The four excluded classes (HelperClasses.ShellUtilities_Tests, HelperClasses.ShellUtilitiesStatic_Tests, HelperClasses.SysImageListHelperTests, EmailIntelligence.OSBrowser_Tests) are a pre-existing local stall reproduced on main and are executed by CI (fact 16). diff --git a/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/baseline/phase0-instructions-read.md b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/baseline/phase0-instructions-read.md new file mode 100644 index 000000000..4a39cad6f --- /dev/null +++ b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/baseline/phase0-instructions-read.md @@ -0,0 +1,39 @@ +# Phase 0 Instructions Read (P0-T1) + +Timestamp: 2026-09-29T08-53 + +Policy Order: +1. CLAUDE.md +2. .claude/rules/general-code-change.md +3. .claude/rules/general-unit-test.md +4. .claude/rules/quality-tiers.md +5. .claude/rules/csharp.md +6. .claude/rules/tonality.md +7. .claude/rules/plan-acceptance-gates.md +8. .claude/skills/atomic-plan-contract/SKILL.md +9. .claude/skills/acceptance-criteria-tracking/SKILL.md +10. .claude/skills/evidence-and-timestamp-conventions/SKILL.md +11. docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/spec.md +12. docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/issue.md +13. docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/research/2026-09-28T20-15-tests-depend-on-uncontrolled-environment-research.md + +Command: pwsh -NoProfile -Command (Set-Location to the worktree root; one `@(Get-Content -LiteralPath ).Count` per file) +EXIT_CODE: 0 + +Files read (repository-relative path and line count, in the order read): + +- CLAUDE.md: 463 +- .claude/rules/general-code-change.md: 80 +- .claude/rules/general-unit-test.md: 105 +- .claude/rules/quality-tiers.md: 51 +- .claude/rules/csharp.md: 96 +- .claude/rules/tonality.md: 80 +- .claude/rules/plan-acceptance-gates.md: 257 +- .claude/skills/atomic-plan-contract/SKILL.md: 245 +- .claude/skills/acceptance-criteria-tracking/SKILL.md: 104 +- .claude/skills/evidence-and-timestamp-conventions/SKILL.md: 176 +- docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/spec.md: 279 +- docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/issue.md: 69 +- docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/research/2026-09-28T20-15-tests-depend-on-uncontrolled-environment-research.md: 320 + +Output Summary: all thirteen files were read in the order above; each line count is the PowerShell `Get-Content` line count of the file in this worktree. diff --git a/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/baseline/test-run-baseline.md b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/baseline/test-run-baseline.md new file mode 100644 index 000000000..053de75a4 --- /dev/null +++ b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/baseline/test-run-baseline.md @@ -0,0 +1,50 @@ +# Test Run Baseline (P0-T10): QuickFiler.Test and UtilitiesCS.Test, parallel regime, before any edit + +Timestamp: 2026-09-29T09-02 +Command: +- QuickFiler.Test: vstest.console.exe QuickFiler.Test\bin\Debug\QuickFiler.Test.dll "/Settings:TaskMaster.runsettings" /InIsolation "/ResultsDirectory:coverage\test-results\931\p0-t10-qf" "/Logger:trx;LogFileName=p0-t10-qf.trx" "/Blame:CollectHangDump;TestTimeout=4min;HangDumpType=None" (resolved through vswhere; test-case filter: NONE) +- UtilitiesCS.Test: vstest.console.exe UtilitiesCS.Test\bin\Debug\UtilitiesCS.Test.dll "/Settings:TaskMaster.runsettings" /InIsolation "/TestCaseFilter:FullyQualifiedName!~HelperClasses.ShellUtilities_Tests&FullyQualifiedName!~HelperClasses.ShellUtilitiesStatic_Tests&FullyQualifiedName!~HelperClasses.SysImageListHelperTests&FullyQualifiedName!~EmailIntelligence.OSBrowser_Tests" "/ResultsDirectory:coverage\test-results\931\p0-t10-ucs" "/Logger:trx;LogFileName=p0-t10-ucs.trx" "/Blame:CollectHangDump;TestTimeout=4min;HangDumpType=None" (resolved through vswhere) +- Runsettings file: TaskMaster.runsettings (repository root; Workers 0, Scope ClassLevel). Isolation switch: /InIsolation. +- UtilitiesCS.Test filter source: UCS-FILTERARG from P0-T9 (STALL-PROBE: REPRODUCES). Excluded classes: HelperClasses.ShellUtilities_Tests, HelperClasses.ShellUtilitiesStatic_Tests, HelperClasses.SysImageListHelperTests, EmailIntelligence.OSBrowser_Tests (pre-existing local shell-icon stall reproduced on main; executed by CI). +EXIT_CODE: 0 +UCS-VSTEST-EXIT: 0 + +Output Summary: + +QuickFiler.Test (TASKID p0-t10-qf): +- RUNSETTINGS-HASH-NOW: 199408CA53CE4E12AE1A894FC66A0926124F3AC0D6447BD93B0C121338297FFA +- COUNTERS total=1468 executed=1468 passed=1468 failed=0 +- RESULT_COUNT: 1468 +- SEQUENCE_FILES: 0 +- COLLECTOR_LINES: 0 +- TRX_PRESENT: True +- RESULT ConfigureBreadcrumbDropDown_OwningThreadInsideDispatcherOperation_DoesNotThrow = Passed +- RESULT InitializeBreadcrumbPipeline_ConstructedInsideDispatcherOperation_SucceedsUnderDifferentAmbientContext = Passed +- RESULT InitializeBreadcrumbPipeline_OwningThreadDifferentPlainContext_DoesNotThrow = Passed +- RESULT InitializeBreadcrumbPipeline_NullOwningDispatcher_DoesNotThrow = Passed +- RESULT Dispatcher_OwnerOnlyWorker_ReportsWithoutRunningAction = Passed +- RESULT InitializeBreadcrumbPipeline_WorkerThread_ThrowsBoundaryDiagnostic = Passed +- RESULT ConfigureBreadcrumbDropDown_WorkerThread_ThrowsBoundaryDiagnostic = Passed +- RESULT InitializeBreadcrumbPipeline_OwningThreadNullAmbientContext_DoesNotThrow = Passed +- MESSAGE lines: none +- BASELINE-FAILED-QF: NONE + +UtilitiesCS.Test (TASKID p0-t10-ucs): +- RUNSETTINGS-HASH-NOW: 199408CA53CE4E12AE1A894FC66A0926124F3AC0D6447BD93B0C121338297FFA +- COUNTERS total=4922 executed=4922 passed=4922 failed=0 +- RESULT_COUNT: 4922 +- SEQUENCE_FILES: 0 +- COLLECTOR_LINES: 0 +- TRX_PRESENT: True +- RESULT StreamAndCopyMethods_ShouldDelegateToWrappedIFileInfo = Passed +- RESULT ExplicitDirectoryCast_ShouldReturnWrappedContainingDirectory = Passed +- RESULT AccessControlAndLifecycleMethods_ShouldDelegateToWrappedIFileInfo = Passed +- RESULT ToString_ShouldDelegateToWrappedFileInfo = Passed +- RESULT PropertyDelegates_ShouldMirrorMockedIFileInfo = Passed +- RESULT OpenRead_ShouldReturnReadableStreamForWrappedFile = Passed +- RESULT Constructor_WhenFileInfoIsNull_ThrowsArgumentNullException = Passed +- RESULT Properties_ShouldMirrorWrappedFileInfo = Passed +- MESSAGE lines: none +- BASELINE-FAILED-UCS: NONE + +The TRX documents stay under the git-ignored coverage\test-results\931\ tree; this file is the committed projection. diff --git a/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/qa-gates/coverage-final.md b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/qa-gates/coverage-final.md new file mode 100644 index 000000000..9e5b5d64b --- /dev/null +++ b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/qa-gates/coverage-final.md @@ -0,0 +1,182 @@ +# Coverage Final (P4-T7): repository-wide test and coverage run after the fix + +Timestamp: 2026-09-29T09-39 +Command: +- Route: COVERAGE-ROUTE: DIRECT (fixed by P0-T9, STALL-PROBE: REPRODUCES) +- Run payload (CMD-COVERAGE-DIRECT, STAGE final): dotnet-coverage collect --output coverage\final-931.cobertura.xml --output-format cobertura --settings coverage\effective-coverage-931.config -- vstest.console.exe <9 test assemblies> "/Settings:scripts\vscode\TaskMaster.cli.runsettings" /InIsolation "/TestCaseFilter:TestCategory!=LiveOutlook&FullyQualifiedName!~HelperClasses.ShellUtilities_Tests&FullyQualifiedName!~HelperClasses.ShellUtilitiesStatic_Tests&FullyQualifiedName!~HelperClasses.SysImageListHelperTests&FullyQualifiedName!~EmailIntelligence.OSBrowser_Tests" "/ResultsDirectory:coverage\test-results\931\final" "/Logger:trx;LogFileName=final-931.trx" "/Blame:CollectHangDump;TestTimeout=4min;HangDumpType=None" (vstest.console.exe resolved through vswhere; effective settings derived with ConvertTo-DerivedCoverageSettingsXml from coverage.config) +- Post payload (CMD-COVERAGE-POST, STAGE final, RAW True): Get-TrxRunSummary / Format-TrxRunSummary, ConvertTo-KoverageCoberturaXml, Assert-CoberturaLineCoverageThreshold, Assert-CoberturaBranchCoverageThreshold, Get-CoberturaFirstPartyCoverageReport, ConvertTo-JacocoPackageProjection, Assert-JacocoProjectionReconciliation (the runner's own helpers, dot-sourced) +- Filter applied by the route: the LiveOutlook category exclusion plus the four-class shell-icon exclusion shown above +EXIT_CODE: 0 +MEASUREMENT: 1 + +Output Summary: +- COVERAGE-ROUTE: DIRECT +- RAW: True +- COLLECT_EXIT_CODE: 0 +- ASSEMBLY_COUNT: 9 +- ASSEMBLY: \QuickFiler.Test\bin\Debug\QuickFiler.Test.dll +- ASSEMBLY: \SVGControl.Test\bin\Debug\SVGControl.Test.dll +- ASSEMBLY: \Tags.Test\bin\Debug\Tags.Test.dll +- ASSEMBLY: \TaskMaster.Test\bin\Debug\TaskMaster.Test.dll +- ASSEMBLY: \TaskTree.Test\bin\Debug\TaskTree.Test.dll +- ASSEMBLY: \TaskVisualization.Test\bin\Debug\TaskVisualization.Test.dll +- ASSEMBLY: \ToDoModel.Test\bin\Debug\ToDoModel.Test.dll +- ASSEMBLY: \UtilitiesCS.Test\bin\Debug\UtilitiesCS.Test.dll +- ASSEMBLY: \VBFunctions.Test\bin\Debug\VBFunctions.Test.dll +- SEQUENCE_FILES: 0 +- TRX_PRESENT: True +- LINE-FLOOR: MET +- BRANCH-FLOOR: MET +- First-party coverage: lines 56084/65737 (85.32%), branches 13595/17052 (79.73%) +- FAILED-SET: (empty; no test failed) +- NEWLY-FAILING: NONE (the P0-T11 FAILED-SET was also empty) +- Branch decided: (a) exit 0 with FAILED-SET empty and both floors met; the task completes. + +SUMMARY-BEGIN +Test run outcome: Completed +Total 7320, executed 7320, passed 7320, failed 0. +Skipped 0, derived as total minus executed rather than reported by the test platform. +Figures reported verbatim by the test platform: error 0, timeout 0, aborted 0, notExecuted 0, inconclusive 0. +Failed tests: none +SUMMARY-END + +PROJECTION-BEGIN + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +PROJECTION-END + +Acceptance: EXIT_CODE 0; SEQUENCE_FILES 0 (DIRECT); the projection contains the UtilitiesCS and QuickFiler package elements with LINE and BRANCH counters; the First-party coverage line is present; the summary block begins Test run outcome: and FAILED-SET is empty; NEWLY-FAILING NONE; LINE-FLOOR MET and BRANCH-FLOOR MET as printed by CMD-COVERAGE-POST; the artifact contains no absolute path. All eight hold. coverage\final-931.jacoco.xml remains on disk, git-ignored, for P4-T8. The raw Cobertura document and the TRX are not copied into the feature folder. + +## Comparison of MEASUREMENT 1 against coverage-baseline.md (P4-T8, first comparison) + +Command: CMD-PACKAGE-COMPARE (coverage\baseline-931.jacoco.xml against coverage\final-931.jacoco.xml as written by MEASUREMENT 1) + +PACKAGE UtilitiesCS LINE baseline=38816/43424 rate=0.893884 final=38816/43424 rate=0.893884 NOT-LOWER=True +PACKAGE UtilitiesCS BRANCH baseline=9411/11269 rate=0.835123 final=9411/11269 rate=0.835123 NOT-LOWER=True +PACKAGE QuickFiler LINE baseline=10461/12754 rate=0.820213 final=10460/12754 rate=0.820135 NOT-LOWER=False +PACKAGE QuickFiler BRANCH baseline=2518/3217 rate=0.782717 final=2518/3217 rate=0.782717 NOT-LOWER=True + +Result: one PACKAGE line reads NOT-LOWER=False (QuickFiler LINE, one covered line fewer over an identical denominator). Per P4-T8 and D-7, P4-T7 is run once more with the identical command as MEASUREMENT 2 and the comparison is repeated. No test failed in MEASUREMENT 1; this repeats the measurement, not a test. + +## MEASUREMENT 2 (P4-T7 re-run with the identical command, per P4-T8) + +Timestamp: 2026-09-29T09-42 +Command: identical to MEASUREMENT 1 (CMD-COVERAGE-DIRECT with STAGE final, then CMD-COVERAGE-POST with STAGE final and RAW True); coverage\final-931.* documents overwritten by this measurement +EXIT_CODE: 0 +MEASUREMENT: 2 + +- COVERAGE-ROUTE: DIRECT +- RAW: True +- COLLECT_EXIT_CODE: 0 +- ASSEMBLY_COUNT: 9 (the same nine ASSEMBLY lines as MEASUREMENT 1) +- SEQUENCE_FILES: 0 +- TRX_PRESENT: True +- LINE-FLOOR: MET +- BRANCH-FLOOR: MET +- First-party coverage: lines 56085/65737 (85.32%), branches 13595/17052 (79.73%) +- FAILED-SET: (empty; no test failed) +- NEWLY-FAILING: NONE +- Branch decided: (a). + +SUMMARY-BEGIN +Test run outcome: Completed +Total 7320, executed 7320, passed 7320, failed 0. +Skipped 0, derived as total minus executed rather than reported by the test platform. +Figures reported verbatim by the test platform: error 0, timeout 0, aborted 0, notExecuted 0, inconclusive 0. +Failed tests: none +SUMMARY-END + +PROJECTION-BEGIN + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +PROJECTION-END + +## Comparison against coverage-baseline.md + +Command: CMD-PACKAGE-COMPARE (coverage\baseline-931.jacoco.xml against coverage\final-931.jacoco.xml as written by MEASUREMENT 2) + +PACKAGE UtilitiesCS LINE baseline=38816/43424 rate=0.893884 final=38816/43424 rate=0.893884 NOT-LOWER=True +PACKAGE UtilitiesCS BRANCH baseline=9411/11269 rate=0.835123 final=9411/11269 rate=0.835123 NOT-LOWER=True +PACKAGE QuickFiler LINE baseline=10461/12754 rate=0.820213 final=10461/12754 rate=0.820213 NOT-LOWER=True +PACKAGE QuickFiler BRANCH baseline=2518/3217 rate=0.782717 final=2518/3217 rate=0.782717 NOT-LOWER=True + +BASELINE-FIRST-PARTY: First-party coverage: lines 56085/65737 (85.32%), branches 13595/17052 (79.73%) +FINAL-FIRST-PARTY: First-party coverage: lines 56085/65737 (85.32%), branches 13595/17052 (79.73%) +COMPARABILITY: A (the two first-party lines denominators are both 65737, a difference of 0, within 1 percent of the baseline denominator) +CHANGED-PRODUCTION-LINES: 0 (the P2-T10 git show path list contains no production path, so every changed line of this item is test code outside the coverage denominator and the policy's no-regression-on-changed-lines clause has an empty subject) + +Observation: MEASUREMENT 1 read the QuickFiler LINE counter one covered line lower (10460/12754) than the baseline and MEASUREMENT 2 with the identical command read it equal (10461/12754). No production file changed, so the one-line difference between two identical measurements is measurement variance of the collector, which D-7 anticipates. + +Acceptance: no PACKAGE line reads MISSING; all four PACKAGE lines read NOT-LOWER=True (MEASUREMENT 2); exactly one COMPARABILITY line is present; CHANGED-PRODUCTION-LINES: 0 is present and the P2-T10 path list contains no path under QuickFiler/ or UtilitiesCS/. All four hold. AC18: MET. diff --git a/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/qa-gates/p4-t1-csharpier-format.2026-09-29T09-33.md b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/qa-gates/p4-t1-csharpier-format.2026-09-29T09-33.md new file mode 100644 index 000000000..fd025da36 --- /dev/null +++ b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/qa-gates/p4-t1-csharpier-format.2026-09-29T09-33.md @@ -0,0 +1,29 @@ +# P4-T1 Repository-Wide CSharpier Format + +Timestamp: 2026-09-29T09-33 +Command: dotnet tool run csharpier format . (inside one pwsh payload whose first statement set the location to the worktree root; the payload captured Get-FileHash SHA256 of the five Write Set .cs files and the SHA-256 of the text printed by git diff MERGE-BASE -- QuickFiler.Test UtilitiesCS.Test immediately before and after the command, then ran git status --porcelain -- QuickFiler QuickFiler.Test UtilitiesCS UtilitiesCS.Test) +EXIT_CODE: 0 +ITERATION: 1 + +Output Summary: +- Formatter summary line (verbatim): Formatted 1625 files in 5533ms. +- MERGE-BASE used: 177b6d78e1b2408e5aedbd794cef3aad6b7fb372 +- BEFORE QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.cs = BAD939900B47AE0AC963D203F621D28C959CC7F3E492119F1BD8272197EF5940 +- BEFORE QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.Part2.cs = D6A35090996E48283CA04D8EE2C441F8ED8946BAECC1D06D9F16931FF767976E +- BEFORE QuickFiler.Test/TestSupport/DedicatedWorkerThread.cs = 986838E4FD72A7E233B26A1BE03B5912DFBB4016980F515F5C01680A0ABF688E +- BEFORE QuickFiler.Test/Viewers/BreadcrumbPopupBoundaryCoverageTests.cs = 4F8B8AFFA9387043AAE3A66EF2028D96AB4F0FA77AFCE244C9044E67B9B8E0FE +- BEFORE UtilitiesCS.Test/HelperClasses/FileInfoWrapper_Tests.cs = 142BCB8E95D710890A71F325A65801ED8EB53A87A339B10018D889491ECEF596 +- AFTER QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.cs = BAD939900B47AE0AC963D203F621D28C959CC7F3E492119F1BD8272197EF5940 +- AFTER QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.Part2.cs = D6A35090996E48283CA04D8EE2C441F8ED8946BAECC1D06D9F16931FF767976E +- AFTER QuickFiler.Test/TestSupport/DedicatedWorkerThread.cs = 986838E4FD72A7E233B26A1BE03B5912DFBB4016980F515F5C01680A0ABF688E +- AFTER QuickFiler.Test/Viewers/BreadcrumbPopupBoundaryCoverageTests.cs = 4F8B8AFFA9387043AAE3A66EF2028D96AB4F0FA77AFCE244C9044E67B9B8E0FE +- AFTER UtilitiesCS.Test/HelperClasses/FileInfoWrapper_Tests.cs = 142BCB8E95D710890A71F325A65801ED8EB53A87A339B10018D889491ECEF596 +- PATCH-BEFORE = 0D10D18AFF2F225DBE4A7EF5B9081B913933091C2EB07CA6F2BAAFCD0330D31F +- PATCH-AFTER = 0D10D18AFF2F225DBE4A7EF5B9081B913933091C2EB07CA6F2BAAFCD0330D31F +- REWRITTEN: 0 +- FORMAT_CHANGED_TREE: NO (the two anchored-patch hashes are equal) +- Porcelain output (git status --porcelain -- QuickFiler QuickFiler.Test UtilitiesCS UtilitiesCS.Test): EMPTY +- Fact 23 prediction (no line-ending rewrite of a Write Set file): MET (REWRITTEN: 0) +- FORMAT SCOPE BREACH: not fired. FORMAT NOT IDEMPOTENT: not applicable (no rewrite). + +Acceptance: EXIT_CODE 0; FORMAT_CHANGED_TREE and REWRITTEN recorded; porcelain lists no path outside the Write Set (it is empty). All three hold. No loop restart is required. diff --git a/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/qa-gates/p4-t11-scope-boundary.2026-09-29T09-44.md b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/qa-gates/p4-t11-scope-boundary.2026-09-29T09-44.md new file mode 100644 index 000000000..c08b61a15 --- /dev/null +++ b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/qa-gates/p4-t11-scope-boundary.2026-09-29T09-44.md @@ -0,0 +1,143 @@ +# P4-T11 Footprint and Scope Boundary (AC3, AC15, AC16 diff clause) + +Timestamp: 2026-09-29T09-44 +Command: (one pwsh payload, MERGE-BASE = 177b6d78e1b2408e5aedbd794cef3aad6b7fb372) git diff --name-only MERGE-BASE; git status --porcelain --untracked-files=all; git diff --name-only MERGE-BASE -- QuickFiler.Test; git diff --name-only MERGE-BASE -- .claude; git diff --name-only MERGE-BASE..HEAD -- .claude config; git log --oneline --diff-filter=A MERGE-BASE..HEAD -- docs\features\potential; git diff --exit-code MERGE-BASE -- QuickFiler UtilitiesCS TaskMaster.runsettings scripts\vscode\TaskMaster.cli.runsettings config; CMD-ADDED-LINES (git diff -U0 MERGE-BASE -- QuickFiler.Test UtilitiesCS.Test); Get-FileHash -Algorithm SHA256 TaskMaster.runsettings +EXIT_CODE: 0 (scoped to the git diff --exit-code span over the production and configuration paths) + +Output Summary: + +## git diff --name-only MERGE-BASE (working tree), verbatim + + .claude/agent-memory/atomic-executor/MEMORY.md + .claude/agent-memory/atomic-planner/MEMORY.md + .claude/agent-memory/atomic-planner/project_planner_mcp_validator_not_in_tool_surface.md + .claude/agent-memory/orchestrator/MEMORY.md + .claude/agent-memory/prd-feature/MEMORY.md + .claude/agent-memory/task-researcher/MEMORY.md + QuickFiler.Test/QuickFiler.Test.csproj + QuickFiler.Test/TestSupport/DedicatedWorkerThread.cs + QuickFiler.Test/Viewers/BreadcrumbPopupBoundaryCoverageTests.cs + QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.Part2.cs + QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.cs + UtilitiesCS.Test/HelperClasses/FileInfoWrapper_Tests.cs + docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/baseline/coverage-baseline.md + docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/baseline/p0-t12-pre-edit-census.2026-09-29T09-05.md + docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/baseline/p0-t13-phase0-commit.2026-09-29T09-07.md + docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/baseline/p0-t2-mode-preconditions.2026-09-29T08-54.md + docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/baseline/p0-t3-worktree-context.2026-09-29T08-55.md + docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/baseline/p0-t4-channel-and-toolchain.2026-09-29T08-56.md + docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/baseline/p0-t5-nuget-restore.2026-09-29T08-56.md + docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/baseline/p0-t6-csharpier-check.2026-09-29T08-56.md + docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/baseline/p0-t7-msbuild-analyzers.2026-09-29T08-59.md + docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/baseline/p0-t8-msbuild-nullable.2026-09-29T08-59.md + docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/baseline/p0-t9-stall-probe.2026-09-29T09-00.md + docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/baseline/phase0-instructions-read.md + docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/baseline/test-run-baseline.md + docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/regression-testing/fail-before-exception.2026-09-29T09-07.md + docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/regression-testing/mutation-inline-precondition.md + docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/regression-testing/mutation-null-owner-escape.md + docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/regression-testing/mutation-openread-sentinel.md + docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/regression-testing/mutation-owner-only-dispatcher-guard.md + docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/regression-testing/p2-t1-helper-census.2026-09-29T09-10.md + docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/regression-testing/p2-t10-fix-commit.2026-09-29T09-21.md + docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/regression-testing/p2-t2-csproj-census.2026-09-29T09-11.md + docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/regression-testing/p2-t3-part2-census.2026-09-29T09-12.md + docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/regression-testing/p2-t4-primary-census.2026-09-29T09-13.md + docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/regression-testing/p2-t5-boundary-census.2026-09-29T09-14.md + docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/regression-testing/p2-t6-fileinfowrapper-census.2026-09-29T09-15.md + docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/regression-testing/p2-t7-csharpier-scoped.2026-09-29T09-16.md + docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/regression-testing/p2-t8-build-after-fix.2026-09-29T09-18.md + docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/regression-testing/p2-t9-pass-before-controls.2026-09-29T09-20.md + docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/regression-testing/p3-t9-post-control-clean-tree.2026-09-29T09-30.md + docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/issue.md + docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/plan.2026-09-28T20-01.md + docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/research/2026-09-28T20-15-tests-depend-on-uncontrolled-environment-research.md + docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/spec.md + docs/features/potential/promoted/2026-09-28-tests-depend-on-uncontrolled-environment.md + +(git also printed one line-ending warning for .claude/agent-memory/task-researcher/MEMORY.md; not a path entry.) + +## git status --porcelain --untracked-files=all, verbatim + + M .claude/agent-memory/atomic-executor/MEMORY.md + M .claude/agent-memory/atomic-planner/MEMORY.md + M .claude/agent-memory/atomic-planner/project_planner_mcp_validator_not_in_tool_surface.md + M .claude/agent-memory/orchestrator/MEMORY.md + M .claude/agent-memory/prd-feature/MEMORY.md + M .claude/agent-memory/task-researcher/MEMORY.md + M docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/plan.2026-09-28T20-01.md + ?? .claude/agent-memory/atomic-executor/index_artifact_hygiene_and_misc.md + ?? .claude/agent-memory/atomic-executor/project_loop_iteration_vs_single_artifact_glob_and_hash_anchor.md + ?? .claude/agent-memory/atomic-executor/project_outcome_branch_sets_keyed_on_route_miss_derived_flag_cases.md + ?? .claude/agent-memory/atomic-executor/project_pinned_target_source_prose_carries_census_tokens.md + ?? .claude/agent-memory/atomic-planner/project_931_uncontrolled_environment_tests_plan_seams.md + ?? .claude/agent-memory/orchestrator/delegation-prompt-needs-canonical-issue-and-branch-lines.md + ?? .claude/agent-memory/prd-feature/reference_ifileinfo_seam_filestream_sentinel_not_memorystream.md + ?? .claude/agent-memory/task-researcher/project_taskrun_triage_931.md + ?? docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/qa-gates/coverage-final.md + ?? docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/qa-gates/p4-t1-csharpier-format.2026-09-29T09-33.md + ?? docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/qa-gates/p4-t2-csharpier-check.2026-09-29T09-34.md + ?? docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/qa-gates/p4-t3-msbuild-analyzers.2026-09-29T09-34.md + ?? docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/qa-gates/p4-t4-msbuild-nullable.2026-09-29T09-35.md + ?? docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/qa-gates/p4-t9-post-format-census.2026-09-29T09-42.md + ?? docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/qa-gates/toolchain-pass.md + ?? docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/regression-testing/parallel-suite-quickfiler-test.md + ?? docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/regression-testing/parallel-suite-utilitiescs-test.md + +The porcelain span is the name-listing diff's companion (it lists the untracked Phase 4 artifacts the anchored diff cannot see). + +## Derived sets + +INHERITED-AND-EXCLUDED: +- Clause A (INHERITED-CLAUSE-A of P0-T3): docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/issue.md; docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/plan.2026-09-28T20-01.md; docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/research/2026-09-28T20-15-tests-depend-on-uncontrolled-environment-research.md; docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/spec.md; docs/features/potential/promoted/2026-09-28-tests-depend-on-uncontrolled-environment.md +- Clause B (prefix .claude/agent-memory/): the six modified and eight untracked .claude/agent-memory/ paths listed above +- No Write Set path was subtracted. + +THIS-ITEM-FOOTPRINT: +- QuickFiler.Test/QuickFiler.Test.csproj +- QuickFiler.Test/TestSupport/DedicatedWorkerThread.cs +- QuickFiler.Test/Viewers/BreadcrumbPopupBoundaryCoverageTests.cs +- QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.Part2.cs +- QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.cs +- UtilitiesCS.Test/HelperClasses/FileInfoWrapper_Tests.cs +- every docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/ path listed in the two spans above (baseline, regression-testing and qa-gates artifacts of this run) +- nothing else + +QUICKFILER-TEST-CHANGED: +- QuickFiler.Test/QuickFiler.Test.csproj +- QuickFiler.Test/TestSupport/DedicatedWorkerThread.cs +- QuickFiler.Test/Viewers/BreadcrumbPopupBoundaryCoverageTests.cs +- QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.Part2.cs +- QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.cs + +CLAUDE-CHANGED: +- .claude/agent-memory/atomic-executor/MEMORY.md +- .claude/agent-memory/atomic-planner/MEMORY.md +- .claude/agent-memory/atomic-planner/project_planner_mcp_validator_not_in_tool_surface.md +- .claude/agent-memory/orchestrator/MEMORY.md +- .claude/agent-memory/prd-feature/MEMORY.md +- .claude/agent-memory/task-researcher/MEMORY.md + +COMMITTED-CLAUDE-OR-CONFIG: NONE + +INHERITED-PROMOTION-RECORD: e13757267 docs(bug-931): promote tests-depend-on-uncontrolled-environment into an active full-bug feature folder +(the promoted record docs/features/potential/promoted/2026-09-28-tests-depend-on-uncontrolled-environment.md was committed on this branch deliberately before execution, lies in INHERITED-CLAUSE-A because it is in the committed MERGE-BASE...HEAD diff, and is subtracted from THIS-ITEM-FOOTPRINT under Clause A) + +PRODUCTION-AND-CONFIG-DIFF-EXIT: 0 + +## CMD-ADDED-LINES + +ADDED-LINE-COUNT: 306 +ADDED-DONOTPARALLELIZE: 0 +ADDED-THREAD-SLEEP: 0 +ADDED-TASK-DELAY: 0 +ADDED-TIMEOUT: 0 +ADDED-RETRY: 0 +ADDED-WORKERS: 0 +ADDED-SCOPE: 0 + +RUNSETTINGS-HASH-NOW: 199408CA53CE4E12AE1A894FC66A0926124F3AC0D6447BD93B0C121338297FFA (equals RUNSETTINGS-HASH from P0-T4) + +Acceptance: THIS-ITEM-FOOTPRINT is exactly the six Write Set code paths plus paths under the feature folder; QUICKFILER-TEST-CHANGED is exactly the four QuickFiler.Test Write Set .cs paths plus the project file (the five QuickFiler.Test Write Set entries, so none of the eight files AC3 names and neither DoNotParallelize carrier changed); every CLAUDE-CHANGED path begins .claude/agent-memory/; COMMITTED-CLAUDE-OR-CONFIG NONE; INHERITED-PROMOTION-RECORD recorded; PRODUCTION-AND-CONFIG-DIFF-EXIT 0; the seven ADDED- counts are 0 over ADDED-LINE-COUNT 306; RUNSETTINGS-HASH-NOW equals RUNSETTINGS-HASH; the porcelain span is present. All nine hold. + +Note on the count wording: the plan's acceptance text for QUICKFILER-TEST-CHANGED says "the four QuickFiler.Test Write Set paths", while the plan's Write Set lists five QuickFiler.Test entries (four .cs files and QuickFiler.Test/QuickFiler.Test.csproj). The observed list equals those five Write Set entries exactly and contains no other path. diff --git a/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/qa-gates/p4-t12-hygiene-sweep.2026-09-29T09-45.md b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/qa-gates/p4-t12-hygiene-sweep.2026-09-29T09-45.md new file mode 100644 index 000000000..8414f96d4 --- /dev/null +++ b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/qa-gates/p4-t12-hygiene-sweep.2026-09-29T09-45.md @@ -0,0 +1,18 @@ +# P4-T12 Hygiene Sweep (AC19) + +Timestamp: 2026-09-29T09-45 +Command: CMD-SWEEP over docs\features\active\2026-09-28-tests-depend-on-uncontrolled-environment-931 (tokens derived at run time and never written), then git diff --name-only MERGE-BASE with git status --porcelain --untracked-files=all for the raw-extension check (MERGE-BASE = 177b6d78e1b2408e5aedbd794cef3aad6b7fb372) +EXIT_CODE: 0 + +Output Summary: +- FILES: 43 +- ACCOUNT-TOKEN-MATCHES: 0 +- PROFILE-LEAF-MATCHES: 0 +- MACHINE-TOKEN-MATCHES: 0 +- WORKTREE-ROOT-MATCHES: 0 +- USERS-PATH-MATCHES: 0 +- RAW-DOCUMENT-FILES: 0 +- RAW-EXTENSION-PATHS: NONE (no path in the anchored diff or the porcelain span ends .trx, .xml or .coverage) +- The sweep ran once; no repair was required. + +Acceptance: the six match counts are 0; RAW-DOCUMENT-FILES 0; RAW-EXTENSION-PATHS NONE. All three hold. diff --git a/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/qa-gates/p4-t13-follow-up-handoff.2026-09-29T09-46.md b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/qa-gates/p4-t13-follow-up-handoff.2026-09-29T09-46.md new file mode 100644 index 000000000..6707516f5 --- /dev/null +++ b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/qa-gates/p4-t13-follow-up-handoff.2026-09-29T09-46.md @@ -0,0 +1,29 @@ +# P4-T13 Follow-Up Handoff Record (for the orchestrator) + +Timestamp: 2026-09-29T09-46 + +The orchestrator files each entry below as a potential entry through the promotion lifecycle's MCP tool (D-9). The executor created nothing under docs/features/potential/. Each body is drawn from the spec's Rollout & Follow-up list (spec lines 274 to 278) and the spec's out-of-scope findings (spec lines 65 to 66). + +## Follow-up 1 + +short_name: physicalfilesystemadapters-tests-open-repository-solution-file + +UtilitiesCS.Test/HelperClasses/PhysicalFileSystemAdapters_Tests.cs carries the same defect class as issue #906. The file defines its own solution-file locator (called at lines 173 and 318 and declared at lines 373 to 376), swallows contention in the `catch (IOException)` blocks at lines 43 and 195 (the spec also cites the swallowing span at lines 186 to 198), and opens the real repository solution file for reading at lines 213 to 234. Its outcome therefore depends on whether another process holds the solution file, which is uncontrolled environment state. The remedy follows the #931 pattern for FileInfoWrapper_Tests.cs: a test-owned stream over the test host's own loaded assembly image through an injectable seam, with no repository file opened. This entry is not fixed under #931. + +## Follow-up 2 + +short_name: directoryinfowrapper-tests-enumerate-repository-solution-file + +UtilitiesCS.Test/HelperClasses/DirectoryInfoWrapper_Tests.cs lines 60, 79 and 381 assert that TaskMaster.sln is enumerated from the repository root. The tests therefore depend on the repository layout and on the real file system at run time, which is the same defect class as issue #906. The remedy is to assert enumeration over a controlled seam, or over a fixture directory the test owns without creating temporary files, instead of over the repository root. This entry is not fixed under #931. + +## Follow-up 3 + +short_name: breadcrumb-dispatchvalue-message-wording-broader-than-mechanism + +QuickFiler.Test/Viewers/BreadcrumbUiThreadDispatchTests.cs line 305 asserts the message "cannot marshal cross-thread UI work". That wording describes the mechanism more broadly than `DispatchValue` implements: `DispatchValue` on an owner-only dispatcher faults for every caller outside an executing callback, on any thread, including the owner thread (spec Triage table row for line 301). The change is to the wording only. No behaviour change is needed. This entry is not fixed under #931. + +## Follow-up 4 + +short_name: issue-900-handoff-misattributes-dispatchvalue-site-to-owner-thread-id-check + +The #900 follow-up handoff record under docs/features/active/breadcrumb-thread-affinity-tests-assume-taskrun-distinct-thread-900/evidence/other/ attributes BreadcrumbUiThreadDispatchTests.cs line 301 to the owner-thread-id check. The #931 research record shows that line 301 reaches `DispatchValue`, which never reads the owner thread id. The correction is to the documentation only. This entry is not fixed under #931. diff --git a/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/qa-gates/p4-t2-csharpier-check.2026-09-29T09-34.md b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/qa-gates/p4-t2-csharpier-check.2026-09-29T09-34.md new file mode 100644 index 000000000..8ffa83178 --- /dev/null +++ b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/qa-gates/p4-t2-csharpier-check.2026-09-29T09-34.md @@ -0,0 +1,13 @@ +# P4-T2 CSharpier Check + +Timestamp: 2026-09-29T09-34 +Command: dotnet tool run csharpier check . (inside a pwsh payload whose first statement set the location to the worktree root) +EXIT_CODE: 0 +ITERATION: 1 + +Output Summary: +- Final summary line (verbatim): Checked 1625 files in 5170ms. +- CHECKED-FILES: 1625 +- CHECKED-DELTA: 2 (1625 minus the P0-T6 CHECKED-FILES value 1623; the two .cs files this plan adds, the project file being excluded by .csharpierignore) + +Acceptance: EXIT_CODE 0; CHECKED-DELTA exactly 2. Both hold. diff --git a/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/qa-gates/p4-t3-msbuild-analyzers.2026-09-29T09-34.md b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/qa-gates/p4-t3-msbuild-analyzers.2026-09-29T09-34.md new file mode 100644 index 000000000..b3873bbff --- /dev/null +++ b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/qa-gates/p4-t3-msbuild-analyzers.2026-09-29T09-34.md @@ -0,0 +1,20 @@ +# P4-T3 Analyzer Rebuild Gate + +Timestamp: 2026-09-29T09-34 +Command: msbuild TaskMaster.sln /t:Rebuild /m /p:Configuration=Debug "/p:Platform=Any CPU" /p:EnableNETAnalyzers=true /p:EnforceCodeStyleInBuild=true (resolved through vswhere, plus /nodeReuse:false; CMD-REBUILD with TASKID p4-t3; file log coverage\logs\p4-t3.msbuild.log; console output discarded, the file log is the observed source) +EXIT_CODE: 0 +ITERATION: 1 + +Output Summary: +- MSBUILD_EXIT_CODE: 0 +- SKIP_CORECOMPILE_LINES: 0 +- QF_TEST_CSC_OUT_LINES: 2 +- UCS_TEST_CSC_OUT_LINES: 2 +- ZERO_ERRORS_LINES: 1 +- WARNINGS: 0 +- ERRORS: 0 +- WRITESET_DIAGNOSTIC_LINES: 0 +- WARNINGS-DELTA: 0 (0 minus ANALYZE-BASELINE-WARNINGS 0; observation) +- No CS0006 analyzer-path error occurred; the analyzer packages provisioned in P0-T7 (git-ignored) were present. No provisioning in this task. + +Acceptance (D-8): EXIT_CODE 0; ERRORS 0; SKIP_CORECOMPILE_LINES 0; both test-project compiler echoes at least 1; WRITESET_DIAGNOSTIC_LINES 0. All five hold. diff --git a/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/qa-gates/p4-t33-closure.2026-09-29T09-47.md b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/qa-gates/p4-t33-closure.2026-09-29T09-47.md new file mode 100644 index 000000000..51765eee2 --- /dev/null +++ b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/qa-gates/p4-t33-closure.2026-09-29T09-47.md @@ -0,0 +1,45 @@ +# P4-T33 Closure Record + +Timestamp: 2026-09-29T09-47 +Command: CMD-SWEEP (step 1); git add -- QuickFiler.Test UtilitiesCS.Test docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931; git commit -m "docs(931): evidence, acceptance check-off and plan state for the uncontrolled-environment test fix" (plus the session attribution paragraphs Co-Authored-By and Claude-Session in bare form); git rev-parse HEAD; git diff --name-only MERGE-BASE..HEAD -- QuickFiler QuickFiler.Test UtilitiesCS UtilitiesCS.Test; git diff --name-only origin/main...HEAD -- QuickFiler QuickFiler.Test UtilitiesCS UtilitiesCS.Test; git add -- docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931; git commit --amend --no-edit (twice); git status --porcelain -- QuickFiler QuickFiler.Test UtilitiesCS UtilitiesCS.Test docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931 + +Output Summary: +- Step 1 CMD-SWEEP: FILES 45; ACCOUNT-TOKEN-MATCHES 0; PROFILE-LEAF-MATCHES 0; MACHINE-TOKEN-MATCHES 0; WORKTREE-ROOT-MATCHES 0; USERS-PATH-MATCHES 0; RAW-DOCUMENT-FILES 0. HYGIENE SWEEP FAILED: not fired. +- COMMIT-1-EXIT: 0 +- HEAD-AFTER-COMMIT: 86d29d77ca653ac6e816bce978ce29ecfcc1beef (superseded by the two amends of steps 5 and 8) +- MERGE-BASE: 177b6d78e1b2408e5aedbd794cef3aad6b7fb372 (origin/main resolves to the same commit) + +git diff --name-only MERGE-BASE..HEAD -- QuickFiler QuickFiler.Test UtilitiesCS UtilitiesCS.Test: + + QuickFiler.Test/QuickFiler.Test.csproj + QuickFiler.Test/TestSupport/DedicatedWorkerThread.cs + QuickFiler.Test/Viewers/BreadcrumbPopupBoundaryCoverageTests.cs + QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.Part2.cs + QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.cs + UtilitiesCS.Test/HelperClasses/FileInfoWrapper_Tests.cs + +git diff --name-only origin/main...HEAD -- QuickFiler QuickFiler.Test UtilitiesCS UtilitiesCS.Test: + + QuickFiler.Test/QuickFiler.Test.csproj + QuickFiler.Test/TestSupport/DedicatedWorkerThread.cs + QuickFiler.Test/Viewers/BreadcrumbPopupBoundaryCoverageTests.cs + QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.Part2.cs + QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.cs + UtilitiesCS.Test/HelperClasses/FileInfoWrapper_Tests.cs + +Both lists are exactly the six Write Set code paths; the step-2 git add span is their companion. + +Follow-up handoff: evidence/qa-gates/p4-t13-follow-up-handoff.2026-09-29T09-46.md (four potential entries for the orchestrator to file; nothing was created under docs/features/potential/). + +### Acceptance Criteria Status +- Source: docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/spec.md +- Total AC items: 19 +- Checked off (delivered): 19 +- Remaining (unchecked): 0 +- Items remaining: none + +## Post-amend state (step 7) + +EXIT_CODE: 0 (scoped to the step-6 git status --porcelain -- QuickFiler QuickFiler.Test UtilitiesCS UtilitiesCS.Test docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931 span) +POST-AMEND-PORCELAIN: EMPTY +HEAD-BEFORE-FINAL-AMEND: 20d51fc67b428f4606c95d4fa88ac606806e6738 (superseded by the step-8 amend; the final head is reported in the executor's final message only) diff --git a/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/qa-gates/p4-t4-msbuild-nullable.2026-09-29T09-35.md b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/qa-gates/p4-t4-msbuild-nullable.2026-09-29T09-35.md new file mode 100644 index 000000000..a9ed3cef0 --- /dev/null +++ b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/qa-gates/p4-t4-msbuild-nullable.2026-09-29T09-35.md @@ -0,0 +1,22 @@ +# P4-T4 Nullable / Type-Check Rebuild Gate + +Timestamp: 2026-09-29T09-35 +Command: msbuild TaskMaster.sln /t:Rebuild /m /p:Configuration=Debug "/p:Platform=Any CPU" /p:TreatWarningsAsErrors=true (resolved through vswhere, plus /nodeReuse:false; CMD-REBUILD with TASKID p4-t4; no Nullable property override, no incremental Build target; file log coverage\logs\p4-t4.msbuild.log; console output discarded, the file log is the observed source) +EXIT_CODE: 0 +ITERATION: 1 + +Output Summary: +- MSBUILD_EXIT_CODE: 0 +- SKIP_CORECOMPILE_LINES: 0 +- QF_TEST_CSC_OUT_LINES: 2 +- UCS_TEST_CSC_OUT_LINES: 2 +- ZERO_ERRORS_LINES: 1 +- WARNINGS: 0 +- ERRORS: 0 +- WRITESET_DIAGNOSTIC_LINES: 0 +- WARNINGS-DELTA: 0 (0 minus NULLABLE-BASELINE-WARNINGS 0; observation) +- QF-TEST-DLL-EXISTS: True +- UCS-TEST-DLL-EXISTS: True +- Wall clock of the rebuild: 16 seconds (the compiler echo counts above show every test project was compiled, not skipped). + +Acceptance: EXIT_CODE 0; ERRORS 0; SKIP_CORECOMPILE_LINES 0; both compiler echoes at least 1; WRITESET_DIAGNOSTIC_LINES 0. All five hold. diff --git a/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/qa-gates/p4-t9-post-format-census.2026-09-29T09-42.md b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/qa-gates/p4-t9-post-format-census.2026-09-29T09-42.md new file mode 100644 index 000000000..0f2d95d19 --- /dev/null +++ b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/qa-gates/p4-t9-post-format-census.2026-09-29T09-42.md @@ -0,0 +1,55 @@ +# P4-T9 Post-Format Census (file size and token table after the repository-wide format) + +Timestamp: 2026-09-29T09-42 +Command: CMD-CENSUS on each of QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.cs, QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.Part2.cs, QuickFiler.Test/TestSupport/DedicatedWorkerThread.cs, QuickFiler.Test/Viewers/BreadcrumbPopupBoundaryCoverageTests.cs, UtilitiesCS.Test/HelperClasses/FileInfoWrapper_Tests.cs and QuickFiler.Test/QuickFiler.Test.csproj (one pwsh payload; the same token list, counting rule, line count and Get-FileHash as CMD-CENSUS; the TOKEN lines of each file are printed joined by " | " on one line) +EXIT_CODE: 0 +ITERATION: 1 + +Output Summary: + +## AFF QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.cs + +TOKENS: Task.Run( = 0 | .GetAwaiter() = 0 | DedicatedWorkerThread.Run( = 0 | RunOnDedicatedWorkerThread = 0 | ClearViewerDispatcher( = 0 | partial class = 1 | [TestMethod] = 4 | action(); = 0 | new Thread( = 0 | IsBackground = true = 0 | thread.Join(); = 0 | Join( = 0 | Join() = 0 | using System.Reflection; = 0 | using QuickFiler.Test.TestSupport; = 0 | namespace QuickFiler.Test.TestSupport = 0 | internal static class DedicatedWorkerThread = 0 | internal static Exception Run(Action action) = 0 | .Should() = 10 | dedicated worker thread must not be = 0 | distinct from every live thread by construction = 0 | unconditionally = 0 | owner.CheckAccess() = 0 | .NotBe( = 0 | ownerThreadId = 0 | .BeNull( = 0 | executions.Should().Be(0) = 0 | cannot marshal = 0 | GetSolutionFile = 0 | TaskMaster.sln = 0 | AppDomain = 0 | File.Exists( = 0 | File.Create = 0 | File.WriteAll = 0 | File.Delete = 0 | Path.GetTemp = 0 | FileMode. = 0 | FileMode.Open = 0 | FileAccess. = 0 | FileAccess.Read = 0 | FileShare.ReadWrite = 0 | Assembly.Location = 0 | FixturePath = 0 | using var sentinel = new FileStream( = 0 | BeSameAs(sentinel) = 0 | .Returns(decoy) = 0 | wrapper.OpenRead() = 0 | stream.CanRead.Should().BeTrue() = 0 | stream.Length.Should().BeGreaterThan(0) = 0 | return true; = 0 | _ownerThreadId.HasValue = 0 | System.Threading.SynchronizationContext.Current = 0 | INCLUDE-AFFINITY = 0 | INCLUDE-PART2 = 0 | INCLUDE-HELPER = 0 | Thread.Sleep = 0 | Task.Delay = 0 | [Timeout = 0 | DoNotParallelize = 0 | Retry = 0 +LINES = 294 +SHA256 = BAD939900B47AE0AC963D203F621D28C959CC7F3E492119F1BD8272197EF5940 + +## PART2 QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.Part2.cs + +TOKENS: Task.Run( = 0 | .GetAwaiter() = 0 | DedicatedWorkerThread.Run( = 3 | RunOnDedicatedWorkerThread = 0 | ClearViewerDispatcher( = 2 | partial class = 1 | [TestMethod] = 3 | action(); = 0 | new Thread( = 0 | IsBackground = true = 0 | thread.Join(); = 0 | Join( = 2 | Join() = 2 | using System.Reflection; = 1 | using QuickFiler.Test.TestSupport; = 1 | namespace QuickFiler.Test.TestSupport = 0 | internal static class DedicatedWorkerThread = 0 | internal static Exception Run(Action action) = 0 | .Should() = 15 | dedicated worker thread must not be = 3 | distinct from every live thread by construction = 0 | unconditionally = 1 | owner.CheckAccess() = 1 | .NotBe( = 0 | ownerThreadId = 0 | .BeNull( = 1 | executions.Should().Be(0) = 0 | cannot marshal = 0 | GetSolutionFile = 0 | TaskMaster.sln = 0 | AppDomain = 0 | File.Exists( = 0 | File.Create = 0 | File.WriteAll = 0 | File.Delete = 0 | Path.GetTemp = 0 | FileMode. = 0 | FileMode.Open = 0 | FileAccess. = 0 | FileAccess.Read = 0 | FileShare.ReadWrite = 0 | Assembly.Location = 0 | FixturePath = 0 | using var sentinel = new FileStream( = 0 | BeSameAs(sentinel) = 0 | .Returns(decoy) = 0 | wrapper.OpenRead() = 0 | stream.CanRead.Should().BeTrue() = 0 | stream.Length.Should().BeGreaterThan(0) = 0 | return true; = 0 | _ownerThreadId.HasValue = 0 | System.Threading.SynchronizationContext.Current = 0 | INCLUDE-AFFINITY = 0 | INCLUDE-PART2 = 0 | INCLUDE-HELPER = 0 | Thread.Sleep = 0 | Task.Delay = 0 | [Timeout = 0 | DoNotParallelize = 0 | Retry = 0 +LINES = 202 +SHA256 = D6A35090996E48283CA04D8EE2C441F8ED8946BAECC1D06D9F16931FF767976E + +## HELPER QuickFiler.Test/TestSupport/DedicatedWorkerThread.cs + +TOKENS: Task.Run( = 0 | .GetAwaiter() = 0 | DedicatedWorkerThread.Run( = 0 | RunOnDedicatedWorkerThread = 0 | ClearViewerDispatcher( = 0 | partial class = 0 | [TestMethod] = 0 | action(); = 1 | new Thread( = 1 | IsBackground = true = 1 | thread.Join(); = 1 | Join( = 2 | Join() = 2 | using System.Reflection; = 0 | using QuickFiler.Test.TestSupport; = 0 | namespace QuickFiler.Test.TestSupport = 1 | internal static class DedicatedWorkerThread = 1 | internal static Exception Run(Action action) = 1 | .Should() = 0 | dedicated worker thread must not be = 0 | distinct from every live thread by construction = 1 | unconditionally = 0 | owner.CheckAccess() = 0 | .NotBe( = 0 | ownerThreadId = 0 | .BeNull( = 0 | executions.Should().Be(0) = 0 | cannot marshal = 0 | GetSolutionFile = 0 | TaskMaster.sln = 0 | AppDomain = 0 | File.Exists( = 0 | File.Create = 0 | File.WriteAll = 0 | File.Delete = 0 | Path.GetTemp = 0 | FileMode. = 0 | FileMode.Open = 0 | FileAccess. = 0 | FileAccess.Read = 0 | FileShare.ReadWrite = 0 | Assembly.Location = 0 | FixturePath = 0 | using var sentinel = new FileStream( = 0 | BeSameAs(sentinel) = 0 | .Returns(decoy) = 0 | wrapper.OpenRead() = 0 | stream.CanRead.Should().BeTrue() = 0 | stream.Length.Should().BeGreaterThan(0) = 0 | return true; = 0 | _ownerThreadId.HasValue = 0 | System.Threading.SynchronizationContext.Current = 0 | INCLUDE-AFFINITY = 0 | INCLUDE-PART2 = 0 | INCLUDE-HELPER = 0 | Thread.Sleep = 0 | Task.Delay = 0 | [Timeout = 0 | DoNotParallelize = 0 | Retry = 0 +LINES = 48 +SHA256 = 986838E4FD72A7E233B26A1BE03B5912DFBB4016980F515F5C01680A0ABF688E + +## BND QuickFiler.Test/Viewers/BreadcrumbPopupBoundaryCoverageTests.cs + +TOKENS: Task.Run( = 0 | .GetAwaiter() = 3 | DedicatedWorkerThread.Run( = 1 | RunOnDedicatedWorkerThread = 0 | ClearViewerDispatcher( = 0 | partial class = 1 | [TestMethod] = 5 | action(); = 1 | new Thread( = 0 | IsBackground = true = 0 | thread.Join(); = 0 | Join( = 0 | Join() = 0 | using System.Reflection; = 1 | using QuickFiler.Test.TestSupport; = 1 | namespace QuickFiler.Test.TestSupport = 0 | internal static class DedicatedWorkerThread = 0 | internal static Exception Run(Action action) = 0 | .Should() = 34 | dedicated worker thread must not be = 1 | distinct from every live thread by construction = 0 | unconditionally = 0 | owner.CheckAccess() = 0 | .NotBe( = 1 | ownerThreadId = 2 | .BeNull( = 1 | executions.Should().Be(0) = 1 | cannot marshal = 1 | GetSolutionFile = 0 | TaskMaster.sln = 0 | AppDomain = 0 | File.Exists( = 0 | File.Create = 0 | File.WriteAll = 0 | File.Delete = 0 | Path.GetTemp = 0 | FileMode. = 0 | FileMode.Open = 0 | FileAccess. = 0 | FileAccess.Read = 0 | FileShare.ReadWrite = 0 | Assembly.Location = 0 | FixturePath = 0 | using var sentinel = new FileStream( = 0 | BeSameAs(sentinel) = 0 | .Returns(decoy) = 0 | wrapper.OpenRead() = 0 | stream.CanRead.Should().BeTrue() = 0 | stream.Length.Should().BeGreaterThan(0) = 0 | return true; = 1 | _ownerThreadId.HasValue = 0 | System.Threading.SynchronizationContext.Current = 0 | INCLUDE-AFFINITY = 0 | INCLUDE-PART2 = 0 | INCLUDE-HELPER = 0 | Thread.Sleep = 0 | Task.Delay = 0 | [Timeout = 0 | DoNotParallelize = 0 | Retry = 0 +LINES = 386 +SHA256 = 4F8B8AFFA9387043AAE3A66EF2028D96AB4F0FA77AFCE244C9044E67B9B8E0FE + +## FIW UtilitiesCS.Test/HelperClasses/FileInfoWrapper_Tests.cs + +TOKENS: Task.Run( = 0 | .GetAwaiter() = 0 | DedicatedWorkerThread.Run( = 0 | RunOnDedicatedWorkerThread = 0 | ClearViewerDispatcher( = 0 | partial class = 0 | [TestMethod] = 8 | action(); = 0 | new Thread( = 0 | IsBackground = true = 0 | thread.Join(); = 0 | Join( = 0 | Join() = 0 | using System.Reflection; = 0 | using QuickFiler.Test.TestSupport; = 0 | namespace QuickFiler.Test.TestSupport = 0 | internal static class DedicatedWorkerThread = 0 | internal static Exception Run(Action action) = 0 | .Should() = 53 | dedicated worker thread must not be = 0 | distinct from every live thread by construction = 0 | unconditionally = 0 | owner.CheckAccess() = 0 | .NotBe( = 0 | ownerThreadId = 0 | .BeNull( = 0 | executions.Should().Be(0) = 0 | cannot marshal = 0 | GetSolutionFile = 0 | TaskMaster.sln = 0 | AppDomain = 0 | File.Exists( = 0 | File.Create = 0 | File.WriteAll = 0 | File.Delete = 0 | Path.GetTemp = 0 | FileMode. = 13 | FileMode.Open = 13 | FileAccess. = 11 | FileAccess.Read = 11 | FileShare.ReadWrite = 9 | Assembly.Location = 7 | FixturePath = 4 | using var sentinel = new FileStream( = 1 | BeSameAs(sentinel) = 1 | .Returns(decoy) = 0 | wrapper.OpenRead() = 2 | stream.CanRead.Should().BeTrue() = 1 | stream.Length.Should().BeGreaterThan(0) = 1 | return true; = 0 | _ownerThreadId.HasValue = 0 | System.Threading.SynchronizationContext.Current = 0 | INCLUDE-AFFINITY = 0 | INCLUDE-PART2 = 0 | INCLUDE-HELPER = 0 | Thread.Sleep = 0 | Task.Delay = 0 | [Timeout = 0 | DoNotParallelize = 0 | Retry = 0 +LINES = 357 +SHA256 = 142BCB8E95D710890A71F325A65801ED8EB53A87A339B10018D889491ECEF596 + +## CSPROJ QuickFiler.Test/QuickFiler.Test.csproj + +TOKENS: Task.Run( = 0 | .GetAwaiter() = 0 | DedicatedWorkerThread.Run( = 0 | RunOnDedicatedWorkerThread = 0 | ClearViewerDispatcher( = 0 | partial class = 0 | [TestMethod] = 0 | action(); = 0 | new Thread( = 0 | IsBackground = true = 0 | thread.Join(); = 0 | Join( = 0 | Join() = 0 | using System.Reflection; = 0 | using QuickFiler.Test.TestSupport; = 0 | namespace QuickFiler.Test.TestSupport = 0 | internal static class DedicatedWorkerThread = 0 | internal static Exception Run(Action action) = 0 | .Should() = 0 | dedicated worker thread must not be = 0 | distinct from every live thread by construction = 0 | unconditionally = 0 | owner.CheckAccess() = 0 | .NotBe( = 0 | ownerThreadId = 0 | .BeNull( = 0 | executions.Should().Be(0) = 0 | cannot marshal = 0 | GetSolutionFile = 0 | TaskMaster.sln = 0 | AppDomain = 0 | File.Exists( = 0 | File.Create = 0 | File.WriteAll = 0 | File.Delete = 0 | Path.GetTemp = 0 | FileMode. = 0 | FileMode.Open = 0 | FileAccess. = 0 | FileAccess.Read = 0 | FileShare.ReadWrite = 0 | Assembly.Location = 0 | FixturePath = 0 | using var sentinel = new FileStream( = 0 | BeSameAs(sentinel) = 0 | .Returns(decoy) = 0 | wrapper.OpenRead() = 0 | stream.CanRead.Should().BeTrue() = 0 | stream.Length.Should().BeGreaterThan(0) = 0 | return true; = 0 | _ownerThreadId.HasValue = 0 | System.Threading.SynchronizationContext.Current = 0 | INCLUDE-AFFINITY = 1 | INCLUDE-PART2 = 1 | INCLUDE-HELPER = 1 | Thread.Sleep = 0 | Task.Delay = 0 | [Timeout = 0 | DoNotParallelize = 0 | Retry = 1 +LINES = 570 +SHA256 = 5538F090CDBC43F57E03000C1776D9881E147302E513D1D03D58C6102E4DC39F + +(INCLUDE-AFFINITY, INCLUDE-PART2 and INCLUDE-HELPER denote the three project-file Include tokens of CMD-CENSUS, named as the plan names them in prose.) + +## Checks + +- AC5 file size: LINES AFF 294, PART2 202, HELPER 48, BND 386, FIW 357; each at most 500. FILE SIZE LIMIT EXCEEDED: not fired. +- Token Census Expectations post column: every value holds for AFF, PART2, HELPER, BND, FIW and CSPROJ, including action(); 1 in HELPER, .Returns(decoy) 0 and Assembly.Location 7 in FIW, INCLUDE-PART2 1 and INCLUDE-HELPER 1 (no control residue). The CSPROJ Retry count of 1 is pre-existing project text and is not a gated token for that file. +- SHA256 against the final P4-T1 iteration (ITERATION 1) after-format hashes: AFF, PART2, HELPER, BND and FIW each equal. +- The only P4-T1 iteration recorded REWRITTEN: 0, so each .cs SHA256 is also compared with its FIX-HASH- anchor: AFF = FIX-HASH-AFF, PART2 = FIX-HASH-PART2, HELPER = FIX-HASH-HELPER, BND = FIX-HASH-BND, FIW = FIX-HASH-FIW (all equal). The CSPROJ SHA256 also equals FIX-HASH-CSPROJ. + +Acceptance: every LINES value for the five .cs files is at most 500; every post value holds; each .cs SHA256 equals its P4-T1 after-format hash and its FIX-HASH- anchor. All three hold. diff --git a/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/qa-gates/post-merge-bootstrap.2026-09-29T19-53.md b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/qa-gates/post-merge-bootstrap.2026-09-29T19-53.md new file mode 100644 index 000000000..2f2aa8435 --- /dev/null +++ b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/qa-gates/post-merge-bootstrap.2026-09-29T19-53.md @@ -0,0 +1,18 @@ +# Post-Merge Step 0: Tool and NuGet Restore + +Timestamp: 2026-09-29T19-53 +HEAD: 55a50e9226173d39d3c169b0dc90400b430af59b (merge commit 55a50e922, origin/main merged into bug/tests-depend-on-uncontrolled-environment-931) +Command: +- dotnet tool restore (run from ) +- pwsh -NoProfile -File scripts\vscode\Invoke-Restore.ps1 (the restore command recorded in evidence/baseline/p0-t5-nuget-restore.2026-09-29T08-56.md; absolute script path resolved at run time through Join-Path (Get-Location).Path; MSBUILDDISABLENODEREUSE set to 1; output teed to the git-ignored coverage\logs\postmerge.restore.log) +EXIT_CODE: 0 + +Output Summary: +- TOOL_RESTORE_EXIT_CODE: 0 ("Tool 'csharpier' (version '1.2.6') was restored."; "Restore was successful.") +- RESTORE_EXIT_CODE: 0 (restore log tail: "Build succeeded."; "0 Warning(s)"; "0 Error(s)") +- PACKAGE-DIR-COUNT: 174 (P0-T5 recorded 172 before the merge) +- MSTEST_ANALYZERS_441: True (packages\MSTest.Analyzers.4.4.1 exists) +- MEZIANTOU_30290: True (packages\Meziantou.Analyzer.3.0.290 exists) +- Observation: both folders were already present before the restore ran; the restore was run anyway, as instructed. + +Acceptance: both exit codes 0 and both analyzer package folders exist. Both hold. diff --git a/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/qa-gates/post-merge-coverage-final.md b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/qa-gates/post-merge-coverage-final.md new file mode 100644 index 000000000..3afc6d74a --- /dev/null +++ b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/qa-gates/post-merge-coverage-final.md @@ -0,0 +1,176 @@ +# Post-Merge Step 5: Repository-Wide Test and Coverage Run + +Timestamp: 2026-09-29T19-56 +HEAD: 55a50e9226173d39d3c169b0dc90400b430af59b +Command: +- Route: COVERAGE-ROUTE: DIRECT, the same route coverage-final.md and coverage-baseline.md used (fixed by P0-T9, STALL-PROBE: REPRODUCES). The block dot-sources scripts\vscode\Invoke-MSTestWithCoverage.ps1 by absolute path in this worktree and uses its own functions. Route substitution, recorded: the runner's entry point hard-codes "/TestCaseFilter:TestCategory!=LiveOutlook" and declares no filter parameter, so invoking the entry point directly cannot apply the shell-icon exclusion recorded by P0-T9. It would also produce figures that cannot be compared with the baseline, which was measured with the exclusion. +- Run payload (the plan's CMD-COVERAGE-DIRECT, STAGE postmerge): dotnet-coverage collect --output coverage\postmerge-931.cobertura.xml --output-format cobertura --settings coverage\effective-coverage-931.config -- vstest.console.exe <9 test assemblies> "/Settings:scripts\vscode\TaskMaster.cli.runsettings" /InIsolation "/TestCaseFilter:TestCategory!=LiveOutlook&FullyQualifiedName!~HelperClasses.ShellUtilities_Tests&FullyQualifiedName!~HelperClasses.ShellUtilitiesStatic_Tests&FullyQualifiedName!~HelperClasses.SysImageListHelperTests&FullyQualifiedName!~EmailIntelligence.OSBrowser_Tests" "/ResultsDirectory:coverage\test-results\931\postmerge" "/Logger:trx;LogFileName=postmerge-931.trx" "/Blame:CollectHangDump;TestTimeout=4min;HangDumpType=None" (vstest.console.exe resolved through vswhere; effective settings derived with ConvertTo-DerivedCoverageSettingsXml from coverage.config) +- Post payload (the plan's CMD-COVERAGE-POST, STAGE postmerge, RAW True): Get-TrxRunSummary / Format-TrxRunSummary, ConvertTo-KoverageCoberturaXml, Assert-CoberturaLineCoverageThreshold, Assert-CoberturaBranchCoverageThreshold, Get-CoberturaFirstPartyCoverageReport, ConvertTo-JacocoPackageProjection, Assert-JacocoProjectionReconciliation +- Stage names postmerge and postmerge2 were used so the baseline-931 and final-931 documents stay unchanged. +EXIT_CODE: 0 +MEASUREMENT: 1 + +Output Summary: +- COLLECT_EXIT_CODE: 0 +- ASSEMBLY_COUNT: 9 (QuickFiler.Test, SVGControl.Test, Tags.Test, TaskMaster.Test, TaskTree.Test, TaskVisualization.Test, ToDoModel.Test, UtilitiesCS.Test, VBFunctions.Test; each under \\bin\Debug\) +- SEQUENCE_FILES: 0 +- TRX_PRESENT: True +- LINE-FLOOR: MET +- BRANCH-FLOOR: MET +- RECONCILIATION: PASSED (Assert-JacocoProjectionReconciliation did not throw) +- First-party coverage: lines 56079/65736 (85.31%), branches 13597/17054 (79.73%) +- FAILED-SET: (empty; no test failed) +- COVERAGE-COMPARISON: FINDING (UtilitiesCS LINE is lower than the baseline in both measurements; see below) + +SUMMARY-BEGIN +Test run outcome: Completed +Total 7323, executed 7323, passed 7323, failed 0. +Skipped 0, derived as total minus executed rather than reported by the test platform. +Figures reported verbatim by the test platform: error 0, timeout 0, aborted 0, notExecuted 0, inconclusive 0. +Failed tests: none +SUMMARY-END + +PROJECTION-BEGIN + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +PROJECTION-END + +## Comparison of MEASUREMENT 1 against coverage-baseline.md + +Command: the plan's CMD-PACKAGE-COMPARE (coverage\baseline-931.jacoco.xml against coverage\postmerge-931.jacoco.xml) + +PACKAGE UtilitiesCS LINE baseline=38816/43424 rate=0.893884 postmerge=38811/43423 rate=0.893789 NOT-LOWER=False +PACKAGE UtilitiesCS BRANCH baseline=9411/11269 rate=0.835123 postmerge=9413/11271 rate=0.835152 NOT-LOWER=True +PACKAGE QuickFiler LINE baseline=10461/12754 rate=0.820213 postmerge=10460/12754 rate=0.820135 NOT-LOWER=False +PACKAGE QuickFiler BRANCH baseline=2518/3217 rate=0.782717 postmerge=2518/3217 rate=0.782717 NOT-LOWER=True + +Two PACKAGE lines read NOT-LOWER=False. Following the P4-T8 and D-7 precedent, the identical command was run once more as MEASUREMENT 2. + +## MEASUREMENT 2 (identical command, STAGE postmerge2) + +Timestamp: 2026-09-29T19-59 +Command: identical to MEASUREMENT 1 with STAGE postmerge2 (CMD-COVERAGE-DIRECT, then CMD-COVERAGE-POST with RAW True) +EXIT_CODE: 0 +MEASUREMENT: 2 + +- COLLECT_EXIT_CODE: 0 +- ASSEMBLY_COUNT: 9 +- SEQUENCE_FILES: 0 +- TRX_PRESENT: True +- LINE-FLOOR: MET +- BRANCH-FLOOR: MET +- RECONCILIATION: PASSED +- First-party coverage: lines 56080/65736 (85.31%), branches 13597/17054 (79.73%) +- FAILED-SET: (empty; no test failed) + +SUMMARY-BEGIN +Test run outcome: Completed +Total 7323, executed 7323, passed 7323, failed 0. +Skipped 0, derived as total minus executed rather than reported by the test platform. +Figures reported verbatim by the test platform: error 0, timeout 0, aborted 0, notExecuted 0, inconclusive 0. +Failed tests: none +SUMMARY-END + +PROJECTION-BEGIN + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +PROJECTION-END + +## Comparison of MEASUREMENT 2 against coverage-baseline.md + +Command: the plan's CMD-PACKAGE-COMPARE (coverage\baseline-931.jacoco.xml against coverage\postmerge2-931.jacoco.xml) + +PACKAGE UtilitiesCS LINE baseline=38816/43424 rate=0.893884 postmerge=38811/43423 rate=0.893789 NOT-LOWER=False +PACKAGE UtilitiesCS BRANCH baseline=9411/11269 rate=0.835123 postmerge=9413/11271 rate=0.835152 NOT-LOWER=True +PACKAGE QuickFiler LINE baseline=10461/12754 rate=0.820213 postmerge=10461/12754 rate=0.820213 NOT-LOWER=True +PACKAGE QuickFiler BRANCH baseline=2518/3217 rate=0.782717 postmerge=2518/3217 rate=0.782717 NOT-LOWER=True + +BASELINE-FIRST-PARTY: First-party coverage: lines 56085/65737 (85.32%), branches 13595/17052 (79.73%) +POSTMERGE-FIRST-PARTY: First-party coverage: lines 56080/65736 (85.31%), branches 13597/17054 (79.73%) + +## Finding: UtilitiesCS LINE rate is lower than the baseline + +Nothing was fixed; this section records the observation only. + +- QuickFiler LINE: MEASUREMENT 1 read one covered line fewer (10460) and MEASUREMENT 2 read the baseline value (10461) on an identical denominator of 12754. The merge changed only documentation comments in QuickFiler production code (BreadcrumbBridgeCoordinator.Search.cs and BreadcrumbItemViewerLifecycleCoordinator.Search.cs). This matches the one-line collector variance coverage-final.md recorded for the same package, so QuickFiler is not lower than baseline on MEASUREMENT 2. +- UtilitiesCS LINE: 38811/43423 in both measurements against a baseline of 38816/43424 (rate 0.893789 against 0.893884). A per-file comparison of the post-processed Cobertura documents against the baseline document (line numbers de-duplicated per file name) shows the following: + - UtilitiesCS\NewtonsoftHelpers\SDIL Reader\ILGlobals.cs: baseline 38/40, post-merge 36/38 in both measurements. The merge from origin/main deleted two static-field declarations with initializers (Cache and modules); the per-file figures, with two fewer lines and two fewer covered lines, are consistent with both deleted lines having been covered. + - UtilitiesCS\Threading\UiThread.cs: baseline 130/133, post-merge 131/134 in both measurements. The merge from origin/main added one covered line (the `_dispatcher is not null` test). + - A further 4 covered lines are missing in each measurement, but in a different file each time. In MEASUREMENT 1 they are in UtilitiesCS\EmailIntelligence\SubjectMap\SubjectMapSco.Orchestration.cs (127/131 to 123/131). In MEASUREMENT 2 they are lines 339 to 342 of UtilitiesCS\OutlookObjects\Table\OlTableExtensions.Etl.cs (268/273 to 264/273), and those lines were covered in both baseline and final and in MEASUREMENT 1. Neither file was changed by the merge. Because the missing lines move between files from run to run on unchanged code, this part of the drop is attributed to run-to-run collector or test-scheduling variance. + - The reproducible part of the drop comes from the merge alone: removing 2 covered lines and adding 1 covered line gives 38815/43423 = 0.893881, which is still lower than 0.893884 by 0.000003. The cause is production code that origin/main contributed. None of it comes from this item's changes, which remain test-only (CHANGED-PRODUCTION-LINES for this item: 0, per coverage-final.md). +- UtilitiesCS BRANCH and QuickFiler BRANCH: not lower in either measurement. +- Solution floors: LINE-FLOOR MET and BRANCH-FLOOR MET in both measurements. + +The raw Cobertura documents and the TRX files remain under the git-ignored coverage\ tree; this file carries only the committed projection and summary forms. diff --git a/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/qa-gates/post-merge-csharpier-check.2026-09-29T19-53.md b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/qa-gates/post-merge-csharpier-check.2026-09-29T19-53.md new file mode 100644 index 000000000..4deb94410 --- /dev/null +++ b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/qa-gates/post-merge-csharpier-check.2026-09-29T19-53.md @@ -0,0 +1,14 @@ +# Post-Merge Step 1: CSharpier Check + +Timestamp: 2026-09-29T19-53 +HEAD: 55a50e9226173d39d3c169b0dc90400b430af59b +Command: dotnet tool run csharpier check . (run from ; output teed to the git-ignored coverage\logs\postmerge.csharpier.log) +EXIT_CODE: 0 + +Output Summary: +- Success line printed: "Checked 1625 files in 8418ms." +- Files reported as unformatted: none +- git status --porcelain (excluding .claude/agent-memory) after the check: empty +- Format step (dotnet tool run csharpier format .): not run, because the check reported no file; no restart was needed. + +Acceptance: EXIT_CODE 0 and no file reported. Both hold. diff --git a/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/qa-gates/post-merge-msbuild-analyzers.2026-09-29T19-53.md b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/qa-gates/post-merge-msbuild-analyzers.2026-09-29T19-53.md new file mode 100644 index 000000000..d53399537 --- /dev/null +++ b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/qa-gates/post-merge-msbuild-analyzers.2026-09-29T19-53.md @@ -0,0 +1,17 @@ +# Post-Merge Step 2: Analyzer Rebuild Gate + +Timestamp: 2026-09-29T19-53 +HEAD: 55a50e9226173d39d3c169b0dc90400b430af59b +Command: msbuild TaskMaster.sln /t:Rebuild /m /nodeReuse:false /p:Configuration=Debug "/p:Platform=Any CPU" /p:EnableNETAnalyzers=true /p:EnforceCodeStyleInBuild=true "/flp:LogFile=coverage\logs\postmerge-analyzers.msbuild.log;Verbosity=normal" (the plan's CMD-REBUILD block; MSBuild.exe resolved through vswhere; console reduced to /v:minimal /clp:ErrorsOnly, which does not alter the file log) +EXIT_CODE: 0 + +Output Summary: +- MSBUILD_EXIT_CODE: 0 +- ERRORS: 0 (final summary "0 Error(s)") +- WARNINGS: 0 (final summary "0 Warning(s)") +- SKIP_CORECOMPILE_LINES: 0 +- QF_TEST_CSC_OUT_LINES: 2; UCS_TEST_CSC_OUT_LINES: 2 (both test assemblies were compiled by this run) +- Analyzer path occurrences in the log: MSTest.Analyzers.4.4.1 = 36, Meziantou.Analyzer.3.0.290 = 32 (the merged analyzer versions were loaded) +- Time Elapsed 00:00:19.80 + +Acceptance: EXIT_CODE 0, 0 errors, SKIP_CORECOMPILE_LINES 0. All hold. diff --git a/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/qa-gates/post-merge-msbuild-nullable.2026-09-29T19-54.md b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/qa-gates/post-merge-msbuild-nullable.2026-09-29T19-54.md new file mode 100644 index 000000000..f97d56acb --- /dev/null +++ b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/qa-gates/post-merge-msbuild-nullable.2026-09-29T19-54.md @@ -0,0 +1,16 @@ +# Post-Merge Step 3: Nullable (TreatWarningsAsErrors) Rebuild Gate + +Timestamp: 2026-09-29T19-54 +HEAD: 55a50e9226173d39d3c169b0dc90400b430af59b +Command: msbuild TaskMaster.sln /t:Rebuild /m /nodeReuse:false /p:Configuration=Debug "/p:Platform=Any CPU" /p:TreatWarningsAsErrors=true "/flp:LogFile=coverage\logs\postmerge-nullable.msbuild.log;Verbosity=normal" (the plan's CMD-REBUILD block; MSBuild.exe resolved through vswhere; console reduced to /v:minimal /clp:ErrorsOnly, which does not alter the file log) +EXIT_CODE: 0 + +Output Summary: +- MSBUILD_EXIT_CODE: 0 +- ERRORS: 0 (final summary "0 Error(s)") +- WARNINGS: 0 (final summary "0 Warning(s)") +- SKIP_CORECOMPILE_LINES: 0 +- QF_TEST_CSC_OUT_LINES: 2; UCS_TEST_CSC_OUT_LINES: 2 +- Time Elapsed 00:00:19.51 + +Acceptance: EXIT_CODE 0, 0 errors, SKIP_CORECOMPILE_LINES 0. All hold. diff --git a/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/qa-gates/post-merge-parallel-suite-quickfiler-test.md b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/qa-gates/post-merge-parallel-suite-quickfiler-test.md new file mode 100644 index 000000000..a5a9e1b9b --- /dev/null +++ b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/qa-gates/post-merge-parallel-suite-quickfiler-test.md @@ -0,0 +1,29 @@ +# Post-Merge Step 4a: Parallel-Suite Run, QuickFiler.Test Full Assembly + +Timestamp: 2026-09-29T19-54 +HEAD: 55a50e9226173d39d3c169b0dc90400b430af59b +Command: vstest.console.exe QuickFiler.Test\bin\Debug\QuickFiler.Test.dll "/Settings:TaskMaster.runsettings" /InIsolation "/ResultsDirectory:coverage\test-results\931\postmerge-qf" "/Logger:trx;LogFileName=postmerge-qf.trx" "/Blame:CollectHangDump;TestTimeout=4min;HangDumpType=None" (resolved through vswhere; the plan's CMD-VSTEST block, as used by P4-T5). Runsettings file: TaskMaster.runsettings (repository root; Workers 0, Scope ClassLevel). Isolation switch: /InIsolation. Filter: no test-case filter (as in P4-T5). Console transcript written to the git-ignored coverage\logs\postmerge-qf.vstest.log. +EXIT_CODE: 0 + +Output Summary: +- RUNSETTINGS-HASH-NOW: 199408CA53CE4E12AE1A894FC66A0926124F3AC0D6447BD93B0C121338297FFA (equals the P0-T4 RUNSETTINGS-HASH) +- VSTEST_EXIT_CODE: 0 +- TRX_PRESENT: True +- SEQUENCE_FILES: 0 +- COLLECTOR_LINES: 0 +- Outcome: Completed +- COUNTERS total=1469 executed=1469 passed=1469 failed=0 error=0 timeout=0 aborted=0 notExecuted=0 inconclusive=0 +- RESULT_COUNT: 1469 (P4-T5 recorded 1468; the merge added QuickFiler.Test/Controllers/QfcItemController.UiThreadDispatcherFixtureTests.cs from a sibling item) +- RESULT InitializeBreadcrumbPipeline_WorkerThread_ThrowsBoundaryDiagnostic = Passed +- RESULT ConfigureBreadcrumbDropDown_WorkerThread_ThrowsBoundaryDiagnostic = Passed +- RESULT InitializeBreadcrumbPipeline_NullOwningDispatcher_DoesNotThrow = Passed +- RESULT Dispatcher_OwnerOnlyWorker_ReportsWithoutRunningAction = Passed +- RESULT ConfigureBreadcrumbDropDown_OwningThreadInsideDispatcherOperation_DoesNotThrow = Passed +- RESULT InitializeBreadcrumbPipeline_OwningThreadNullAmbientContext_DoesNotThrow = Passed +- RESULT InitializeBreadcrumbPipeline_OwningThreadDifferentPlainContext_DoesNotThrow = Passed +- RESULT InitializeBreadcrumbPipeline_ConstructedInsideDispatcherOperation_SucceedsUnderDifferentAmbientContext = Passed +- Non-passed results: none + +Acceptance: zero failed tests; the four required names (the first four RESULT lines) are executed and Passed; all eight P4-T5 names are Passed. All hold. + +The TRX document stays under the git-ignored coverage\test-results\931\ tree; this file is the committed projection. diff --git a/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/qa-gates/post-merge-parallel-suite-utilitiescs-test.md b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/qa-gates/post-merge-parallel-suite-utilitiescs-test.md new file mode 100644 index 000000000..7726eba14 --- /dev/null +++ b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/qa-gates/post-merge-parallel-suite-utilitiescs-test.md @@ -0,0 +1,30 @@ +# Post-Merge Step 4b: Parallel-Suite Run, UtilitiesCS.Test Full Assembly + +Timestamp: 2026-09-29T19-55 +HEAD: 55a50e9226173d39d3c169b0dc90400b430af59b +Command: vstest.console.exe UtilitiesCS.Test\bin\Debug\UtilitiesCS.Test.dll "/Settings:TaskMaster.runsettings" /InIsolation "/TestCaseFilter:FullyQualifiedName!~HelperClasses.ShellUtilities_Tests&FullyQualifiedName!~HelperClasses.ShellUtilitiesStatic_Tests&FullyQualifiedName!~HelperClasses.SysImageListHelperTests&FullyQualifiedName!~EmailIntelligence.OSBrowser_Tests" "/ResultsDirectory:coverage\test-results\931\postmerge-ucs" "/Logger:trx;LogFileName=postmerge-ucs.trx" "/Blame:CollectHangDump;TestTimeout=4min;HangDumpType=None" (resolved through vswhere; the plan's CMD-VSTEST block, as used by P4-T6). Runsettings file: TaskMaster.runsettings (repository root; Workers 0, Scope ClassLevel). Isolation switch: /InIsolation. Filter: only the UCS-FILTERARG shell-icon exclusion recorded by P0-T9. Console transcript written to the git-ignored coverage\logs\postmerge-ucs.vstest.log. +EXIT_CODE: 0 + +Output Summary: +- RUNSETTINGS-HASH-NOW: 199408CA53CE4E12AE1A894FC66A0926124F3AC0D6447BD93B0C121338297FFA (equals the P0-T4 RUNSETTINGS-HASH) +- VSTEST_EXIT_CODE: 0 +- TRX_PRESENT: True +- SEQUENCE_FILES: 0 +- COLLECTOR_LINES: 0 +- Outcome: Completed +- COUNTERS total=4924 executed=4924 passed=4924 failed=0 error=0 timeout=0 aborted=0 notExecuted=0 inconclusive=0 +- RESULT_COUNT: 4924 (P4-T6 recorded 4922; the merge changed two UtilitiesCS.Test files, ILGlobals_Tests.cs and UiThreadApartmentMeasurement_Tests.cs, which is the likely source of the two additional tests; not attributed per test) +- FileInfoWrapper_Tests (class UtilitiesCS.Test.HelperClasses.FileInfoWrapper_Tests) contains eight test methods in this run's TRX, and all eight are executed and Passed: + - RESULT ToString_ShouldDelegateToWrappedFileInfo = Passed + - RESULT Constructor_WhenFileInfoIsNull_ThrowsArgumentNullException = Passed + - RESULT OpenRead_ShouldReturnReadableStreamForWrappedFile = Passed + - RESULT ExplicitDirectoryCast_ShouldReturnWrappedContainingDirectory = Passed + - RESULT AccessControlAndLifecycleMethods_ShouldDelegateToWrappedIFileInfo = Passed + - RESULT StreamAndCopyMethods_ShouldDelegateToWrappedIFileInfo = Passed + - RESULT PropertyDelegates_ShouldMirrorMockedIFileInfo = Passed + - RESULT Properties_ShouldMirrorWrappedFileInfo = Passed +- Non-passed results: none + +Acceptance: zero failed tests; every FileInfoWrapper_Tests name is executed and Passed. All hold. Note: the re-verification request referred to four FileInfoWrapper_Tests names; the class contains eight, matching the eight NAMES-FIW lines in P4-T6, and all eight are listed above. + +The TRX document stays under the git-ignored coverage\test-results\931\ tree; this file is the committed projection. diff --git a/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/qa-gates/post-merge-toolchain-pass.md b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/qa-gates/post-merge-toolchain-pass.md new file mode 100644 index 000000000..1877c058a --- /dev/null +++ b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/qa-gates/post-merge-toolchain-pass.md @@ -0,0 +1,37 @@ +# Post-Merge Toolchain Pass: Re-Run of the Phase 4 Final QC Gates + +Timestamp: 2026-09-29T20-01 +HEAD: 55a50e9226173d39d3c169b0dc90400b430af59b (merge commit 55a50e922, origin/main merged into bug/tests-depend-on-uncontrolled-environment-931) +Command: reconciliation of the six post-merge step artifacts listed below. Each step reuses the command, runsettings file, isolation switch and test-case filter recorded in the pre-merge Phase 4 evidence. +EXIT_CODE: 0 + +Output Summary: + +Steps, in CLAUDE.md toolchain order: +- 0 Bootstrap: dotnet tool restore, then pwsh -NoProfile -File scripts\vscode\Invoke-Restore.ps1. Exit 0 and 0. packages\MSTest.Analyzers.4.4.1 and packages\Meziantou.Analyzer.3.0.290 both exist. Evidence: post-merge-bootstrap.2026-09-29T19-53.md +- 1 Format: dotnet tool run csharpier check . Exit 0, printed "Checked 1625 files in 8418ms.", no file reported, so no format run and no restart. Evidence: post-merge-csharpier-check.2026-09-29T19-53.md +- 2 Analyze: msbuild TaskMaster.sln /t:Rebuild /m /p:Configuration=Debug "/p:Platform=Any CPU" /p:EnableNETAnalyzers=true /p:EnforceCodeStyleInBuild=true (plus /nodeReuse:false and a file logger). Exit 0, 0 errors, 0 warnings, SKIP_CORECOMPILE_LINES 0. Evidence: post-merge-msbuild-analyzers.2026-09-29T19-53.md +- 3 Type-check: msbuild TaskMaster.sln /t:Rebuild /m /p:Configuration=Debug "/p:Platform=Any CPU" /p:TreatWarningsAsErrors=true (plus /nodeReuse:false and a file logger). Exit 0, 0 errors, 0 warnings, SKIP_CORECOMPILE_LINES 0. Evidence: post-merge-msbuild-nullable.2026-09-29T19-54.md +- 4a QuickFiler.Test full assembly under TaskMaster.runsettings with /InIsolation and no filter. Exit 0, total 1469, failed 0. The four required names are executed and Passed. Evidence: post-merge-parallel-suite-quickfiler-test.md +- 4b UtilitiesCS.Test full assembly under TaskMaster.runsettings with /InIsolation and the P0-T9 shell-icon filter only. Exit 0, total 4924, failed 0. All eight FileInfoWrapper_Tests methods are executed and Passed. Evidence: post-merge-parallel-suite-utilitiescs-test.md +- 5 Coverage: COVERAGE-ROUTE DIRECT, which dot-sources scripts\vscode\Invoke-MSTestWithCoverage.ps1 and applies the P0-T9 filter; the substitution is recorded in the artifact. Exit 0 in both measurements, total 7323, failed 0, LINE-FLOOR MET and BRANCH-FLOOR MET. First-party lines are 56079/65736 and 56080/65736 (85.31%); branches are 13597/17054 (79.73%). Evidence: post-merge-coverage-final.md + +Coverage comparison against evidence/baseline/coverage-baseline.md (MEASUREMENT 2): + +| Package | Counter | Baseline | Post-merge | Not lower | +| --- | --- | --- | --- | --- | +| UtilitiesCS | LINE | 38816/43424 (0.893884) | 38811/43423 (0.893789) | False | +| UtilitiesCS | BRANCH | 9411/11269 (0.835123) | 9413/11271 (0.835152) | True | +| QuickFiler | LINE | 10461/12754 (0.820213) | 10461/12754 (0.820213) | True | +| QuickFiler | BRANCH | 2518/3217 (0.782717) | 2518/3217 (0.782717) | True | + +MEASUREMENT 1 read QuickFiler LINE as 10460/12754; post-merge-coverage-final.md attributes this to the same one-line collector variance recorded in coverage-final.md. + +FINDING: the UtilitiesCS LINE rate is lower than the baseline in both measurements. Per-file analysis (post-merge-coverage-final.md) splits the drop into two parts: +- Reproducible: the production edits that origin/main brought in, ILGlobals.cs (-2 lines, -2 covered) and UiThread.cs (+1 line, +1 covered). Together they give 38815/43423 (0.893881). +- Variance: 4 covered lines missing in a different unchanged file on each run. +No part of the drop comes from this item's test-only changes. Nothing was fixed. + +Tree state: git status --porcelain for the feature folder was empty before these artifacts were written. No .cs, .csproj, plan checkbox or spec acceptance-criteria line was edited. No .trx, .xml or .coverage file was staged. + +LOOP: CLEAN PASS on gates 1 to 4 (no step failed and no step changed a file). The coverage step completed with exit 0; its package comparison carries the UtilitiesCS LINE finding above. diff --git a/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/qa-gates/toolchain-pass.md b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/qa-gates/toolchain-pass.md new file mode 100644 index 000000000..54e6b6cf5 --- /dev/null +++ b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/qa-gates/toolchain-pass.md @@ -0,0 +1,34 @@ +# Toolchain Pass (P4-T10) + +Timestamp: 2026-09-29T09-43 +Command: reconciliation of P4-T1 through P4-T9 +EXIT_CODE: 0 +ITERATION: 1 + +Output Summary: + +Toolchain commands of the final clean iteration, in CLAUDE.md order: +- 1 Format: dotnet tool run csharpier check . - exit 0 - evidence/qa-gates/p4-t2-csharpier-check.2026-09-29T09-34.md (preceded by dotnet tool run csharpier format . exit 0, REWRITTEN: 0, evidence/qa-gates/p4-t1-csharpier-format.2026-09-29T09-33.md) +- 2 Analyze: msbuild TaskMaster.sln /t:Rebuild /m /p:Configuration=Debug "/p:Platform=Any CPU" /p:EnableNETAnalyzers=true /p:EnforceCodeStyleInBuild=true (plus /nodeReuse:false) - exit 0 - SKIP_CORECOMPILE_LINES: 0 - evidence/qa-gates/p4-t3-msbuild-analyzers.2026-09-29T09-34.md +- 3 Type-check: msbuild TaskMaster.sln /t:Rebuild /m /p:Configuration=Debug "/p:Platform=Any CPU" /p:TreatWarningsAsErrors=true (plus /nodeReuse:false) - exit 0 - SKIP_CORECOMPILE_LINES: 0 - evidence/qa-gates/p4-t4-msbuild-nullable.2026-09-29T09-35.md +- 4 Test with coverage: the MSTest-with-coverage route, COVERAGE-ROUTE: DIRECT (dotnet-coverage collect around vstest.console.exe with the runner's own functions and the four-class exclusion) - exit 0 (COLLECT_EXIT_CODE 0 in MEASUREMENT 1 and MEASUREMENT 2; LINE-FLOOR MET and BRANCH-FLOOR MET) - evidence/qa-gates/coverage-final.md + +Parallel-suite runs: +- P4-T5 QuickFiler.Test full assembly - exit 0, total 1468, failed 0 - evidence/regression-testing/parallel-suite-quickfiler-test.md +- P4-T6 UtilitiesCS.Test full assembly with UCS-FILTERARG - exit 0, total 4922, failed 0 - evidence/regression-testing/parallel-suite-utilitiescs-test.md + +ITERATIONS: 1 + +EXPECTATION-MET: P4-T1 YES (EXIT_CODE 0, REWRITTEN 0, FORMAT_CHANGED_TREE NO, porcelain empty) +EXPECTATION-MET: P4-T2 YES (EXIT_CODE 0, CHECKED-DELTA 2) +EXPECTATION-MET: P4-T3 YES (EXIT_CODE 0, ERRORS 0, SKIP_CORECOMPILE_LINES 0, compiler echoes 2 and 2, WRITESET_DIAGNOSTIC_LINES 0) +EXPECTATION-MET: P4-T4 YES (EXIT_CODE 0, ERRORS 0, SKIP_CORECOMPILE_LINES 0, compiler echoes 2 and 2, WRITESET_DIAGNOSTIC_LINES 0) +EXPECTATION-MET: P4-T5 YES (all six acceptance conditions) +EXPECTATION-MET: P4-T6 YES (all six acceptance conditions) +EXPECTATION-MET: P4-T7 YES (all eight acceptance conditions, branch (a)) +EXPECTATION-MET: P4-T8 YES (four PACKAGE lines NOT-LOWER=True on MEASUREMENT 2, COMPARABILITY A, CHANGED-PRODUCTION-LINES 0; MEASUREMENT 1 read QuickFiler LINE one line lower and was re-measured once per D-7) +EXPECTATION-MET: P4-T9 YES (all LINES at most 500, every post value holds, hashes equal P4-T1 and FIX-HASH anchors) + +LOOP: CLEAN PASS + +Acceptance: the four toolchain lines each record exit 0; both SKIP_CORECOMPILE_LINES values are 0; nine EXPECTATION-MET lines each read YES; LOOP: CLEAN PASS is present. All four hold. diff --git a/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/regression-testing/fail-before-exception.2026-09-29T09-07.md b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/regression-testing/fail-before-exception.2026-09-29T09-07.md new file mode 100644 index 000000000..e065cc35b --- /dev/null +++ b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/regression-testing/fail-before-exception.2026-09-29T09-07.md @@ -0,0 +1,27 @@ +# Fail-Before Exception Dossier (P1-T1), issue #931 + +Timestamp: 2026-09-29T09-07 + +WhyFailingRunImpossible: The distinct-thread defect is decided by whether the runtime executes a thread-pool work item inline on the waiting thread, a branch no committed test can force without mutating process-global thread-pool state. The file-handle defect is decided by whether another process holds TaskMaster.sln with a share mode that excludes readers, which a committed test cannot arrange without starting an external process. Both of those arrangements are prohibited by the unit-test policy (no mutable global state, no external processes, no temporary files). + +## Alternative Proof + +Static reproduction (spec Repro steps 1 to 3). The line citations below refer to the pre-edit file state that P0-T12 re-recorded: P0-T12 measured QuickFiler.Test/Viewers/BreadcrumbPopupBoundaryCoverageTests.cs at 361 lines, QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.cs at 490 lines and UtilitiesCS.Test/HelperClasses/FileInfoWrapper_Tests.cs at 359 lines, with SHA-256 values equal to the P0-T4 pre-edit hashes, and recorded one `Task.Run(` occurrence in each of the two QuickFiler.Test files and five `GetSolutionFile` occurrences plus one `TaskMaster.sln` occurrence in the FileInfoWrapper test file. + +1. BreadcrumbPopupBoundaryCoverageTests.cs lines 52 to 62 (`Dispatcher_OwnerOnlyWorker_ReportsWithoutRunningAction`): `CreateOwnerOnlyDispatcher` (lines 124 to 134) builds a `BreadcrumbUiDispatcher` with a null context and the test thread's managed id as owner (line 133). The test queues `dispatcher.Dispatch(...)` through `Task.Run` at line 58 and blocks on it at line 59. If the work item runs inline on the test thread, `IsCurrentBoundary()` (QuickFiler/Viewers/BreadcrumbUiDispatcher.cs lines 255 to 278; thread-id branch at lines 276 to 277) returns true, the action runs, `executions` is one and no error is reported, so the test fails for a reason unrelated to the code under test. +2. ItemViewerBreadcrumbThreadAffinityTests.cs lines 319 to 346 (`InitializeBreadcrumbPipeline_NullOwningDispatcher_DoesNotThrow`): the test clears the owning dispatcher through `ClearViewerDispatcher` (lines 357 to 371), then calls `InitializeBreadcrumbPipeline` from a `Task.Run` work item at line 332 with a blocking wait at lines 335 to 336. If the work item runs inline on the owner thread, the call is on-thread, and the pre-#781 context-reference guard that the remark at lines 311 to 317 claims to discriminate against would also have admitted it, so the test passes without discriminating. The null-owner escape under test is QuickFiler/Viewers/ItemViewer.Breadcrumb.cs lines 435 to 438. +3. FileInfoWrapper_Tests.cs lines 55 to 67 (`OpenRead_ShouldReturnReadableStreamForWrappedFile`) and 339 to 357 (`GetSolutionFile`): the helper walks up from `AppDomain.CurrentDomain.BaseDirectory` to the repository's TaskMaster.sln (lines 341, 345 and 346), and the test opens a read handle on it with the default `FileShare.Read` share mode of `FileInfo.OpenRead()`. + +Recorded observation of the file-handle defect (fact 17): the #900 run recorded exactly this failure. Its final coverage iteration 1 failed on `FileInfoWrapper_Tests.OpenRead_ShouldReturnReadableStreamForWrappedFile` with the solution file held by a resident MSBuild node-reuse worker left by that plan's own multi-process rebuilds (docs/features/active/breadcrumb-thread-affinity-tests-assume-taskrun-distinct-thread-900/evidence/qa-gates/p5-t5-mstest-coverage.2026-09-17T02-35.md, loop-context section). + +Deterministic failing evidence planned for this item: P3-T1, P3-T3, P3-T5 and P3-T7 apply a deliberately broken guard or a deliberately broken sentinel to each rewritten test and are designed to demonstrate that each rewritten test fails against that broken guard and passes after the revert. P3-T5 in particular inserts `action();` so the delegate also runs inline on the caller, which is the scheduling branch this dossier cannot force. Those tasks have not run at the time this dossier is written, so no result of theirs is stated here. + +SearchScope: docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/regression-testing/ +SearchPatterns: `fail-before-exception.*.md` +SearchResult: docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/regression-testing/fail-before-exception.2026-09-29T09-07.md (this file; the directory held no file before it was written) + +## Output Summary + +- A committed failing run of either defect is structurally impossible under the unit-test policy; this dossier is the schema-valid substitute for the fail-before requirement. +- Alternative proof: static reproduction of all three defect sites at their recorded pre-edit lines, plus the recorded #906 failure from the #900 run. +- Deterministic observed-failing evidence is assigned to the Phase 3 negative controls (P3-T1, P3-T3, P3-T5, P3-T7). diff --git a/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/regression-testing/mutation-inline-precondition.md b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/regression-testing/mutation-inline-precondition.md new file mode 100644 index 000000000..dff20e635 --- /dev/null +++ b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/regression-testing/mutation-inline-precondition.md @@ -0,0 +1,63 @@ +# Negative Control M3: Precondition Run Inline + +Timestamp: 2026-09-29T09-26 +Command: (1) CMD-CENSUS with PATH = QuickFiler.Test/TestSupport/DedicatedWorkerThread.cs; (2) msbuild QuickFiler.Test\QuickFiler.Test.csproj /t:Build /m /p:Configuration=Debug /p:Platform=AnyCPU (CMD-BUILD-QF, TASKID p3-t5), resolved through vswhere, plus /nodeReuse:false; (3) vstest.console.exe QuickFiler.Test\bin\Debug\QuickFiler.Test.dll "/Settings:TaskMaster.runsettings" /InIsolation "/TestCaseFilter:FullyQualifiedName~InitializeBreadcrumbPipeline_WorkerThread_ThrowsBoundaryDiagnostic|FullyQualifiedName~ConfigureBreadcrumbDropDown_WorkerThread_ThrowsBoundaryDiagnostic|FullyQualifiedName~InitializeBreadcrumbPipeline_NullOwningDispatcher_DoesNotThrow|FullyQualifiedName~Dispatcher_OwnerOnlyWorker_ReportsWithoutRunningAction" "/ResultsDirectory:coverage\test-results\931\p3-t5" "/Logger:trx;LogFileName=p3-t5.trx" "/Blame:CollectHangDump;TestTimeout=4min;HangDumpType=None" (CMD-VSTEST, resolved through vswhere) +EXIT_CODE: 1 +ExpectedExitCode: 1 + +Output Summary: +- The EXIT_CODE row is scoped to the mutated run (the VSTEST_EXIT_CODE of step 3); the expected failure is the control's purpose. +- Predicted failure (D-6) observed: all four tests Failed at their in-thread distinctness precondition, each MESSAGE containing "dedicated worker thread must not be". +- Acceptance: TOKEN action(); = 2; MSBUILD_EXIT_CODE 0 and CSC_OUT_LINES 2; total 4, failed 4; each of the four MESSAGE lines contains the precondition phrase; RUNSETTINGS-HASH-NOW equals RUNSETTINGS-HASH - HOLD. MUTATION PREDICTION MISMATCH did not fire. + +## Mutated run + +- Mutated file: QuickFiler.Test/TestSupport/DedicatedWorkerThread.cs +- Hunk: action(); inserted as the first statement of Run, immediately before Exception captured = null; (applied with the Edit tool) +- TOKEN action(); = 2 (post-fix value 1) +- Mutated-file census, other non-zero tokens: new Thread( 1, IsBackground = true 1, thread.Join(); 1, Join( 2, Join() 2, namespace QuickFiler.Test.TestSupport 1, internal static class DedicatedWorkerThread 1, internal static Exception Run(Action action) 1, distinct from every live thread by construction 1; LINES = 49; SHA256 = 20CAEB70471B0F8617F372F31271BBBC70780E52FA3E909645DD5F41F4E6B4D3 +- MSBUILD_EXIT_CODE: 0 +- CSC_OUT_LINES: 2 +- PROD_CSC_OUT_LINES: 0 (observation; no production file changed) +- ZERO_ERRORS_LINES: 1 +- DLL_ADVANCED: True +- Runsettings file: TaskMaster.runsettings +- Isolation switch: /InIsolation +- Filter: "/TestCaseFilter:FullyQualifiedName~InitializeBreadcrumbPipeline_WorkerThread_ThrowsBoundaryDiagnostic|FullyQualifiedName~ConfigureBreadcrumbDropDown_WorkerThread_ThrowsBoundaryDiagnostic|FullyQualifiedName~InitializeBreadcrumbPipeline_NullOwningDispatcher_DoesNotThrow|FullyQualifiedName~Dispatcher_OwnerOnlyWorker_ReportsWithoutRunningAction" +- RUNSETTINGS-HASH-NOW: 199408CA53CE4E12AE1A894FC66A0926124F3AC0D6447BD93B0C121338297FFA +- VSTEST_EXIT_CODE: 1 +- TRX_PRESENT: True +- SEQUENCE_FILES: 0 +- COLLECTOR_LINES: 0 + + COUNTERS total=4 executed=4 passed=0 failed=4 + RESULT_COUNT: 4 + RESULT ConfigureBreadcrumbDropDown_WorkerThread_ThrowsBoundaryDiagnostic = Failed + RESULT InitializeBreadcrumbPipeline_NullOwningDispatcher_DoesNotThrow = Failed + RESULT Dispatcher_OwnerOnlyWorker_ReportsWithoutRunningAction = Failed + RESULT InitializeBreadcrumbPipeline_WorkerThread_ThrowsBoundaryDiagnostic = Failed + MESSAGE ConfigureBreadcrumbDropDown_WorkerThread_ThrowsBoundaryDiagnostic :: Expected isOwnerThread to be False because the dedicated worker thread must not be the thread that constructed the viewer, or the boundary assertion would pass vacuously, but found True. + MESSAGE InitializeBreadcrumbPipeline_NullOwningDispatcher_DoesNotThrow :: Expected isOwnerThread to be False because the dedicated worker thread must not be the owner thread, or the null-owner escape would be witnessed on the owner thread and the test would pass vacuously, but found True. + MESSAGE Dispatcher_OwnerOnlyWorker_ReportsWithoutRunningAction :: Did not expect Environment.CurrentManagedThreadId to be 29 because the dedicated worker thread must not be the owner thread the dispatcher was built for, or the rejection path would never be reached. + MESSAGE InitializeBreadcrumbPipeline_WorkerThread_ThrowsBoundaryDiagnostic :: Expected isOwnerThread to be False because the dedicated worker thread must not be the thread that constructed the viewer, or the boundary assertion would pass vacuously, but found True. + +## Revert and confirming run + +- Recorded at: 2026-09-29T09-28 +- Commands: git checkout -- QuickFiler.Test/TestSupport/DedicatedWorkerThread.cs; git diff --exit-code HEAD -- QuickFiler.Test/TestSupport/DedicatedWorkerThread.cs; git status --porcelain -- QuickFiler.Test/TestSupport/DedicatedWorkerThread.cs; CMD-CENSUS on the file; CMD-BUILD-QF (TASKID p3-t6); CMD-VSTEST with ASSEMBLY-QF, FILTER-FOUR, NAMES-FOUR, TASKID p3-t6 +- REVERT-DIFF-EXIT: 0 +- Porcelain output: EMPTY +- TOKEN action(); = 1 +- SHA256: 986838E4FD72A7E233B26A1BE03B5912DFBB4016980F515F5C01680A0ABF688E (FIX-HASH-HELPER: 986838E4FD72A7E233B26A1BE03B5912DFBB4016980F515F5C01680A0ABF688E; equal) +- LINES = 48 +- MSBUILD_EXIT_CODE: 0; CSC_OUT_LINES: 2; PROD_CSC_OUT_LINES: 0; ZERO_ERRORS_LINES: 1; DLL_ADVANCED: True +- CONFIRMING-RUN-EXIT: 0 +- RUNSETTINGS-HASH-NOW: 199408CA53CE4E12AE1A894FC66A0926124F3AC0D6447BD93B0C121338297FFA +- Acceptance: REVERT-DIFF-EXIT 0; porcelain empty; TOKEN action(); = 1 and SHA256 equal to FIX-HASH-HELPER; CSC_OUT_LINES at least 1 with DLL_ADVANCED True; CONFIRMING-RUN-EXIT 0 with total 4, passed 4, all four RESULT lines Passed - HOLD. + + COUNTERS total=4 executed=4 passed=4 failed=0 + RESULT_COUNT: 4 + RESULT InitializeBreadcrumbPipeline_NullOwningDispatcher_DoesNotThrow = Passed + RESULT ConfigureBreadcrumbDropDown_WorkerThread_ThrowsBoundaryDiagnostic = Passed + RESULT Dispatcher_OwnerOnlyWorker_ReportsWithoutRunningAction = Passed + RESULT InitializeBreadcrumbPipeline_WorkerThread_ThrowsBoundaryDiagnostic = Passed diff --git a/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/regression-testing/mutation-null-owner-escape.md b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/regression-testing/mutation-null-owner-escape.md new file mode 100644 index 000000000..744283823 --- /dev/null +++ b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/regression-testing/mutation-null-owner-escape.md @@ -0,0 +1,58 @@ +# Negative Control M2: Null-Owner Escape Replaced by the Pre-#781 Context-Reference Throw + +Timestamp: 2026-09-29T09-24 +Command: (1) CMD-CENSUS with PATH = QuickFiler/Viewers/ItemViewer.Breadcrumb.cs; (2) msbuild QuickFiler.Test\QuickFiler.Test.csproj /t:Build /m /p:Configuration=Debug /p:Platform=AnyCPU (CMD-BUILD-QF, TASKID p3-t3), resolved through vswhere, plus /nodeReuse:false; (3) vstest.console.exe QuickFiler.Test\bin\Debug\QuickFiler.Test.dll "/Settings:TaskMaster.runsettings" /InIsolation "/TestCaseFilter:FullyQualifiedName~InitializeBreadcrumbPipeline_NullOwningDispatcher_DoesNotThrow" "/ResultsDirectory:coverage\test-results\931\p3-t3" "/Logger:trx;LogFileName=p3-t3.trx" "/Blame:CollectHangDump;TestTimeout=4min;HangDumpType=None" (CMD-VSTEST, resolved through vswhere) +EXIT_CODE: 1 +ExpectedExitCode: 1 + +Output Summary: +- The EXIT_CODE row is scoped to the mutated run (the VSTEST_EXIT_CODE of step 3); the expected failure is the control's purpose. +- Predicted failure (D-6) observed: the test Failed at the captured null assertion with a MESSAGE containing both "InvalidOperationException" and "but found". +- Acceptance: census value 1; MSBUILD_EXIT_CODE 0 and PROD_CSC_OUT_LINES 2; total 1, failed 1; MESSAGE contains both phrases (no NullReferenceException); RUNSETTINGS-HASH-NOW equals RUNSETTINGS-HASH - HOLD. MUTATION PREDICTION MISMATCH did not fire. + +## Mutated run + +- Mutated file: QuickFiler/Viewers/ItemViewer.Breadcrumb.cs +- Hunk: lines 435 to 438 (the if (owning == null) block); line 437 (return;) replaced by the four lines if (!ReferenceEquals(System.Threading.SynchronizationContext.Current, UiSyncContext)) / { / throw new InvalidOperationException("mutation 931: pre-781 context-reference guard"); / } followed by return; (applied with the Edit tool) +- TOKEN System.Threading.SynchronizationContext.Current = 1 (P0-T12 value 0) +- Mutated-file census, other non-zero tokens: partial class 1; LINES = 464; SHA256 = 3131C371FD6AC78AC6DEFE7775F9515DAD975F984F2C5F2CAFBAED1451726F9C +- MSBUILD_EXIT_CODE: 0 +- CSC_OUT_LINES: 2 +- PROD_CSC_OUT_LINES: 2 +- ZERO_ERRORS_LINES: 1 +- DLL_ADVANCED: True +- Runsettings file: TaskMaster.runsettings +- Isolation switch: /InIsolation +- Filter: "/TestCaseFilter:FullyQualifiedName~InitializeBreadcrumbPipeline_NullOwningDispatcher_DoesNotThrow" +- RUNSETTINGS-HASH-NOW: 199408CA53CE4E12AE1A894FC66A0926124F3AC0D6447BD93B0C121338297FFA +- VSTEST_EXIT_CODE: 1 +- TRX_PRESENT: True +- SEQUENCE_FILES: 0 +- COLLECTOR_LINES: 0 + + COUNTERS total=1 executed=1 passed=0 failed=1 + RESULT_COUNT: 1 + RESULT InitializeBreadcrumbPipeline_NullOwningDispatcher_DoesNotThrow = Failed + MESSAGE InitializeBreadcrumbPipeline_NullOwningDispatcher_DoesNotThrow :: Expected captured to be because a viewer with no owning dispatcher has no boundary to enforce and must stay inert, but found System.InvalidOperationException: mutation 931: pre-781 context-reference guard + at QuickFiler.ItemViewer.ThrowIfOffUiBoundary(String operation) in \QuickFiler\Viewers\ItemViewer.Breadcrumb.cs:line 441 + at QuickFiler.ItemViewer.InitializeBreadcrumbPipeline(IFolderHierarchyProvider provider, BreadcrumbPopupUiOperations operations) in \QuickFiler\Viewers\ItemViewer.Breadcrumb.cs:line 51 + at QuickFiler.Test.Viewers.ItemViewerBreadcrumbThreadAffinityTests.<>c__DisplayClass10_1.b__0() in \QuickFiler.Test\Viewers\ItemViewerBreadcrumbThreadAffinityTests.Part2.cs:line 172 + at QuickFiler.Test.TestSupport.DedicatedWorkerThread.<>c__DisplayClass0_0.b__0() in \QuickFiler.Test\TestSupport\DedicatedWorkerThread.cs:line 35. + +## Revert and confirming run + +- Recorded at: 2026-09-29T09-25 +- Commands: git checkout -- QuickFiler\Viewers\ItemViewer.Breadcrumb.cs; git diff --exit-code HEAD -- QuickFiler\Viewers\ItemViewer.Breadcrumb.cs; git status --porcelain -- QuickFiler\Viewers\ItemViewer.Breadcrumb.cs; CMD-CENSUS on the file; CMD-BUILD-QF (TASKID p3-t4); CMD-VSTEST with ASSEMBLY-QF, FILTER-NULLOWNER, NAMES-FOUR, TASKID p3-t4 +- REVERT-DIFF-EXIT: 0 +- Porcelain output: EMPTY +- TOKEN System.Threading.SynchronizationContext.Current = 0 +- SHA256: 928E466A8C4C0D69E1CDF1577E3BF42BBC7E86BB6AA88C21B06F7A96C2F12CCF (P0-T12 SHA256 of the same file: 928E466A8C4C0D69E1CDF1577E3BF42BBC7E86BB6AA88C21B06F7A96C2F12CCF; equal) +- LINES = 460 (P0-T12: 460) +- MSBUILD_EXIT_CODE: 0; CSC_OUT_LINES: 2; PROD_CSC_OUT_LINES: 2; ZERO_ERRORS_LINES: 1; DLL_ADVANCED: True +- CONFIRMING-RUN-EXIT: 0 +- RUNSETTINGS-HASH-NOW: 199408CA53CE4E12AE1A894FC66A0926124F3AC0D6447BD93B0C121338297FFA +- Acceptance: REVERT-DIFF-EXIT 0; porcelain empty; census 0; SHA256 equal to P0-T12; PROD_CSC_OUT_LINES at least 1; CONFIRMING-RUN-EXIT 0 with total 1, passed 1, RESULT Passed - HOLD. + + COUNTERS total=1 executed=1 passed=1 failed=0 + RESULT_COUNT: 1 + RESULT InitializeBreadcrumbPipeline_NullOwningDispatcher_DoesNotThrow = Passed diff --git a/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/regression-testing/mutation-openread-sentinel.md b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/regression-testing/mutation-openread-sentinel.md new file mode 100644 index 000000000..a40fff3ce --- /dev/null +++ b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/regression-testing/mutation-openread-sentinel.md @@ -0,0 +1,93 @@ +# Negative Control M4: OpenRead Sentinel Replaced + +Timestamp: 2026-09-29T09-29 +Command: (1) CMD-CENSUS with PATH = UtilitiesCS.Test/HelperClasses/FileInfoWrapper_Tests.cs; (2) msbuild UtilitiesCS.Test\UtilitiesCS.Test.csproj /t:Build /m /p:Configuration=Debug /p:Platform=AnyCPU (CMD-BUILD-UCS, TASKID p3-t7), resolved through vswhere, plus /nodeReuse:false; (3) vstest.console.exe UtilitiesCS.Test\bin\Debug\UtilitiesCS.Test.dll "/Settings:TaskMaster.runsettings" /InIsolation "/TestCaseFilter:FullyQualifiedName~OpenRead_ShouldReturnReadableStreamForWrappedFile" "/ResultsDirectory:coverage\test-results\931\p3-t7" "/Logger:trx;LogFileName=p3-t7.trx" "/Blame:CollectHangDump;TestTimeout=4min;HangDumpType=None" (CMD-VSTEST, resolved through vswhere) +EXIT_CODE: 1 +ExpectedExitCode: 1 + +Output Summary: +- The EXIT_CODE row is scoped to the mutated run (the VSTEST_EXIT_CODE of step 3); the expected failure is the control's purpose. +- Predicted failure (D-6) observed: the test Failed at the same-instance assertion with a MESSAGE containing "to refer to". +- Acceptance: TOKEN .Returns(decoy) = 1, TOKEN Assembly.Location = 8, TOKEN BeSameAs(sentinel) = 1; MSBUILD_EXIT_CODE 0 and CSC_OUT_LINES 2; total 1, failed 1; MESSAGE contains "to refer to"; RUNSETTINGS-HASH-NOW equals RUNSETTINGS-HASH - HOLD. MUTATION PREDICTION MISMATCH did not fire. + +## Mutated run + +- Mutated file: UtilitiesCS.Test/HelperClasses/FileInfoWrapper_Tests.cs +- Hunk: in OpenRead_ShouldReturnReadableStreamForWrappedFile, a second six-line using var decoy = new FileStream( declaration with the same four arguments inserted immediately after the sentinel declaration, and .Returns(sentinel) changed to .Returns(decoy) (applied with the Edit tool) +- TOKEN .Returns(decoy) = 1 +- TOKEN Assembly.Location = 8 +- TOKEN BeSameAs(sentinel) = 1 +- Mutated-file census, other non-zero tokens: [TestMethod] 8, .Should() 53, FileMode. 14, FileMode.Open 14, FileAccess. 12, FileAccess.Read 12, FileShare.ReadWrite 10, FixturePath 4, using var sentinel = new FileStream( 1, wrapper.OpenRead() 2, stream.CanRead.Should().BeTrue() 1, stream.Length.Should().BeGreaterThan(0) 1; LINES = 363 (six more than 357); SHA256 = 4521642F719F7324B66F82FDC23C57CECAA1FE778168175AC6D1BA28034C4C54 +- MSBUILD_EXIT_CODE: 0 +- CSC_OUT_LINES: 2 +- PROD_CSC_OUT_LINES: 0 (observation; no production file changed) +- ZERO_ERRORS_LINES: 1 +- DLL_ADVANCED: True +- Runsettings file: TaskMaster.runsettings +- Isolation switch: /InIsolation +- Filter: "/TestCaseFilter:FullyQualifiedName~OpenRead_ShouldReturnReadableStreamForWrappedFile" +- RUNSETTINGS-HASH-NOW: 199408CA53CE4E12AE1A894FC66A0926124F3AC0D6447BD93B0C121338297FFA +- VSTEST_EXIT_CODE: 1 +- TRX_PRESENT: True +- SEQUENCE_FILES: 0 +- COLLECTOR_LINES: 0 + + COUNTERS total=1 executed=1 passed=0 failed=1 + RESULT_COUNT: 1 + RESULT OpenRead_ShouldReturnReadableStreamForWrappedFile = Failed + MESSAGE OpenRead_ShouldReturnReadableStreamForWrappedFile :: Expected stream to refer to System.IO.FileStream + { + CanRead = True, + CanSeek = True, + CanTimeout = False, + CanWrite = False, + Handle = 4372, + IsAsync = False, + Length = 4150272L, + Name = "\UtilitiesCS.Test\bin\Debug\UtilitiesCS.Test.dll", + Position = 0L, + ReadTimeout = "[Member 'ReadTimeout' threw an exception: 'Timeouts are not supported on this stream.']", + SafeFileHandle = Microsoft.Win32.SafeHandles.SafeFileHandle + { + IsClosed = False, + IsInvalid = False + }, + WriteTimeout = "[Member 'WriteTimeout' threw an exception: 'Timeouts are not supported on this stream.']" + }, but found System.IO.FileStream + { + CanRead = True, + CanSeek = True, + CanTimeout = False, + CanWrite = False, + Handle = 4376, + IsAsync = False, + Length = 4150272L, + Name = "\UtilitiesCS.Test\bin\Debug\UtilitiesCS.Test.dll", + Position = 0L, + ReadTimeout = "[Member 'ReadTimeout' threw an exception: 'Timeouts are not supported on this stream.']", + SafeFileHandle = Microsoft.Win32.SafeHandles.SafeFileHandle + { + IsClosed = False, + IsInvalid = False + }, + WriteTimeout = "[Member 'WriteTimeout' threw an exception: 'Timeouts are not supported on this stream.']" + }. + +## Revert and confirming run + +- Recorded at: 2026-09-29T09-30 +- Commands: git checkout -- UtilitiesCS.Test/HelperClasses/FileInfoWrapper_Tests.cs; git diff --exit-code HEAD -- UtilitiesCS.Test/HelperClasses/FileInfoWrapper_Tests.cs; git status --porcelain -- UtilitiesCS.Test/HelperClasses/FileInfoWrapper_Tests.cs; CMD-CENSUS on the file; CMD-BUILD-UCS (TASKID p3-t8); CMD-VSTEST with ASSEMBLY-UCS, FILTER-OPENREAD, NAMES-FIW, TASKID p3-t8 +- REVERT-DIFF-EXIT: 0 +- Porcelain output: EMPTY +- TOKEN .Returns(decoy) = 0 +- TOKEN Assembly.Location = 7 +- SHA256: 142BCB8E95D710890A71F325A65801ED8EB53A87A339B10018D889491ECEF596 (FIX-HASH-FIW: 142BCB8E95D710890A71F325A65801ED8EB53A87A339B10018D889491ECEF596; equal) +- LINES = 357 +- MSBUILD_EXIT_CODE: 0; CSC_OUT_LINES: 2; PROD_CSC_OUT_LINES: 0; ZERO_ERRORS_LINES: 1; DLL_ADVANCED: True +- CONFIRMING-RUN-EXIT: 0 +- RUNSETTINGS-HASH-NOW: 199408CA53CE4E12AE1A894FC66A0926124F3AC0D6447BD93B0C121338297FFA +- Acceptance: REVERT-DIFF-EXIT 0; porcelain empty; .Returns(decoy) 0, Assembly.Location 7 and SHA256 equal to FIX-HASH-FIW; CSC_OUT_LINES at least 1 with DLL_ADVANCED True; CONFIRMING-RUN-EXIT 0 with total 1, passed 1, RESULT Passed - HOLD. + + COUNTERS total=1 executed=1 passed=1 failed=0 + RESULT_COUNT: 1 + RESULT OpenRead_ShouldReturnReadableStreamForWrappedFile = Passed diff --git a/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/regression-testing/mutation-owner-only-dispatcher-guard.md b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/regression-testing/mutation-owner-only-dispatcher-guard.md new file mode 100644 index 000000000..b5fb30e03 --- /dev/null +++ b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/regression-testing/mutation-owner-only-dispatcher-guard.md @@ -0,0 +1,55 @@ +# Negative Control M1: Owner-Only Dispatcher Guard Disabled + +Timestamp: 2026-09-29T09-22 +Command: (1) CMD-CENSUS with PATH = QuickFiler/Viewers/BreadcrumbUiDispatcher.cs; (2) msbuild QuickFiler.Test\QuickFiler.Test.csproj /t:Build /m /p:Configuration=Debug /p:Platform=AnyCPU (CMD-BUILD-QF, TASKID p3-t1), resolved through vswhere, plus /nodeReuse:false; (3) vstest.console.exe QuickFiler.Test\bin\Debug\QuickFiler.Test.dll "/Settings:TaskMaster.runsettings" /InIsolation "/TestCaseFilter:FullyQualifiedName~Dispatcher_OwnerOnlyWorker_ReportsWithoutRunningAction" "/ResultsDirectory:coverage\test-results\931\p3-t1" "/Logger:trx;LogFileName=p3-t1.trx" "/Blame:CollectHangDump;TestTimeout=4min;HangDumpType=None" (CMD-VSTEST, resolved through vswhere) +EXIT_CODE: 1 +ExpectedExitCode: 1 + +Output Summary: +- The EXIT_CODE row is scoped to the mutated run (the VSTEST_EXIT_CODE of step 3); the expected failure is the control's purpose. +- Predicted failure (D-6) observed: the test Failed with a MESSAGE containing "to be 0, but found 1". +- Acceptance: census transitions hold; MSBUILD_EXIT_CODE 0 and PROD_CSC_OUT_LINES 2; total 1, failed 1; MESSAGE contains the predicted phrase; RUNSETTINGS-HASH-NOW equals RUNSETTINGS-HASH - HOLD. MUTATION PREDICTION MISMATCH did not fire. + +## Mutated run + +- Mutated file: QuickFiler/Viewers/BreadcrumbUiDispatcher.cs +- Hunk: lines 276 to 277 (return _ownerThreadId.HasValue / && Environment.CurrentManagedThreadId == _ownerThreadId.Value;) replaced by the single line return true; (applied with the Edit tool) +- TOKEN return true; = 2 (N1 plus 1; P0-T12 N1 = 1) +- TOKEN _ownerThreadId.HasValue = 0 (N2 minus 1; P0-T12 N2 = 1) +- Mutated-file census, other non-zero tokens: action(); 2, ownerThreadId 5, cannot marshal 2; LINES = 284; SHA256 = 847DBDC8B416FF98C3D3F8599FCFFE6CE1CB106F8FF75C623C7DB06F47E0FF6D +- MSBUILD_EXIT_CODE: 0 +- CSC_OUT_LINES: 2 +- PROD_CSC_OUT_LINES: 2 +- ZERO_ERRORS_LINES: 1 +- DLL_ADVANCED: True +- Runsettings file: TaskMaster.runsettings +- Isolation switch: /InIsolation +- Filter: "/TestCaseFilter:FullyQualifiedName~Dispatcher_OwnerOnlyWorker_ReportsWithoutRunningAction" +- RUNSETTINGS-HASH-NOW: 199408CA53CE4E12AE1A894FC66A0926124F3AC0D6447BD93B0C121338297FFA +- VSTEST_EXIT_CODE: 1 +- TRX_PRESENT: True +- SEQUENCE_FILES: 0 +- COLLECTOR_LINES: 0 + + COUNTERS total=1 executed=1 passed=0 failed=1 + RESULT_COUNT: 1 + RESULT Dispatcher_OwnerOnlyWorker_ReportsWithoutRunningAction = Failed + MESSAGE Dispatcher_OwnerOnlyWorker_ReportsWithoutRunningAction :: Expected executions to be 0, but found 1 (difference of 1). + +## Revert and confirming run + +- Recorded at: 2026-09-29T09-23 +- Commands: git checkout -- QuickFiler\Viewers\BreadcrumbUiDispatcher.cs; git diff --exit-code HEAD -- QuickFiler\Viewers\BreadcrumbUiDispatcher.cs; git status --porcelain -- QuickFiler\Viewers\BreadcrumbUiDispatcher.cs; CMD-CENSUS on the file; CMD-BUILD-QF (TASKID p3-t2); CMD-VSTEST with ASSEMBLY-QF, FILTER-DISPATCHER, NAMES-FOUR, TASKID p3-t2 +- REVERT-DIFF-EXIT: 0 +- Porcelain output: EMPTY +- TOKEN return true; = 1 (P0-T12 N1 = 1) +- TOKEN _ownerThreadId.HasValue = 1 (P0-T12 N2 = 1) +- SHA256: 0764D49C8747276722853BF30FE32ACA133CB19A3D634A9CDA351217FD49017E (P0-T12 SHA256 of the same file: 0764D49C8747276722853BF30FE32ACA133CB19A3D634A9CDA351217FD49017E; equal) +- MSBUILD_EXIT_CODE: 0; CSC_OUT_LINES: 2; PROD_CSC_OUT_LINES: 2; ZERO_ERRORS_LINES: 1; DLL_ADVANCED: True +- CONFIRMING-RUN-EXIT: 0 +- RUNSETTINGS-HASH-NOW: 199408CA53CE4E12AE1A894FC66A0926124F3AC0D6447BD93B0C121338297FFA +- Acceptance: REVERT-DIFF-EXIT 0; porcelain empty; both census values equal P0-T12; SHA256 equal to P0-T12; PROD_CSC_OUT_LINES at least 1; CONFIRMING-RUN-EXIT 0 with total 1, passed 1, RESULT Passed - HOLD. + + COUNTERS total=1 executed=1 passed=1 failed=0 + RESULT_COUNT: 1 + RESULT Dispatcher_OwnerOnlyWorker_ReportsWithoutRunningAction = Passed diff --git a/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/regression-testing/p2-t1-helper-census.2026-09-29T09-10.md b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/regression-testing/p2-t1-helper-census.2026-09-29T09-10.md new file mode 100644 index 000000000..f67cd4207 --- /dev/null +++ b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/regression-testing/p2-t1-helper-census.2026-09-29T09-10.md @@ -0,0 +1,78 @@ +# P2-T1 Helper Census + +Timestamp: 2026-09-29T09-10 +Command: CMD-CENSUS with PATH = QuickFiler.Test/TestSupport/DedicatedWorkerThread.cs (pwsh -NoProfile -Command payload; case-sensitive non-overlapping occurrence counts) +EXIT_CODE: 0 + +Output Summary: +- File created with exactly the Target Source A content (Write tool; no PreToolUse refusal). +- Acceptance: namespace QuickFiler.Test.TestSupport 1, internal static class DedicatedWorkerThread 1, internal static Exception Run(Action action) 1, new Thread( 1, IsBackground = true 1, thread.Join(); 1, action(); 1, distinct from every live thread by construction 1 - HOLD. +- Join( 2 equals Join() 2 - HOLDS. +- .Should() 0, Task.Run( 0, Thread.Sleep 0, Task.Delay 0, [Timeout 0, DoNotParallelize 0, Retry 0 - HOLD. +- LINES 48 (at most 500) - HOLDS. + +## Census + + FILE QuickFiler.Test/TestSupport/DedicatedWorkerThread.cs + TOKEN Task.Run( = 0 + TOKEN .GetAwaiter() = 0 + TOKEN DedicatedWorkerThread.Run( = 0 + TOKEN RunOnDedicatedWorkerThread = 0 + TOKEN ClearViewerDispatcher( = 0 + TOKEN partial class = 0 + TOKEN [TestMethod] = 0 + TOKEN action(); = 1 + TOKEN new Thread( = 1 + TOKEN IsBackground = true = 1 + TOKEN thread.Join(); = 1 + TOKEN Join( = 2 + TOKEN Join() = 2 + TOKEN using System.Reflection; = 0 + TOKEN using QuickFiler.Test.TestSupport; = 0 + TOKEN namespace QuickFiler.Test.TestSupport = 1 + TOKEN internal static class DedicatedWorkerThread = 1 + TOKEN internal static Exception Run(Action action) = 1 + TOKEN .Should() = 0 + TOKEN dedicated worker thread must not be = 0 + TOKEN distinct from every live thread by construction = 1 + TOKEN unconditionally = 0 + TOKEN owner.CheckAccess() = 0 + TOKEN .NotBe( = 0 + TOKEN ownerThreadId = 0 + TOKEN .BeNull( = 0 + TOKEN executions.Should().Be(0) = 0 + TOKEN cannot marshal = 0 + TOKEN GetSolutionFile = 0 + TOKEN TaskMaster.sln = 0 + TOKEN AppDomain = 0 + TOKEN File.Exists( = 0 + TOKEN File.Create = 0 + TOKEN File.WriteAll = 0 + TOKEN File.Delete = 0 + TOKEN Path.GetTemp = 0 + TOKEN FileMode. = 0 + TOKEN FileMode.Open = 0 + TOKEN FileAccess. = 0 + TOKEN FileAccess.Read = 0 + TOKEN FileShare.ReadWrite = 0 + TOKEN Assembly.Location = 0 + TOKEN FixturePath = 0 + TOKEN using var sentinel = new FileStream( = 0 + TOKEN BeSameAs(sentinel) = 0 + TOKEN .Returns(decoy) = 0 + TOKEN wrapper.OpenRead() = 0 + TOKEN stream.CanRead.Should().BeTrue() = 0 + TOKEN stream.Length.Should().BeGreaterThan(0) = 0 + TOKEN return true; = 0 + TOKEN _ownerThreadId.HasValue = 0 + TOKEN System.Threading.SynchronizationContext.Current = 0 + TOKEN Include="Viewers\ItemViewerBreadcrumbThreadAffinityTests.cs" = 0 + TOKEN Include="Viewers\ItemViewerBreadcrumbThreadAffinityTests.Part2.cs" = 0 + TOKEN Include="TestSupport\DedicatedWorkerThread.cs" = 0 + TOKEN Thread.Sleep = 0 + TOKEN Task.Delay = 0 + TOKEN [Timeout = 0 + TOKEN DoNotParallelize = 0 + TOKEN Retry = 0 + LINES = 48 + SHA256 = 309FC3119F39B08EFE766176DC4D33C44342896B6197DB09D986F53EDBAD59C8 diff --git a/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/regression-testing/p2-t10-fix-commit.2026-09-29T09-21.md b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/regression-testing/p2-t10-fix-commit.2026-09-29T09-21.md new file mode 100644 index 000000000..45029ad39 --- /dev/null +++ b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/regression-testing/p2-t10-fix-commit.2026-09-29T09-21.md @@ -0,0 +1,56 @@ +# P2-T10 Fix Commit + +Timestamp: 2026-09-29T09-21 +Command: git add -- QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.cs QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.Part2.cs QuickFiler.Test/TestSupport/DedicatedWorkerThread.cs QuickFiler.Test/Viewers/BreadcrumbPopupBoundaryCoverageTests.cs QuickFiler.Test/QuickFiler.Test.csproj UtilitiesCS.Test/HelperClasses/FileInfoWrapper_Tests.cs docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931; git commit -m "test(931): remove scheduler and file-handle dependence from breadcrumb affinity and FileInfoWrapper tests" (plus the session attribution paragraphs Co-Authored-By and Claude-Session in bare form); git ls-files --eol -- (the six code paths) (before and after); git diff --exit-code HEAD -- QuickFiler.Test UtilitiesCS.Test; git show --name-only --format= HEAD; git rev-parse HEAD; Get-FileHash -Algorithm SHA256 of the six code files +EXIT_CODE: 0 + +Output Summary: +- EXIT_CODE is scoped to the git diff --exit-code HEAD -- QuickFiler.Test UtilitiesCS.Test span (exit 0: the committed state equals the working tree for both test projects). +- COMMIT-EXIT: 0 (no PreToolUse refusal) +- FIX-HEAD: 2956820e1be115c9e473e2be2c083ff60a0f08a8 +- EOL-RESTORED: NONE (every entry already read w/crlf; the P2-T7 scoped format had converted the two new files to CRLF) +- FIX-HASH-AFF: BAD939900B47AE0AC963D203F621D28C959CC7F3E492119F1BD8272197EF5940 +- FIX-HASH-PART2: D6A35090996E48283CA04D8EE2C441F8ED8946BAECC1D06D9F16931FF767976E +- FIX-HASH-HELPER: 986838E4FD72A7E233B26A1BE03B5912DFBB4016980F515F5C01680A0ABF688E +- FIX-HASH-BND: 4F8B8AFFA9387043AAE3A66EF2028D96AB4F0FA77AFCE244C9044E67B9B8E0FE +- FIX-HASH-CSPROJ: 5538F090CDBC43F57E03000C1776D9881E147302E513D1D03D58C6102E4DC39F +- FIX-HASH-FIW: 142BCB8E95D710890A71F325A65801ED8EB53A87A339B10018D889491ECEF596 +- Each of the five .cs hashes equals its P2-T7 (ITERATION: 1) post-format hash; no path was restored. +- Acceptance: COMMIT-EXIT 0; EXIT_CODE 0; git show lists exactly the six code paths plus paths under the feature folder; every EOL-AFTER entry reads i/lf and w/crlf - HOLD. + +## EOL-BEFORE + + i/lf w/crlf attr/text=auto QuickFiler.Test/QuickFiler.Test.csproj + i/lf w/crlf attr/text=auto QuickFiler.Test/TestSupport/DedicatedWorkerThread.cs + i/lf w/crlf attr/text=auto QuickFiler.Test/Viewers/BreadcrumbPopupBoundaryCoverageTests.cs + i/lf w/crlf attr/text=auto QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.Part2.cs + i/lf w/crlf attr/text=auto QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.cs + i/lf w/crlf attr/text=auto UtilitiesCS.Test/HelperClasses/FileInfoWrapper_Tests.cs + +## EOL-AFTER + + i/lf w/crlf attr/text=auto QuickFiler.Test/QuickFiler.Test.csproj + i/lf w/crlf attr/text=auto QuickFiler.Test/TestSupport/DedicatedWorkerThread.cs + i/lf w/crlf attr/text=auto QuickFiler.Test/Viewers/BreadcrumbPopupBoundaryCoverageTests.cs + i/lf w/crlf attr/text=auto QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.Part2.cs + i/lf w/crlf attr/text=auto QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.cs + i/lf w/crlf attr/text=auto UtilitiesCS.Test/HelperClasses/FileInfoWrapper_Tests.cs + +## git show --name-only --format= HEAD + + QuickFiler.Test/QuickFiler.Test.csproj + QuickFiler.Test/TestSupport/DedicatedWorkerThread.cs + QuickFiler.Test/Viewers/BreadcrumbPopupBoundaryCoverageTests.cs + QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.Part2.cs + QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.cs + UtilitiesCS.Test/HelperClasses/FileInfoWrapper_Tests.cs + docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/regression-testing/p2-t1-helper-census.2026-09-29T09-10.md + docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/regression-testing/p2-t2-csproj-census.2026-09-29T09-11.md + docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/regression-testing/p2-t3-part2-census.2026-09-29T09-12.md + docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/regression-testing/p2-t4-primary-census.2026-09-29T09-13.md + docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/regression-testing/p2-t5-boundary-census.2026-09-29T09-14.md + docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/regression-testing/p2-t6-fileinfowrapper-census.2026-09-29T09-15.md + docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/regression-testing/p2-t7-csharpier-scoped.2026-09-29T09-16.md + docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/regression-testing/p2-t8-build-after-fix.2026-09-29T09-18.md + docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/regression-testing/p2-t9-pass-before-controls.2026-09-29T09-20.md + docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/plan.2026-09-28T20-01.md diff --git a/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/regression-testing/p2-t2-csproj-census.2026-09-29T09-11.md b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/regression-testing/p2-t2-csproj-census.2026-09-29T09-11.md new file mode 100644 index 000000000..a4d1eff5c --- /dev/null +++ b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/regression-testing/p2-t2-csproj-census.2026-09-29T09-11.md @@ -0,0 +1,27 @@ +# P2-T2 Project-File Census + +Timestamp: 2026-09-29T09-11 +Command: CMD-CENSUS with PATH = QuickFiler.Test/QuickFiler.Test.csproj; git diff --numstat HEAD -- QuickFiler.Test/QuickFiler.Test.csproj; git status --porcelain -- QuickFiler.Test/QuickFiler.Test.csproj +EXIT_CODE: 0 + +Output Summary: +- Edits applied exactly as Target Source F states (Edit tool; no PreToolUse refusal): the Part2 entry inserted immediately after the primary affinity entry (line 98), the helper entry inserted immediately after TestSupport\WinFormsPumpHost.cs. +- INCLUDE-AFFINITY 1, INCLUDE-PART2 1, INCLUDE-HELPER 1 - HOLD. +- numstat: 2 added, 0 deleted - HOLDS. +- porcelain begins " M" for the project file - HOLDS. + +## Project-file TOKEN lines + + TOKEN Include="Viewers\ItemViewerBreadcrumbThreadAffinityTests.cs" = 1 + TOKEN Include="Viewers\ItemViewerBreadcrumbThreadAffinityTests.Part2.cs" = 1 + TOKEN Include="TestSupport\DedicatedWorkerThread.cs" = 1 + LINES = 570 + SHA256 = 5538F090CDBC43F57E03000C1776D9881E147302E513D1D03D58C6102E4DC39F + +## numstat + + 2 0 QuickFiler.Test/QuickFiler.Test.csproj + +## porcelain + + M QuickFiler.Test/QuickFiler.Test.csproj diff --git a/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/regression-testing/p2-t3-part2-census.2026-09-29T09-12.md b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/regression-testing/p2-t3-part2-census.2026-09-29T09-12.md new file mode 100644 index 000000000..f175ad800 --- /dev/null +++ b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/regression-testing/p2-t3-part2-census.2026-09-29T09-12.md @@ -0,0 +1,78 @@ +# P2-T3 Part2 Census + +Timestamp: 2026-09-29T09-12 +Command: CMD-CENSUS with PATH = QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.Part2.cs (pwsh -NoProfile -Command payload) +EXIT_CODE: 0 + +Output Summary: +- File created with the Target Source B content (Write tool; no PreToolUse refusal): the two moved tests copied verbatim from pre-edit lines 199 to 251 and 253 to 304 of the primary file with the remark substitutions at pre-edit lines 204 and 259 and the call substitutions at pre-edit lines 228 and 277, the rewritten null-owner test, and ClearViewerDispatcher copied verbatim from pre-edit lines 357 to 371. +- DedicatedWorkerThread.Run( 3, ClearViewerDispatcher( 2, [TestMethod] 3, dedicated worker thread must not be 3 - HOLD. +- partial class 1, using QuickFiler.Test.TestSupport; 1, using System.Reflection; 1, owner.CheckAccess() 1, .BeNull( 1, unconditionally 1 - HOLD. +- Task.Run( 0, .GetAwaiter() 0, RunOnDedicatedWorkerThread 0, action(); 0, Thread.Sleep 0, Task.Delay 0, [Timeout 0, DoNotParallelize 0, Retry 0 - HOLD. +- LINES 202 (at most 500) - HOLDS. + +## Census + + FILE QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.Part2.cs + TOKEN Task.Run( = 0 + TOKEN .GetAwaiter() = 0 + TOKEN DedicatedWorkerThread.Run( = 3 + TOKEN RunOnDedicatedWorkerThread = 0 + TOKEN ClearViewerDispatcher( = 2 + TOKEN partial class = 1 + TOKEN [TestMethod] = 3 + TOKEN action(); = 0 + TOKEN new Thread( = 0 + TOKEN IsBackground = true = 0 + TOKEN thread.Join(); = 0 + TOKEN Join( = 2 + TOKEN Join() = 2 + TOKEN using System.Reflection; = 1 + TOKEN using QuickFiler.Test.TestSupport; = 1 + TOKEN namespace QuickFiler.Test.TestSupport = 0 + TOKEN internal static class DedicatedWorkerThread = 0 + TOKEN internal static Exception Run(Action action) = 0 + TOKEN .Should() = 15 + TOKEN dedicated worker thread must not be = 3 + TOKEN distinct from every live thread by construction = 0 + TOKEN unconditionally = 1 + TOKEN owner.CheckAccess() = 1 + TOKEN .NotBe( = 0 + TOKEN ownerThreadId = 0 + TOKEN .BeNull( = 1 + TOKEN executions.Should().Be(0) = 0 + TOKEN cannot marshal = 0 + TOKEN GetSolutionFile = 0 + TOKEN TaskMaster.sln = 0 + TOKEN AppDomain = 0 + TOKEN File.Exists( = 0 + TOKEN File.Create = 0 + TOKEN File.WriteAll = 0 + TOKEN File.Delete = 0 + TOKEN Path.GetTemp = 0 + TOKEN FileMode. = 0 + TOKEN FileMode.Open = 0 + TOKEN FileAccess. = 0 + TOKEN FileAccess.Read = 0 + TOKEN FileShare.ReadWrite = 0 + TOKEN Assembly.Location = 0 + TOKEN FixturePath = 0 + TOKEN using var sentinel = new FileStream( = 0 + TOKEN BeSameAs(sentinel) = 0 + TOKEN .Returns(decoy) = 0 + TOKEN wrapper.OpenRead() = 0 + TOKEN stream.CanRead.Should().BeTrue() = 0 + TOKEN stream.Length.Should().BeGreaterThan(0) = 0 + TOKEN return true; = 0 + TOKEN _ownerThreadId.HasValue = 0 + TOKEN System.Threading.SynchronizationContext.Current = 0 + TOKEN Include="Viewers\ItemViewerBreadcrumbThreadAffinityTests.cs" = 0 + TOKEN Include="Viewers\ItemViewerBreadcrumbThreadAffinityTests.Part2.cs" = 0 + TOKEN Include="TestSupport\DedicatedWorkerThread.cs" = 0 + TOKEN Thread.Sleep = 0 + TOKEN Task.Delay = 0 + TOKEN [Timeout = 0 + TOKEN DoNotParallelize = 0 + TOKEN Retry = 0 + LINES = 202 + SHA256 = FB94BB94B65EA6F3C9E634DB2E0B5219CC003C3FF0F8565DB9C746BCA302B57D diff --git a/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/regression-testing/p2-t4-primary-census.2026-09-29T09-13.md b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/regression-testing/p2-t4-primary-census.2026-09-29T09-13.md new file mode 100644 index 000000000..208f79667 --- /dev/null +++ b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/regression-testing/p2-t4-primary-census.2026-09-29T09-13.md @@ -0,0 +1,87 @@ +# P2-T4 Primary Affinity File Census + +Timestamp: 2026-09-29T09-13 +Command: CMD-CENSUS with PATH = QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.cs; git diff --numstat HEAD -- QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.cs; git status --porcelain -- QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.cs +EXIT_CODE: 0 + +Output Summary: +- The five Target Source C edits applied bottom-up with the Edit tool (no PreToolUse refusal): (1) pre-edit lines 357 to 404 deleted; (2) pre-edit lines 199 to 347 deleted; (3) line 30 declaration made partial; (4) two remark lines inserted after line 27; (5) line 4 using System.Reflection; deleted. +- partial class 1 and [TestMethod] 4 - HOLD. +- Task.Run( 0, .GetAwaiter() 0, RunOnDedicatedWorkerThread 0, ClearViewerDispatcher( 0, action(); 0, new Thread( 0, IsBackground = true 0, thread.Join(); 0, using System.Reflection; 0, dedicated worker thread must not be 0, distinct from every live thread by construction 0, DedicatedWorkerThread.Run( 0 - HOLD. +- LINES 294 (at most 500 and at least 280) - HOLDS. +- numstat 3 added (at most 4) and 199 deleted (at least 197) - HOLDS. +- porcelain begins " M" - HOLDS. + +## Census + + FILE QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.cs + TOKEN Task.Run( = 0 + TOKEN .GetAwaiter() = 0 + TOKEN DedicatedWorkerThread.Run( = 0 + TOKEN RunOnDedicatedWorkerThread = 0 + TOKEN ClearViewerDispatcher( = 0 + TOKEN partial class = 1 + TOKEN [TestMethod] = 4 + TOKEN action(); = 0 + TOKEN new Thread( = 0 + TOKEN IsBackground = true = 0 + TOKEN thread.Join(); = 0 + TOKEN Join( = 0 + TOKEN Join() = 0 + TOKEN using System.Reflection; = 0 + TOKEN using QuickFiler.Test.TestSupport; = 0 + TOKEN namespace QuickFiler.Test.TestSupport = 0 + TOKEN internal static class DedicatedWorkerThread = 0 + TOKEN internal static Exception Run(Action action) = 0 + TOKEN .Should() = 10 + TOKEN dedicated worker thread must not be = 0 + TOKEN distinct from every live thread by construction = 0 + TOKEN unconditionally = 0 + TOKEN owner.CheckAccess() = 0 + TOKEN .NotBe( = 0 + TOKEN ownerThreadId = 0 + TOKEN .BeNull( = 0 + TOKEN executions.Should().Be(0) = 0 + TOKEN cannot marshal = 0 + TOKEN GetSolutionFile = 0 + TOKEN TaskMaster.sln = 0 + TOKEN AppDomain = 0 + TOKEN File.Exists( = 0 + TOKEN File.Create = 0 + TOKEN File.WriteAll = 0 + TOKEN File.Delete = 0 + TOKEN Path.GetTemp = 0 + TOKEN FileMode. = 0 + TOKEN FileMode.Open = 0 + TOKEN FileAccess. = 0 + TOKEN FileAccess.Read = 0 + TOKEN FileShare.ReadWrite = 0 + TOKEN Assembly.Location = 0 + TOKEN FixturePath = 0 + TOKEN using var sentinel = new FileStream( = 0 + TOKEN BeSameAs(sentinel) = 0 + TOKEN .Returns(decoy) = 0 + TOKEN wrapper.OpenRead() = 0 + TOKEN stream.CanRead.Should().BeTrue() = 0 + TOKEN stream.Length.Should().BeGreaterThan(0) = 0 + TOKEN return true; = 0 + TOKEN _ownerThreadId.HasValue = 0 + TOKEN System.Threading.SynchronizationContext.Current = 0 + TOKEN Include="Viewers\ItemViewerBreadcrumbThreadAffinityTests.cs" = 0 + TOKEN Include="Viewers\ItemViewerBreadcrumbThreadAffinityTests.Part2.cs" = 0 + TOKEN Include="TestSupport\DedicatedWorkerThread.cs" = 0 + TOKEN Thread.Sleep = 0 + TOKEN Task.Delay = 0 + TOKEN [Timeout = 0 + TOKEN DoNotParallelize = 0 + TOKEN Retry = 0 + LINES = 294 + SHA256 = BAD939900B47AE0AC963D203F621D28C959CC7F3E492119F1BD8272197EF5940 + +## numstat + + 3 199 QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.cs + +## porcelain + + M QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.cs diff --git a/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/regression-testing/p2-t5-boundary-census.2026-09-29T09-14.md b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/regression-testing/p2-t5-boundary-census.2026-09-29T09-14.md new file mode 100644 index 000000000..11dd311f9 --- /dev/null +++ b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/regression-testing/p2-t5-boundary-census.2026-09-29T09-14.md @@ -0,0 +1,83 @@ +# P2-T5 Boundary-Coverage File Census + +Timestamp: 2026-09-29T09-14 +Command: CMD-CENSUS with PATH = QuickFiler.Test/Viewers/BreadcrumbPopupBoundaryCoverageTests.cs; git status --porcelain -- QuickFiler.Test/Viewers/BreadcrumbPopupBoundaryCoverageTests.cs +EXIT_CODE: 0 + +Output Summary: +- Target Source D applied with the Edit tool (no PreToolUse refusal): pre-edit lines 52 to 62 replaced by the rewritten Dispatcher_OwnerOnlyWorker_ReportsWithoutRunningAction; using QuickFiler.Test.TestSupport; inserted between using Moq; and using QuickFiler.Viewers;. +- Task.Run( 0 (P0-T12: 1) and .GetAwaiter() 3 (P0-T12: 4), each one less - HOLD. +- DedicatedWorkerThread.Run( 1, using QuickFiler.Test.TestSupport; 1, .NotBe( 1, .BeNull( 1, dedicated worker thread must not be 1, ownerThreadId 2 - HOLD. +- executions.Should().Be(0) 1, cannot marshal 1, partial class 1, Thread.Sleep 0, Task.Delay 0, [Timeout 0, DoNotParallelize 0 - HOLD. +- LINES 386 (at most 500) - HOLDS. +- porcelain: " M QuickFiler.Test/Viewers/BreadcrumbPopupBoundaryCoverageTests.cs". + +## Census + + FILE QuickFiler.Test/Viewers/BreadcrumbPopupBoundaryCoverageTests.cs + TOKEN Task.Run( = 0 + TOKEN .GetAwaiter() = 3 + TOKEN DedicatedWorkerThread.Run( = 1 + TOKEN RunOnDedicatedWorkerThread = 0 + TOKEN ClearViewerDispatcher( = 0 + TOKEN partial class = 1 + TOKEN [TestMethod] = 5 + TOKEN action(); = 1 + TOKEN new Thread( = 0 + TOKEN IsBackground = true = 0 + TOKEN thread.Join(); = 0 + TOKEN Join( = 0 + TOKEN Join() = 0 + TOKEN using System.Reflection; = 1 + TOKEN using QuickFiler.Test.TestSupport; = 1 + TOKEN namespace QuickFiler.Test.TestSupport = 0 + TOKEN internal static class DedicatedWorkerThread = 0 + TOKEN internal static Exception Run(Action action) = 0 + TOKEN .Should() = 34 + TOKEN dedicated worker thread must not be = 1 + TOKEN distinct from every live thread by construction = 0 + TOKEN unconditionally = 0 + TOKEN owner.CheckAccess() = 0 + TOKEN .NotBe( = 1 + TOKEN ownerThreadId = 2 + TOKEN .BeNull( = 1 + TOKEN executions.Should().Be(0) = 1 + TOKEN cannot marshal = 1 + TOKEN GetSolutionFile = 0 + TOKEN TaskMaster.sln = 0 + TOKEN AppDomain = 0 + TOKEN File.Exists( = 0 + TOKEN File.Create = 0 + TOKEN File.WriteAll = 0 + TOKEN File.Delete = 0 + TOKEN Path.GetTemp = 0 + TOKEN FileMode. = 0 + TOKEN FileMode.Open = 0 + TOKEN FileAccess. = 0 + TOKEN FileAccess.Read = 0 + TOKEN FileShare.ReadWrite = 0 + TOKEN Assembly.Location = 0 + TOKEN FixturePath = 0 + TOKEN using var sentinel = new FileStream( = 0 + TOKEN BeSameAs(sentinel) = 0 + TOKEN .Returns(decoy) = 0 + TOKEN wrapper.OpenRead() = 0 + TOKEN stream.CanRead.Should().BeTrue() = 0 + TOKEN stream.Length.Should().BeGreaterThan(0) = 0 + TOKEN return true; = 1 + TOKEN _ownerThreadId.HasValue = 0 + TOKEN System.Threading.SynchronizationContext.Current = 0 + TOKEN Include="Viewers\ItemViewerBreadcrumbThreadAffinityTests.cs" = 0 + TOKEN Include="Viewers\ItemViewerBreadcrumbThreadAffinityTests.Part2.cs" = 0 + TOKEN Include="TestSupport\DedicatedWorkerThread.cs" = 0 + TOKEN Thread.Sleep = 0 + TOKEN Task.Delay = 0 + TOKEN [Timeout = 0 + TOKEN DoNotParallelize = 0 + TOKEN Retry = 0 + LINES = 386 + SHA256 = 4F8B8AFFA9387043AAE3A66EF2028D96AB4F0FA77AFCE244C9044E67B9B8E0FE + +## porcelain + + M QuickFiler.Test/Viewers/BreadcrumbPopupBoundaryCoverageTests.cs diff --git a/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/regression-testing/p2-t6-fileinfowrapper-census.2026-09-29T09-15.md b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/regression-testing/p2-t6-fileinfowrapper-census.2026-09-29T09-15.md new file mode 100644 index 000000000..b7567cea9 --- /dev/null +++ b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/regression-testing/p2-t6-fileinfowrapper-census.2026-09-29T09-15.md @@ -0,0 +1,96 @@ +# P2-T6 FileInfoWrapper Test File Census + +Timestamp: 2026-09-29T09-15 +Command: CMD-CENSUS with PATH = UtilitiesCS.Test/HelperClasses/FileInfoWrapper_Tests.cs; git diff -U0 HEAD -- UtilitiesCS.Test/HelperClasses/FileInfoWrapper_Tests.cs (added-line counts computed in the same pwsh payload over lines beginning with a single +); git status --porcelain -- UtilitiesCS.Test/HelperClasses/FileInfoWrapper_Tests.cs +EXIT_CODE: 0 + +Output Summary: +- The three Target Source E edits applied bottom-up with the Edit tool (no PreToolUse refusal): (1) pre-edit lines 338 to 357 deleted; (2) pre-edit lines 25 to 81 replaced by the four rewritten tests; (3) the FixturePath constant with its three-line comment and one blank line inserted after the class opening brace. +- GetSolutionFile 0, TaskMaster.sln 0, AppDomain 0, File.Exists( 0, File.Create 0, File.WriteAll 0, File.Delete 0, Path.GetTemp 0 - HOLD. +- FileMode. 13 = FileMode.Open 13; FileAccess. 11 = FileAccess.Read 11 - HOLD. +- FileShare.ReadWrite 9, Assembly.Location 7, FixturePath 4, using var sentinel = new FileStream( 1, BeSameAs(sentinel) 1, .Returns(decoy) 0, wrapper.OpenRead() 2, stream.CanRead.Should().BeTrue() 1, stream.Length.Should().BeGreaterThan(0) 1, [TestMethod] 8 - HOLD. +- ADDED-LENGTH: 0 (alignment-independent definition: the line whose trimmed text is exactly stream.Length.Should().BeGreaterThan(0); is excluded; in this diff it was reported as unchanged context) +- ADDED-OPENREAD: 2 (the mock setup line and FileStream stream = wrapper.OpenRead();) +- ADDED-FIXTUREPATH: 4 +- ADDED-OPEN-CREATE-WRITE: 0 +- ADDED-LINE-COUNT: 24 (observation) +- LINES 357 (at most 500) - HOLDS. +- porcelain begins " M" - HOLDS. + +## Census + + FILE UtilitiesCS.Test/HelperClasses/FileInfoWrapper_Tests.cs + TOKEN Task.Run( = 0 + TOKEN .GetAwaiter() = 0 + TOKEN DedicatedWorkerThread.Run( = 0 + TOKEN RunOnDedicatedWorkerThread = 0 + TOKEN ClearViewerDispatcher( = 0 + TOKEN partial class = 0 + TOKEN [TestMethod] = 8 + TOKEN action(); = 0 + TOKEN new Thread( = 0 + TOKEN IsBackground = true = 0 + TOKEN thread.Join(); = 0 + TOKEN Join( = 0 + TOKEN Join() = 0 + TOKEN using System.Reflection; = 0 + TOKEN using QuickFiler.Test.TestSupport; = 0 + TOKEN namespace QuickFiler.Test.TestSupport = 0 + TOKEN internal static class DedicatedWorkerThread = 0 + TOKEN internal static Exception Run(Action action) = 0 + TOKEN .Should() = 53 + TOKEN dedicated worker thread must not be = 0 + TOKEN distinct from every live thread by construction = 0 + TOKEN unconditionally = 0 + TOKEN owner.CheckAccess() = 0 + TOKEN .NotBe( = 0 + TOKEN ownerThreadId = 0 + TOKEN .BeNull( = 0 + TOKEN executions.Should().Be(0) = 0 + TOKEN cannot marshal = 0 + TOKEN GetSolutionFile = 0 + TOKEN TaskMaster.sln = 0 + TOKEN AppDomain = 0 + TOKEN File.Exists( = 0 + TOKEN File.Create = 0 + TOKEN File.WriteAll = 0 + TOKEN File.Delete = 0 + TOKEN Path.GetTemp = 0 + TOKEN FileMode. = 13 + TOKEN FileMode.Open = 13 + TOKEN FileAccess. = 11 + TOKEN FileAccess.Read = 11 + TOKEN FileShare.ReadWrite = 9 + TOKEN Assembly.Location = 7 + TOKEN FixturePath = 4 + TOKEN using var sentinel = new FileStream( = 1 + TOKEN BeSameAs(sentinel) = 1 + TOKEN .Returns(decoy) = 0 + TOKEN wrapper.OpenRead() = 2 + TOKEN stream.CanRead.Should().BeTrue() = 1 + TOKEN stream.Length.Should().BeGreaterThan(0) = 1 + TOKEN return true; = 0 + TOKEN _ownerThreadId.HasValue = 0 + TOKEN System.Threading.SynchronizationContext.Current = 0 + TOKEN Include="Viewers\ItemViewerBreadcrumbThreadAffinityTests.cs" = 0 + TOKEN Include="Viewers\ItemViewerBreadcrumbThreadAffinityTests.Part2.cs" = 0 + TOKEN Include="TestSupport\DedicatedWorkerThread.cs" = 0 + TOKEN Thread.Sleep = 0 + TOKEN Task.Delay = 0 + TOKEN [Timeout = 0 + TOKEN DoNotParallelize = 0 + TOKEN Retry = 0 + LINES = 357 + SHA256 = 142BCB8E95D710890A71F325A65801ED8EB53A87A339B10018D889491ECEF596 + +## Added-line counts (git diff -U0 HEAD) + + ADDED-LINE-COUNT: 24 + ADDED-LENGTH: 0 + ADDED-OPENREAD: 2 + ADDED-FIXTUREPATH: 4 + ADDED-OPEN-CREATE-WRITE: 0 + +## porcelain + + M UtilitiesCS.Test/HelperClasses/FileInfoWrapper_Tests.cs diff --git a/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/regression-testing/p2-t7-csharpier-scoped.2026-09-29T09-16.md b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/regression-testing/p2-t7-csharpier-scoped.2026-09-29T09-16.md new file mode 100644 index 000000000..634be53ce --- /dev/null +++ b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/regression-testing/p2-t7-csharpier-scoped.2026-09-29T09-16.md @@ -0,0 +1,344 @@ +# P2-T7 Scoped CSharpier Format and Check + +Timestamp: 2026-09-29T09-16 +ITERATION: 1 +Command: (1) Get-FileHash -Algorithm SHA256 of the five Write Set .cs files; dotnet tool run csharpier format QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.cs QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.Part2.cs QuickFiler.Test/TestSupport/DedicatedWorkerThread.cs QuickFiler.Test/Viewers/BreadcrumbPopupBoundaryCoverageTests.cs UtilitiesCS.Test/HelperClasses/FileInfoWrapper_Tests.cs; Get-FileHash again (2) dotnet tool run csharpier check with the same five paths (3) CMD-CENSUS on each of the five files +EXIT_CODE: 0 + +Output Summary: +- EXIT_CODE is scoped to the csharpier check invocation. FORMAT_EXIT: 0. +- Format console line: Formatted 5 files in 4177ms. (processed-file count, not REWRITTEN) +- check final summary line verbatim: Checked 5 files in 1950ms. +- REWRITTEN: 2 (the two new files, Part2 and DedicatedWorkerThread.cs; line counts unchanged at 202 and 48 and every token count unchanged, and both now carry CRLF endings, consistent with the end_of_line = crlf .editorconfig setting of fact 23) +- Hashes, before and after format: + - AFF before: BAD939900B47AE0AC963D203F621D28C959CC7F3E492119F1BD8272197EF5940 + - AFF after: BAD939900B47AE0AC963D203F621D28C959CC7F3E492119F1BD8272197EF5940 + - PART2 before: FB94BB94B65EA6F3C9E634DB2E0B5219CC003C3FF0F8565DB9C746BCA302B57D + - PART2 after: D6A35090996E48283CA04D8EE2C441F8ED8946BAECC1D06D9F16931FF767976E + - HELPER before: 309FC3119F39B08EFE766176DC4D33C44342896B6197DB09D986F53EDBAD59C8 + - HELPER after: 986838E4FD72A7E233B26A1BE03B5912DFBB4016980F515F5C01680A0ABF688E + - BND before: 4F8B8AFFA9387043AAE3A66EF2028D96AB4F0FA77AFCE244C9044E67B9B8E0FE + - BND after: 4F8B8AFFA9387043AAE3A66EF2028D96AB4F0FA77AFCE244C9044E67B9B8E0FE + - FIW before: 142BCB8E95D710890A71F325A65801ED8EB53A87A339B10018D889491ECEF596 + - FIW after: 142BCB8E95D710890A71F325A65801ED8EB53A87A339B10018D889491ECEF596 +- Every post value of the Token Census Expectations table holds for all five files; every LINES value is at most 500 (AFF 294, PART2 202, HELPER 48, BND 386, FIW 357). FILE SIZE LIMIT EXCEEDED did not fire. + +## Census: QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.cs (AFF) + + TOKEN Task.Run( = 0 + TOKEN .GetAwaiter() = 0 + TOKEN DedicatedWorkerThread.Run( = 0 + TOKEN RunOnDedicatedWorkerThread = 0 + TOKEN ClearViewerDispatcher( = 0 + TOKEN partial class = 1 + TOKEN [TestMethod] = 4 + TOKEN action(); = 0 + TOKEN new Thread( = 0 + TOKEN IsBackground = true = 0 + TOKEN thread.Join(); = 0 + TOKEN Join( = 0 + TOKEN Join() = 0 + TOKEN using System.Reflection; = 0 + TOKEN using QuickFiler.Test.TestSupport; = 0 + TOKEN namespace QuickFiler.Test.TestSupport = 0 + TOKEN internal static class DedicatedWorkerThread = 0 + TOKEN internal static Exception Run(Action action) = 0 + TOKEN .Should() = 10 + TOKEN dedicated worker thread must not be = 0 + TOKEN distinct from every live thread by construction = 0 + TOKEN unconditionally = 0 + TOKEN owner.CheckAccess() = 0 + TOKEN .NotBe( = 0 + TOKEN ownerThreadId = 0 + TOKEN .BeNull( = 0 + TOKEN executions.Should().Be(0) = 0 + TOKEN cannot marshal = 0 + TOKEN GetSolutionFile = 0 + TOKEN TaskMaster.sln = 0 + TOKEN AppDomain = 0 + TOKEN File.Exists( = 0 + TOKEN File.Create = 0 + TOKEN File.WriteAll = 0 + TOKEN File.Delete = 0 + TOKEN Path.GetTemp = 0 + TOKEN FileMode. = 0 + TOKEN FileMode.Open = 0 + TOKEN FileAccess. = 0 + TOKEN FileAccess.Read = 0 + TOKEN FileShare.ReadWrite = 0 + TOKEN Assembly.Location = 0 + TOKEN FixturePath = 0 + TOKEN using var sentinel = new FileStream( = 0 + TOKEN BeSameAs(sentinel) = 0 + TOKEN .Returns(decoy) = 0 + TOKEN wrapper.OpenRead() = 0 + TOKEN stream.CanRead.Should().BeTrue() = 0 + TOKEN stream.Length.Should().BeGreaterThan(0) = 0 + TOKEN return true; = 0 + TOKEN _ownerThreadId.HasValue = 0 + TOKEN System.Threading.SynchronizationContext.Current = 0 + TOKEN Include="Viewers\ItemViewerBreadcrumbThreadAffinityTests.cs" = 0 + TOKEN Include="Viewers\ItemViewerBreadcrumbThreadAffinityTests.Part2.cs" = 0 + TOKEN Include="TestSupport\DedicatedWorkerThread.cs" = 0 + TOKEN Thread.Sleep = 0 + TOKEN Task.Delay = 0 + TOKEN [Timeout = 0 + TOKEN DoNotParallelize = 0 + TOKEN Retry = 0 + LINES = 294 + +## Census: QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.Part2.cs (PART2) + + TOKEN Task.Run( = 0 + TOKEN .GetAwaiter() = 0 + TOKEN DedicatedWorkerThread.Run( = 3 + TOKEN RunOnDedicatedWorkerThread = 0 + TOKEN ClearViewerDispatcher( = 2 + TOKEN partial class = 1 + TOKEN [TestMethod] = 3 + TOKEN action(); = 0 + TOKEN new Thread( = 0 + TOKEN IsBackground = true = 0 + TOKEN thread.Join(); = 0 + TOKEN Join( = 2 + TOKEN Join() = 2 + TOKEN using System.Reflection; = 1 + TOKEN using QuickFiler.Test.TestSupport; = 1 + TOKEN namespace QuickFiler.Test.TestSupport = 0 + TOKEN internal static class DedicatedWorkerThread = 0 + TOKEN internal static Exception Run(Action action) = 0 + TOKEN .Should() = 15 + TOKEN dedicated worker thread must not be = 3 + TOKEN distinct from every live thread by construction = 0 + TOKEN unconditionally = 1 + TOKEN owner.CheckAccess() = 1 + TOKEN .NotBe( = 0 + TOKEN ownerThreadId = 0 + TOKEN .BeNull( = 1 + TOKEN executions.Should().Be(0) = 0 + TOKEN cannot marshal = 0 + TOKEN GetSolutionFile = 0 + TOKEN TaskMaster.sln = 0 + TOKEN AppDomain = 0 + TOKEN File.Exists( = 0 + TOKEN File.Create = 0 + TOKEN File.WriteAll = 0 + TOKEN File.Delete = 0 + TOKEN Path.GetTemp = 0 + TOKEN FileMode. = 0 + TOKEN FileMode.Open = 0 + TOKEN FileAccess. = 0 + TOKEN FileAccess.Read = 0 + TOKEN FileShare.ReadWrite = 0 + TOKEN Assembly.Location = 0 + TOKEN FixturePath = 0 + TOKEN using var sentinel = new FileStream( = 0 + TOKEN BeSameAs(sentinel) = 0 + TOKEN .Returns(decoy) = 0 + TOKEN wrapper.OpenRead() = 0 + TOKEN stream.CanRead.Should().BeTrue() = 0 + TOKEN stream.Length.Should().BeGreaterThan(0) = 0 + TOKEN return true; = 0 + TOKEN _ownerThreadId.HasValue = 0 + TOKEN System.Threading.SynchronizationContext.Current = 0 + TOKEN Include="Viewers\ItemViewerBreadcrumbThreadAffinityTests.cs" = 0 + TOKEN Include="Viewers\ItemViewerBreadcrumbThreadAffinityTests.Part2.cs" = 0 + TOKEN Include="TestSupport\DedicatedWorkerThread.cs" = 0 + TOKEN Thread.Sleep = 0 + TOKEN Task.Delay = 0 + TOKEN [Timeout = 0 + TOKEN DoNotParallelize = 0 + TOKEN Retry = 0 + LINES = 202 + +## Census: QuickFiler.Test/TestSupport/DedicatedWorkerThread.cs (HELPER) + + TOKEN Task.Run( = 0 + TOKEN .GetAwaiter() = 0 + TOKEN DedicatedWorkerThread.Run( = 0 + TOKEN RunOnDedicatedWorkerThread = 0 + TOKEN ClearViewerDispatcher( = 0 + TOKEN partial class = 0 + TOKEN [TestMethod] = 0 + TOKEN action(); = 1 + TOKEN new Thread( = 1 + TOKEN IsBackground = true = 1 + TOKEN thread.Join(); = 1 + TOKEN Join( = 2 + TOKEN Join() = 2 + TOKEN using System.Reflection; = 0 + TOKEN using QuickFiler.Test.TestSupport; = 0 + TOKEN namespace QuickFiler.Test.TestSupport = 1 + TOKEN internal static class DedicatedWorkerThread = 1 + TOKEN internal static Exception Run(Action action) = 1 + TOKEN .Should() = 0 + TOKEN dedicated worker thread must not be = 0 + TOKEN distinct from every live thread by construction = 1 + TOKEN unconditionally = 0 + TOKEN owner.CheckAccess() = 0 + TOKEN .NotBe( = 0 + TOKEN ownerThreadId = 0 + TOKEN .BeNull( = 0 + TOKEN executions.Should().Be(0) = 0 + TOKEN cannot marshal = 0 + TOKEN GetSolutionFile = 0 + TOKEN TaskMaster.sln = 0 + TOKEN AppDomain = 0 + TOKEN File.Exists( = 0 + TOKEN File.Create = 0 + TOKEN File.WriteAll = 0 + TOKEN File.Delete = 0 + TOKEN Path.GetTemp = 0 + TOKEN FileMode. = 0 + TOKEN FileMode.Open = 0 + TOKEN FileAccess. = 0 + TOKEN FileAccess.Read = 0 + TOKEN FileShare.ReadWrite = 0 + TOKEN Assembly.Location = 0 + TOKEN FixturePath = 0 + TOKEN using var sentinel = new FileStream( = 0 + TOKEN BeSameAs(sentinel) = 0 + TOKEN .Returns(decoy) = 0 + TOKEN wrapper.OpenRead() = 0 + TOKEN stream.CanRead.Should().BeTrue() = 0 + TOKEN stream.Length.Should().BeGreaterThan(0) = 0 + TOKEN return true; = 0 + TOKEN _ownerThreadId.HasValue = 0 + TOKEN System.Threading.SynchronizationContext.Current = 0 + TOKEN Include="Viewers\ItemViewerBreadcrumbThreadAffinityTests.cs" = 0 + TOKEN Include="Viewers\ItemViewerBreadcrumbThreadAffinityTests.Part2.cs" = 0 + TOKEN Include="TestSupport\DedicatedWorkerThread.cs" = 0 + TOKEN Thread.Sleep = 0 + TOKEN Task.Delay = 0 + TOKEN [Timeout = 0 + TOKEN DoNotParallelize = 0 + TOKEN Retry = 0 + LINES = 48 + +## Census: QuickFiler.Test/Viewers/BreadcrumbPopupBoundaryCoverageTests.cs (BND) + + TOKEN Task.Run( = 0 + TOKEN .GetAwaiter() = 3 + TOKEN DedicatedWorkerThread.Run( = 1 + TOKEN RunOnDedicatedWorkerThread = 0 + TOKEN ClearViewerDispatcher( = 0 + TOKEN partial class = 1 + TOKEN [TestMethod] = 5 + TOKEN action(); = 1 + TOKEN new Thread( = 0 + TOKEN IsBackground = true = 0 + TOKEN thread.Join(); = 0 + TOKEN Join( = 0 + TOKEN Join() = 0 + TOKEN using System.Reflection; = 1 + TOKEN using QuickFiler.Test.TestSupport; = 1 + TOKEN namespace QuickFiler.Test.TestSupport = 0 + TOKEN internal static class DedicatedWorkerThread = 0 + TOKEN internal static Exception Run(Action action) = 0 + TOKEN .Should() = 34 + TOKEN dedicated worker thread must not be = 1 + TOKEN distinct from every live thread by construction = 0 + TOKEN unconditionally = 0 + TOKEN owner.CheckAccess() = 0 + TOKEN .NotBe( = 1 + TOKEN ownerThreadId = 2 + TOKEN .BeNull( = 1 + TOKEN executions.Should().Be(0) = 1 + TOKEN cannot marshal = 1 + TOKEN GetSolutionFile = 0 + TOKEN TaskMaster.sln = 0 + TOKEN AppDomain = 0 + TOKEN File.Exists( = 0 + TOKEN File.Create = 0 + TOKEN File.WriteAll = 0 + TOKEN File.Delete = 0 + TOKEN Path.GetTemp = 0 + TOKEN FileMode. = 0 + TOKEN FileMode.Open = 0 + TOKEN FileAccess. = 0 + TOKEN FileAccess.Read = 0 + TOKEN FileShare.ReadWrite = 0 + TOKEN Assembly.Location = 0 + TOKEN FixturePath = 0 + TOKEN using var sentinel = new FileStream( = 0 + TOKEN BeSameAs(sentinel) = 0 + TOKEN .Returns(decoy) = 0 + TOKEN wrapper.OpenRead() = 0 + TOKEN stream.CanRead.Should().BeTrue() = 0 + TOKEN stream.Length.Should().BeGreaterThan(0) = 0 + TOKEN return true; = 1 + TOKEN _ownerThreadId.HasValue = 0 + TOKEN System.Threading.SynchronizationContext.Current = 0 + TOKEN Include="Viewers\ItemViewerBreadcrumbThreadAffinityTests.cs" = 0 + TOKEN Include="Viewers\ItemViewerBreadcrumbThreadAffinityTests.Part2.cs" = 0 + TOKEN Include="TestSupport\DedicatedWorkerThread.cs" = 0 + TOKEN Thread.Sleep = 0 + TOKEN Task.Delay = 0 + TOKEN [Timeout = 0 + TOKEN DoNotParallelize = 0 + TOKEN Retry = 0 + LINES = 386 + +## Census: UtilitiesCS.Test/HelperClasses/FileInfoWrapper_Tests.cs (FIW) + + TOKEN Task.Run( = 0 + TOKEN .GetAwaiter() = 0 + TOKEN DedicatedWorkerThread.Run( = 0 + TOKEN RunOnDedicatedWorkerThread = 0 + TOKEN ClearViewerDispatcher( = 0 + TOKEN partial class = 0 + TOKEN [TestMethod] = 8 + TOKEN action(); = 0 + TOKEN new Thread( = 0 + TOKEN IsBackground = true = 0 + TOKEN thread.Join(); = 0 + TOKEN Join( = 0 + TOKEN Join() = 0 + TOKEN using System.Reflection; = 0 + TOKEN using QuickFiler.Test.TestSupport; = 0 + TOKEN namespace QuickFiler.Test.TestSupport = 0 + TOKEN internal static class DedicatedWorkerThread = 0 + TOKEN internal static Exception Run(Action action) = 0 + TOKEN .Should() = 53 + TOKEN dedicated worker thread must not be = 0 + TOKEN distinct from every live thread by construction = 0 + TOKEN unconditionally = 0 + TOKEN owner.CheckAccess() = 0 + TOKEN .NotBe( = 0 + TOKEN ownerThreadId = 0 + TOKEN .BeNull( = 0 + TOKEN executions.Should().Be(0) = 0 + TOKEN cannot marshal = 0 + TOKEN GetSolutionFile = 0 + TOKEN TaskMaster.sln = 0 + TOKEN AppDomain = 0 + TOKEN File.Exists( = 0 + TOKEN File.Create = 0 + TOKEN File.WriteAll = 0 + TOKEN File.Delete = 0 + TOKEN Path.GetTemp = 0 + TOKEN FileMode. = 13 + TOKEN FileMode.Open = 13 + TOKEN FileAccess. = 11 + TOKEN FileAccess.Read = 11 + TOKEN FileShare.ReadWrite = 9 + TOKEN Assembly.Location = 7 + TOKEN FixturePath = 4 + TOKEN using var sentinel = new FileStream( = 1 + TOKEN BeSameAs(sentinel) = 1 + TOKEN .Returns(decoy) = 0 + TOKEN wrapper.OpenRead() = 2 + TOKEN stream.CanRead.Should().BeTrue() = 1 + TOKEN stream.Length.Should().BeGreaterThan(0) = 1 + TOKEN return true; = 0 + TOKEN _ownerThreadId.HasValue = 0 + TOKEN System.Threading.SynchronizationContext.Current = 0 + TOKEN Include="Viewers\ItemViewerBreadcrumbThreadAffinityTests.cs" = 0 + TOKEN Include="Viewers\ItemViewerBreadcrumbThreadAffinityTests.Part2.cs" = 0 + TOKEN Include="TestSupport\DedicatedWorkerThread.cs" = 0 + TOKEN Thread.Sleep = 0 + TOKEN Task.Delay = 0 + TOKEN [Timeout = 0 + TOKEN DoNotParallelize = 0 + TOKEN Retry = 0 + LINES = 357 diff --git a/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/regression-testing/p2-t8-build-after-fix.2026-09-29T09-18.md b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/regression-testing/p2-t8-build-after-fix.2026-09-29T09-18.md new file mode 100644 index 000000000..76b4a462e --- /dev/null +++ b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/regression-testing/p2-t8-build-after-fix.2026-09-29T09-18.md @@ -0,0 +1,12 @@ +# P2-T8 Build After Fix + +Timestamp: 2026-09-29T09-18 +Command: msbuild QuickFiler.Test\QuickFiler.Test.csproj /t:Build /m /p:Configuration=Debug /p:Platform=AnyCPU (CMD-BUILD-QF, TASKID p2-t8-qf) and msbuild UtilitiesCS.Test\UtilitiesCS.Test.csproj /t:Build /m /p:Configuration=Debug /p:Platform=AnyCPU (CMD-BUILD-UCS, TASKID p2-t8-ucs), resolved through vswhere, plus /nodeReuse:false; file logs under the git-ignored coverage\logs directory +EXIT_CODE: 0 + +Output Summary: +- EXIT_CODE is scoped to the QuickFiler.Test build. +- QuickFiler.Test build: MSBUILD_EXIT_CODE: 0; CSC_OUT_LINES: 2; PROD_CSC_OUT_LINES: 0 (no production file changed); ZERO_ERRORS_LINES: 1; DLL_ADVANCED: True; console: Build succeeded, 0 Warning(s), 0 Error(s). +- UCS-MSBUILD-EXIT: 0 +- UtilitiesCS.Test build: MSBUILD_EXIT_CODE: 0; CSC_OUT_LINES: 2; PROD_CSC_OUT_LINES: 0 (no production file changed); ZERO_ERRORS_LINES: 1; DLL_ADVANCED: True; console: Build succeeded, 0 Warning(s), 0 Error(s). +- Acceptance: both exit codes 0; both CSC_OUT_LINES at least 1 and both DLL_ADVANCED True; both ZERO_ERRORS_LINES at least 1 - HOLD. diff --git a/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/regression-testing/p2-t9-pass-before-controls.2026-09-29T09-20.md b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/regression-testing/p2-t9-pass-before-controls.2026-09-29T09-20.md new file mode 100644 index 000000000..3250e9379 --- /dev/null +++ b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/regression-testing/p2-t9-pass-before-controls.2026-09-29T09-20.md @@ -0,0 +1,79 @@ +# P2-T9 Confirming Runs Before Controls + +Timestamp: 2026-09-29T09-20 +ITERATION: 1 +Command: (1) vstest.console.exe QuickFiler.Test\bin\Debug\QuickFiler.Test.dll "/Settings:TaskMaster.runsettings" /InIsolation "/TestCaseFilter:FullyQualifiedName~InitializeBreadcrumbPipeline_WorkerThread_ThrowsBoundaryDiagnostic|FullyQualifiedName~ConfigureBreadcrumbDropDown_WorkerThread_ThrowsBoundaryDiagnostic|FullyQualifiedName~InitializeBreadcrumbPipeline_NullOwningDispatcher_DoesNotThrow|FullyQualifiedName~Dispatcher_OwnerOnlyWorker_ReportsWithoutRunningAction" "/ResultsDirectory:coverage\test-results\931\p2-t9-four" "/Logger:trx;LogFileName=p2-t9-four.trx" "/Blame:CollectHangDump;TestTimeout=4min;HangDumpType=None"; (2) vstest.console.exe UtilitiesCS.Test\bin\Debug\UtilitiesCS.Test.dll "/Settings:TaskMaster.runsettings" /InIsolation "/TestCaseFilter:FullyQualifiedName~OpenRead_ShouldReturnReadableStreamForWrappedFile" "/ResultsDirectory:coverage\test-results\931\p2-t9-openread" "/Logger:trx;LogFileName=p2-t9-openread.trx" "/Blame:CollectHangDump;TestTimeout=4min;HangDumpType=None"; (3) vstest.console.exe QuickFiler.Test\bin\Debug\QuickFiler.Test.dll "/Settings:TaskMaster.runsettings" /InIsolation "/TestCaseFilter:FullyQualifiedName~ItemViewerBreadcrumbThreadAffinityTests" "/ResultsDirectory:coverage\test-results\931\p2-t9-affinity" "/Logger:trx;LogFileName=p2-t9-affinity.trx" "/Blame:CollectHangDump;TestTimeout=4min;HangDumpType=None"; (4) vstest.console.exe UtilitiesCS.Test\bin\Debug\UtilitiesCS.Test.dll "/Settings:TaskMaster.runsettings" /InIsolation "/TestCaseFilter:FullyQualifiedName~FileInfoWrapper_Tests" "/ResultsDirectory:coverage\test-results\931\p2-t9-fiw" "/Logger:trx;LogFileName=p2-t9-fiw.trx" "/Blame:CollectHangDump;TestTimeout=4min;HangDumpType=None" - each resolved through vswhere (CMD-VSTEST) +EXIT_CODE: 0 + +Output Summary: +- EXIT_CODE is scoped to run 1 (p2-t9-four). +- OPENREAD-VSTEST-EXIT: 0 +- AFFINITY-VSTEST-EXIT: 0 +- FIW-VSTEST-EXIT: 0 +- RUNSETTINGS-HASH (P0-T4): 199408CA53CE4E12AE1A894FC66A0926124F3AC0D6447BD93B0C121338297FFA +- Observation: each run printed a coverage attachment under its git-ignored results directory (the root runsettings collector), while COLLECTOR_LINES was 0 in each run. +- Acceptance: all four exit codes 0; totals 4, 1, 7, 8 with failed 0; every RESULT line Passed; every targeted name present; every RUNSETTINGS-HASH-NOW equals RUNSETTINGS-HASH - HOLD. No repair iteration was needed. + +## Run 1: p2-t9-four (ASSEMBLY-QF, FILTER-FOUR, NAMES-FOUR) + + RUNSETTINGS-HASH-NOW: 199408CA53CE4E12AE1A894FC66A0926124F3AC0D6447BD93B0C121338297FFA + VSTEST_EXIT_CODE: 0 + TRX_PRESENT: True + SEQUENCE_FILES: 0 + COLLECTOR_LINES: 0 + COUNTERS total=4 executed=4 passed=4 failed=0 + RESULT_COUNT: 4 + RESULT InitializeBreadcrumbPipeline_WorkerThread_ThrowsBoundaryDiagnostic = Passed + RESULT InitializeBreadcrumbPipeline_NullOwningDispatcher_DoesNotThrow = Passed + RESULT ConfigureBreadcrumbDropDown_WorkerThread_ThrowsBoundaryDiagnostic = Passed + RESULT Dispatcher_OwnerOnlyWorker_ReportsWithoutRunningAction = Passed + MESSAGE lines: none + +## Run 2: p2-t9-openread (ASSEMBLY-UCS, FILTER-OPENREAD, NAMES-FIW) + + RUNSETTINGS-HASH-NOW: 199408CA53CE4E12AE1A894FC66A0926124F3AC0D6447BD93B0C121338297FFA + VSTEST_EXIT_CODE: 0 + TRX_PRESENT: True + SEQUENCE_FILES: 0 + COLLECTOR_LINES: 0 + COUNTERS total=1 executed=1 passed=1 failed=0 + RESULT_COUNT: 1 + RESULT OpenRead_ShouldReturnReadableStreamForWrappedFile = Passed + MESSAGE lines: none + +## Run 3: p2-t9-affinity (ASSEMBLY-QF, FILTER-AFFINITY-CLASS, NAMES-AFFINITY) + + RUNSETTINGS-HASH-NOW: 199408CA53CE4E12AE1A894FC66A0926124F3AC0D6447BD93B0C121338297FFA + VSTEST_EXIT_CODE: 0 + TRX_PRESENT: True + SEQUENCE_FILES: 0 + COLLECTOR_LINES: 0 + COUNTERS total=7 executed=7 passed=7 failed=0 + RESULT_COUNT: 7 + RESULT InitializeBreadcrumbPipeline_OwningThreadDifferentPlainContext_DoesNotThrow = Passed + RESULT InitializeBreadcrumbPipeline_OwningThreadNullAmbientContext_DoesNotThrow = Passed + RESULT ConfigureBreadcrumbDropDown_WorkerThread_ThrowsBoundaryDiagnostic = Passed + RESULT ConfigureBreadcrumbDropDown_OwningThreadInsideDispatcherOperation_DoesNotThrow = Passed + RESULT InitializeBreadcrumbPipeline_NullOwningDispatcher_DoesNotThrow = Passed + RESULT InitializeBreadcrumbPipeline_WorkerThread_ThrowsBoundaryDiagnostic = Passed + RESULT InitializeBreadcrumbPipeline_ConstructedInsideDispatcherOperation_SucceedsUnderDifferentAmbientContext = Passed + MESSAGE lines: none + +## Run 4: p2-t9-fiw (ASSEMBLY-UCS, FILTER-FIW-CLASS, NAMES-FIW) + + RUNSETTINGS-HASH-NOW: 199408CA53CE4E12AE1A894FC66A0926124F3AC0D6447BD93B0C121338297FFA + VSTEST_EXIT_CODE: 0 + TRX_PRESENT: True + SEQUENCE_FILES: 0 + COLLECTOR_LINES: 0 + COUNTERS total=8 executed=8 passed=8 failed=0 + RESULT_COUNT: 8 + RESULT OpenRead_ShouldReturnReadableStreamForWrappedFile = Passed + RESULT Properties_ShouldMirrorWrappedFileInfo = Passed + RESULT Constructor_WhenFileInfoIsNull_ThrowsArgumentNullException = Passed + RESULT StreamAndCopyMethods_ShouldDelegateToWrappedIFileInfo = Passed + RESULT ToString_ShouldDelegateToWrappedFileInfo = Passed + RESULT AccessControlAndLifecycleMethods_ShouldDelegateToWrappedIFileInfo = Passed + RESULT PropertyDelegates_ShouldMirrorMockedIFileInfo = Passed + RESULT ExplicitDirectoryCast_ShouldReturnWrappedContainingDirectory = Passed + MESSAGE lines: none diff --git a/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/regression-testing/p3-t9-post-control-clean-tree.2026-09-29T09-30.md b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/regression-testing/p3-t9-post-control-clean-tree.2026-09-29T09-30.md new file mode 100644 index 000000000..abc3c0541 --- /dev/null +++ b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/regression-testing/p3-t9-post-control-clean-tree.2026-09-29T09-30.md @@ -0,0 +1,17 @@ +# P3-T9 Post-Control Clean Tree + +Timestamp: 2026-09-29T09-30 +Command: git diff --exit-code HEAD -- QuickFiler QuickFiler.Test UtilitiesCS UtilitiesCS.Test; git status --porcelain -- QuickFiler QuickFiler.Test UtilitiesCS UtilitiesCS.Test; Get-FileHash -Algorithm SHA256 of the six Write Set code files +EXIT_CODE: 0 + +Output Summary: +- EXIT_CODE is scoped to the git diff --exit-code span (HEAD is FIX-HEAD 2956820e1be115c9e473e2be2c083ff60a0f08a8). +- Porcelain output: EMPTY +- Hashes beside their P2-T10 anchors: + - AFF: BAD939900B47AE0AC963D203F621D28C959CC7F3E492119F1BD8272197EF5940 (FIX-HASH-AFF: BAD939900B47AE0AC963D203F621D28C959CC7F3E492119F1BD8272197EF5940; equal) + - PART2: D6A35090996E48283CA04D8EE2C441F8ED8946BAECC1D06D9F16931FF767976E (FIX-HASH-PART2: D6A35090996E48283CA04D8EE2C441F8ED8946BAECC1D06D9F16931FF767976E; equal) + - HELPER: 986838E4FD72A7E233B26A1BE03B5912DFBB4016980F515F5C01680A0ABF688E (FIX-HASH-HELPER: 986838E4FD72A7E233B26A1BE03B5912DFBB4016980F515F5C01680A0ABF688E; equal) + - BND: 4F8B8AFFA9387043AAE3A66EF2028D96AB4F0FA77AFCE244C9044E67B9B8E0FE (FIX-HASH-BND: 4F8B8AFFA9387043AAE3A66EF2028D96AB4F0FA77AFCE244C9044E67B9B8E0FE; equal) + - CSPROJ: 5538F090CDBC43F57E03000C1776D9881E147302E513D1D03D58C6102E4DC39F (FIX-HASH-CSPROJ: 5538F090CDBC43F57E03000C1776D9881E147302E513D1D03D58C6102E4DC39F; equal) + - FIW: 142BCB8E95D710890A71F325A65801ED8EB53A87A339B10018D889491ECEF596 (FIX-HASH-FIW: 142BCB8E95D710890A71F325A65801ED8EB53A87A339B10018D889491ECEF596; equal) +- Acceptance: EXIT_CODE 0; porcelain empty; all six hashes equal their P2-T10 anchors - HOLD. No temporary production edit and no control residue survives into Phase 4. diff --git a/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/regression-testing/parallel-suite-quickfiler-test.md b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/regression-testing/parallel-suite-quickfiler-test.md new file mode 100644 index 000000000..0fb564aa2 --- /dev/null +++ b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/regression-testing/parallel-suite-quickfiler-test.md @@ -0,0 +1,30 @@ +# Parallel-Suite Run (P4-T5): QuickFiler.Test, full assembly, after the fix + +Timestamp: 2026-09-29T09-36 +Command: vstest.console.exe QuickFiler.Test\bin\Debug\QuickFiler.Test.dll "/Settings:TaskMaster.runsettings" /InIsolation "/ResultsDirectory:coverage\test-results\931\p4-t5" "/Logger:trx;LogFileName=p4-t5.trx" "/Blame:CollectHangDump;TestTimeout=4min;HangDumpType=None" (resolved through vswhere; CMD-VSTEST with ASSEMBLY-QF, NAMES-QF, TASKID p4-t5). Runsettings file: TaskMaster.runsettings (repository root; Workers 0, Scope ClassLevel). Isolation switch: /InIsolation. Filter: no test-case filter. Console transcript teed to the git-ignored coverage\logs\p4-t5.vstest.log. +EXIT_CODE: 0 +ITERATION: 1 + +Output Summary: +- RUNSETTINGS-HASH-NOW: 199408CA53CE4E12AE1A894FC66A0926124F3AC0D6447BD93B0C121338297FFA (equals RUNSETTINGS-HASH from P0-T4) +- VSTEST_EXIT_CODE: 0 +- TRX_PRESENT: True +- COUNTERS total=1468 executed=1468 passed=1468 failed=0 +- RESULT_COUNT: 1468 +- SEQUENCE_FILES: 0 +- COLLECTOR_LINES: 0 +- RESULT ConfigureBreadcrumbDropDown_OwningThreadInsideDispatcherOperation_DoesNotThrow = Passed +- RESULT InitializeBreadcrumbPipeline_OwningThreadNullAmbientContext_DoesNotThrow = Passed +- RESULT InitializeBreadcrumbPipeline_OwningThreadDifferentPlainContext_DoesNotThrow = Passed +- RESULT InitializeBreadcrumbPipeline_WorkerThread_ThrowsBoundaryDiagnostic = Passed +- RESULT InitializeBreadcrumbPipeline_ConstructedInsideDispatcherOperation_SucceedsUnderDifferentAmbientContext = Passed +- RESULT ConfigureBreadcrumbDropDown_WorkerThread_ThrowsBoundaryDiagnostic = Passed +- RESULT InitializeBreadcrumbPipeline_NullOwningDispatcher_DoesNotThrow = Passed +- RESULT Dispatcher_OwnerOnlyWorker_ReportsWithoutRunningAction = Passed +- MESSAGE lines: none +- NEWLY-FAILING: NONE (BASELINE-FAILED-QF: NONE; this run's failed set is empty) +- executed 1468 is at least the P0-T10 QuickFiler executed value 1468. + +Acceptance: EXIT_CODE 0; failed 0 and executed at least the baseline value; SEQUENCE_FILES 0; the eight NAMES-QF RESULT lines present and each Passed (the Part2 file and the helper compiled and were discovered, AC6); RUNSETTINGS-HASH-NOW equals RUNSETTINGS-HASH; NEWLY-FAILING NONE. All six hold. + +The TRX document stays under the git-ignored coverage\test-results\931\ tree; this file is the committed projection. diff --git a/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/regression-testing/parallel-suite-utilitiescs-test.md b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/regression-testing/parallel-suite-utilitiescs-test.md new file mode 100644 index 000000000..cdf4ded70 --- /dev/null +++ b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/regression-testing/parallel-suite-utilitiescs-test.md @@ -0,0 +1,29 @@ +# Parallel-Suite Run (P4-T6): UtilitiesCS.Test, full assembly, after the fix + +Timestamp: 2026-09-29T09-37 +Command: vstest.console.exe UtilitiesCS.Test\bin\Debug\UtilitiesCS.Test.dll "/Settings:TaskMaster.runsettings" /InIsolation "/TestCaseFilter:FullyQualifiedName!~HelperClasses.ShellUtilities_Tests&FullyQualifiedName!~HelperClasses.ShellUtilitiesStatic_Tests&FullyQualifiedName!~HelperClasses.SysImageListHelperTests&FullyQualifiedName!~EmailIntelligence.OSBrowser_Tests" "/ResultsDirectory:coverage\test-results\931\p4-t6" "/Logger:trx;LogFileName=p4-t6.trx" "/Blame:CollectHangDump;TestTimeout=4min;HangDumpType=None" (resolved through vswhere; CMD-VSTEST with ASSEMBLY-UCS, UCS-FILTERARG from P0-T9, NAMES-FIW, TASKID p4-t6). Runsettings file: TaskMaster.runsettings (repository root; Workers 0, Scope ClassLevel). Isolation switch: /InIsolation. Filter: the UCS-FILTERARG recorded by P0-T9 (STALL-PROBE: REPRODUCES), shown verbatim above. Excluded classes: HelperClasses.ShellUtilities_Tests, HelperClasses.ShellUtilitiesStatic_Tests, HelperClasses.SysImageListHelperTests, EmailIntelligence.OSBrowser_Tests. The stall is pre-existing on main and these classes are covered by CI. Console transcript teed to the git-ignored coverage\logs\p4-t6.vstest.log. +EXIT_CODE: 0 +ITERATION: 1 + +Output Summary: +- RUNSETTINGS-HASH-NOW: 199408CA53CE4E12AE1A894FC66A0926124F3AC0D6447BD93B0C121338297FFA (equals RUNSETTINGS-HASH from P0-T4) +- VSTEST_EXIT_CODE: 0 +- TRX_PRESENT: True +- COUNTERS total=4922 executed=4922 passed=4922 failed=0 +- RESULT_COUNT: 4922 +- SEQUENCE_FILES: 0 +- COLLECTOR_LINES: 0 +- RESULT ToString_ShouldDelegateToWrappedFileInfo = Passed +- RESULT Constructor_WhenFileInfoIsNull_ThrowsArgumentNullException = Passed +- RESULT OpenRead_ShouldReturnReadableStreamForWrappedFile = Passed +- RESULT ExplicitDirectoryCast_ShouldReturnWrappedContainingDirectory = Passed +- RESULT AccessControlAndLifecycleMethods_ShouldDelegateToWrappedIFileInfo = Passed +- RESULT StreamAndCopyMethods_ShouldDelegateToWrappedIFileInfo = Passed +- RESULT PropertyDelegates_ShouldMirrorMockedIFileInfo = Passed +- RESULT Properties_ShouldMirrorWrappedFileInfo = Passed +- MESSAGE lines: none (DictionaryExtensions_Tests.TryAddValuesAsync_UpdatesExistingValue passed in this run; no test was re-run) +- NEWLY-FAILING: NONE (BASELINE-FAILED-UCS: NONE; this run's failed set is empty) + +Acceptance: EXIT_CODE 0; failed 0 and executed at least 1; SEQUENCE_FILES 0; the eight NAMES-FIW RESULT lines present and each Passed; RUNSETTINGS-HASH-NOW equals RUNSETTINGS-HASH; NEWLY-FAILING NONE. All six hold. + +The TRX document stays under the git-ignored coverage\test-results\931\ tree; this file is the committed projection. diff --git a/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/feature-audit.2026-09-29T20-15.md b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/feature-audit.2026-09-29T20-15.md new file mode 100644 index 000000000..730116304 --- /dev/null +++ b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/feature-audit.2026-09-29T20-15.md @@ -0,0 +1,85 @@ +# Feature Audit: tests-depend-on-uncontrolled-environment (Issue #931) + +- Branch: bug/tests-depend-on-uncontrolled-environment-931 +- Head under review: ce744bbba3db4701e070785fd782c8b45289f2da +- Base: origin/main (c4ff0e2be0bc9c51acc43dacd2cc5954a448676c, merged at 55a50e922 with no conflicts) +- Work mode: full-bug (issue.md marker `- Work Mode: full-bug`); AC source: spec.md only +- Audit timestamp: 2026-09-29T20-15 +- Total blocking findings in this artifact: 0 +- Total non-blocking findings in this artifact: 2 (both disclosures carried over from the policy audit: PA-1 on AC15 wording, PA-4 on the post-merge UtilitiesCS package line rate) + +## Executive Summary + +All 19 acceptance criteria in spec.md evaluate PASS on evidence. No criterion was unchecked; no criterion was newly checked (all 19 were already `[x]`). Two criteria (AC15 and AC18) pass with an explicit disclosure recorded below. Zero blocking findings. + +## Scope and Baseline + +- AC source file: `docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/spec.md`, section `## Acceptance Criteria`, 19 checkbox items AC1-AC19. The counter began after that heading and ended at the next heading (`## Numeric Derivation Evidence`); the `GeneratedDocumentCounters.psm1` module could not be invoked in this no-Bash run, so the count is a direct read of the section. +- Baseline: merge base 177b6d78e1b2408e5aedbd794cef3aad6b7fb372 for every pre-merge projection; origin/main c4ff0e2b merged at 55a50e922 for the post-merge re-run. Pre-edit file state recorded in `evidence/baseline/p0-t12-pre-edit-census.2026-09-29T09-05.md` (490 / 361 / 359 lines; one `Task.Run(` in each QuickFiler.Test file; five `GetSolutionFile` and one `TaskMaster.sln` in the file test). +- Branch diff (orchestrator-supplied `git diff --name-only origin/main...HEAD`, excluding the feature folder): `QuickFiler.Test/QuickFiler.Test.csproj`; `QuickFiler.Test/TestSupport/DedicatedWorkerThread.cs`; `QuickFiler.Test/Viewers/BreadcrumbPopupBoundaryCoverageTests.cs`; `QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.Part2.cs`; `QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.cs`; `UtilitiesCS.Test/HelperClasses/FileInfoWrapper_Tests.cs`; `docs/features/potential/promoted/2026-09-28-tests-depend-on-uncontrolled-environment.md`. +- Verification method: Read/Grep/Glob over the worktree; committed projections; the git-ignored raw coverage documents under `coverage/`; orchestrator-supplied git facts. No command executed. + +## Acceptance Criteria Inventory + +| ID | Criterion (abbreviated) | Checkbox state in spec.md | +|---|---|---| +| AC1 | `Dispatcher_OwnerOnlyWorker_ReportsWithoutRunningAction` rewritten to the dedicated-thread pattern with id precondition; pre-existing assertions unchanged | `[x]` | +| AC2 | `InitializeBreadcrumbPipeline_NullOwningDispatcher_DoesNotThrow` (Part2) rewritten with captured owner, `CheckAccess()` precondition, unconditional remark | `[x]` | +| AC3 | The twenty UNAFFECTED `Task.Run` sites (eight files) byte-identical to the merge base; two pre-existing `[DoNotParallelize]` untouched | `[x]` | +| AC4 | Class `partial` in both files; correct member distribution; no test renamed/removed; `RunOnDedicatedWorkerThread` gone | `[x]` | +| AC5 | Five `.cs` files at or under 500 lines | `[x]` | +| AC6 | Two `Compile Include` entries in backslash form; the four dedicated-thread test names executed and passed in the committed projection | `[x]` | +| AC7 | `DedicatedWorkerThread` shape, remark, no sleep/delay/timeout/retry/assertion; all four dedicated-thread tests call it | `[x]` | +| AC8 | `FileInfoWrapper_Tests.cs` free of `GetSolutionFile`, `TaskMaster.sln`, `AppDomain.CurrentDomain.BaseDirectory`, file-creating/writing/deleting calls; four test names kept | `[x]` | +| AC9 | `OpenRead` test through the internal seam with strict mock and assembly-image `FileStream` sentinel; `BeSameAs`, `CanRead`, `Length > 0` | `[x]` | +| AC10 | Three metadata tests on a rooted literal; no stream; retained assertions | `[x]` | +| AC11 | `mutation-owner-only-dispatcher-guard.md` shows fail-then-pass with recorded fields | `[x]` | +| AC12 | `mutation-null-owner-escape.md` shows fail-then-pass | `[x]` | +| AC13 | `mutation-inline-precondition.md` shows all four fail-then-pass | `[x]` | +| AC14 | `mutation-openread-sentinel.md` shows fail-then-pass | `[x]` | +| AC15 | No production / `.claude` / `config` change; changed set equals Write Set plus feature-folder documents | `[x]` | +| AC16 | Both full suites pass in the parallel regime; projections record totals, runsettings, switch, filter; no regime change | `[x]` | +| AC17 | Full toolchain passes in one final pass; `toolchain-pass.md` records commands, exit codes, zero skipped CoreCompile | `[x]` | +| AC18 | `coverage-baseline.md` and `coverage-final.md` carry projection and summary; final per-package figures not lower than baseline | `[x]` | +| AC19 | No `.trx` / `.xml` / `.coverage` added; no host path, account or host name in committed evidence; placeholders used | `[x]` | + +## Acceptance Criteria Evaluation + +| ID | Verdict | Blocking? | Evidence and reasoning | +|---|---|---|---| +| AC1 | PASS | No | `BreadcrumbPopupBoundaryCoverageTests.cs` lines 66-87 (current file): no `Task.Run`, no `GetAwaiter().GetResult()` in the test (the three `GetAwaiter()` occurrences in the file are the pre-existing `PumpSynchronizationContext` drain at lines 322-333); `ownerThreadId` captured at line 70 before `CreateOwnerOnlyDispatcher` at line 71; `DedicatedWorkerThread.Run` at line 73; delegate asserts `Environment.CurrentManagedThreadId.Should().NotBe(ownerThreadId, ...)` at lines 75-81 before `dispatcher.Dispatch` at line 82; `captured.Should().BeNull()` line 84; `executions.Should().Be(0)` line 85; `errors.Should().ContainSingle().Which.Message.Should().Contain("cannot marshal")` line 86, identical to the removed text. M1 confirms the guard is exercised. | +| AC2 | PASS | No | Part2 lines 147-184: no `Task.Run` or `GetAwaiter()` in the file (Grep); `Dispatcher owner = scope.Viewer.UiDispatcher` at line 157 precedes `ClearViewerDispatcher` at line 159; `DedicatedWorkerThread.Run` at line 162; delegate asserts `owner.CheckAccess()` false at lines 164-171 before the guarded call at line 172; `captured.Should().BeNull(...)` lines 176-181; `BreadcrumbCoordinator.Should().BeSameAs(before)` line 182; remark lines 135-146 states "The call is therefore off the owner thread unconditionally, so the test discriminates against the pre-#781 context-reference guard". M2 confirms discrimination. | +| AC3 | PASS | No | The orchestrator-supplied name-only diff against origin/main lists none of `EmailMoveMonitorTests.cs`, `BreadcrumbCoordinatorLifecycleTests.cs`, `BreadcrumbPopupControlDispatchTests.cs`, `BreadcrumbPopupBoundaryCoverageTests.Part2.cs`, `BreadcrumbSelectorToggleUiBoundaryTests.cs`, `BreadcrumbSelectorOpenRetryTests.cs`, `BreadcrumbUiThreadDispatchTests.cs`, `QfcItemController.UiThreadDispatcherFixtureTests.cs`; a file absent from a name-only diff is byte-identical to the base. `p4-t11-scope-boundary` QUICKFILER-TEST-CHANGED lists exactly the five Write Set QuickFiler.Test paths. Grep of QuickFiler.Test for `DoNotParallelize` returns exactly `Helper Classes/EmailMoveMonitorTests.cs:24` and `Helper Classes/ViewerQueueStaticWrapperTests.cs:11`, the two pre-existing sites. Byte-identity was not re-measured by git in this run (no Bash); the name-only listing is sufficient. | +| AC4 | PASS | No | `public sealed partial class ItemViewerBreadcrumbThreadAffinityTests` at primary line 31 (with `[TestClass]`) and Part2 line 21. Primary holds the four owner-thread tests (lines 39-198), `InertOperations` (204-207), `InertDropDownHost` (210-238), `DrainableSynchronizationContext` (246-265), `ViewerScope` (271-292). Part2 holds the three cross-thread tests (43, 93, 148) and `ClearViewerDispatcher` (190-200). `RunOnDedicatedWorkerThread` count 0 in both files (Grep; `p4-t9` census). Eight test names appear in both the baseline and post-fix QuickFiler.Test projections; 1468 tests before and after. | +| AC5 | PASS | No | Reviewer Read line counts: `ItemViewerBreadcrumbThreadAffinityTests.cs` 294; `Part2.cs` 202; `DedicatedWorkerThread.cs` 48; `BreadcrumbPopupBoundaryCoverageTests.cs` 386; `FileInfoWrapper_Tests.cs` 357. `p4-t9-post-format-census` reports the same values. Project file and Markdown exempt. | +| AC6 | PASS | No | `QuickFiler.Test.csproj` line 99 `` and line 229 ``, backslash form matching lines 98 and 228. `parallel-suite-quickfiler-test.md` lists `InitializeBreadcrumbPipeline_WorkerThread_ThrowsBoundaryDiagnostic`, `ConfigureBreadcrumbDropDown_WorkerThread_ThrowsBoundaryDiagnostic`, `InitializeBreadcrumbPipeline_NullOwningDispatcher_DoesNotThrow` and `Dispatcher_OwnerOnlyWorker_ReportsWithoutRunningAction` as `= Passed`; the post-merge twin repeats this at 1469 tests. | +| AC7 | PASS | No | `DedicatedWorkerThread.cs`: `namespace QuickFiler.Test.TestSupport` line 4; `internal static class DedicatedWorkerThread` line 21; `internal static Exception Run(Action action)` line 28; `new Thread(...)` line 31; `IsBackground = true` line 42; `Start()` line 43; untimed `Join()` line 44; returns `captured` line 45. Remark lines 10-20 carries the dedicated-thread rationale from the removed helper plus the added "asserts nothing itself" sentence. Grep: no `Thread.Sleep`, `Task.Delay`, timeout argument, loop or `.Should()` in the file. Call sites: Part2 lines 52, 101, 162 and `BreadcrumbPopupBoundaryCoverageTests.cs` line 73 (four). M3 shows all four fail when the delegate is also run inline. | +| AC8 | PASS | No | Grep of the current file for `GetSolutionFile`, `TaskMaster.sln`, `AppDomain`, `File.Create`, `File.WriteAll`, `File.Delete`, `GetTempFileName`, `GetTempPath`: zero matches. Every `FileMode.` occurrence is `FileMode.Open` (13) and every `FileAccess.` is `FileAccess.Read` (11), including the pre-existing mock setups at lines 274-302. The four named tests exist at lines 31, 47, 61, 88. The `x.Delete()` / `x.Create()` setups at lines 272 and 328 are strict-mock member setups on `IFileInfo`, not file-system calls. | +| AC9 | PASS | No | Lines 68-84: `using var sentinel = new FileStream(typeof(FileInfoWrapper_Tests).Assembly.Location, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)`; `new Mock(MockBehavior.Strict)` with `Setup(x => x.OpenRead()).Returns(sentinel)`; `new FileInfoWrapper(fileInfo.Object)` (internal seam); `stream.Should().BeSameAs(sentinel)`, `stream.CanRead.Should().BeTrue()`, `stream.Length.Should().BeGreaterThan(0)`. M4 shows `BeSameAs` fails against a second stream. | +| AC10 | PASS | No | `FixturePath = @"C:\Repo\fixture.sln"` line 18 (rooted; not under the repository). `Properties_ShouldMirrorWrappedFileInfo` (31-44) asserts `Exists`, `FullName`, `Name`, `Extension == ".sln"`, `DirectoryName`, `Directory.FullName`; `ExplicitDirectoryCast_...` (47-58) asserts the cast's `FullName` and `Name`; `ToString_...` (88-99) asserts `ToString()`. None of the three references `Length`, `OpenRead`, `Open`, `Create` or `OpenWrite`; no stream is opened. | +| AC11 | PASS | No | `mutation-owner-only-dispatcher-guard.md`: mutated file `QuickFiler/Viewers/BreadcrumbUiDispatcher.cs`, hunk lines 276-277 replaced by `return true;`; runsettings `TaskMaster.runsettings`, `/InIsolation`, filter `FullyQualifiedName~Dispatcher_OwnerOnlyWorker_ReportsWithoutRunningAction`; RESULT Failed with MESSAGE "Expected executions to be 0, but found 1 (difference of 1)"; revert: REVERT-DIFF-EXIT 0, porcelain EMPTY, SHA-256 equal to P0-T12; confirming run total 1 passed 1. | +| AC12 | PASS | No | `mutation-null-owner-escape.md`: mutated `QuickFiler/Viewers/ItemViewer.Breadcrumb.cs` lines 435-438, `return;` replaced by the context-reference throw; same runsettings/switch, filter `FullyQualifiedName~InitializeBreadcrumbPipeline_NullOwningDispatcher_DoesNotThrow`; RESULT Failed with MESSAGE "Expected captured to be ... but found System.InvalidOperationException: mutation 931: pre-781 context-reference guard" with a sanitized stack (``); revert proven (diff-exit 0, porcelain EMPTY, SHA-256 equal); confirming run passed. | +| AC13 | PASS | No | `mutation-inline-precondition.md`: `action();` inserted as the first statement of `Run`; filter naming all four tests; COUNTERS total=4 failed=4; each MESSAGE contains "dedicated worker thread must not be"; revert proven (SHA-256 equals FIX-HASH-HELPER); confirming run total 4 passed 4. | +| AC14 | PASS | No | `mutation-openread-sentinel.md`: second `FileStream` `decoy` returned by the mock; filter `FullyQualifiedName~OpenRead_ShouldReturnReadableStreamForWrappedFile`; RESULT Failed with MESSAGE "Expected stream to refer to System.IO.FileStream {...} but found System.IO.FileStream {...}" (paths sanitized to ``); revert proven (SHA-256 equals FIX-HASH-FIW); confirming run passed. | +| AC15 | PASS (with disclosure) | No | Clause 1: the name-only diff against origin/main contains no path under `QuickFiler/`, `UtilitiesCS/`, `.claude/` or `config/`; `p4-t11-scope-boundary` records PRODUCTION-AND-CONFIG-DIFF-EXIT 0 and COMMITTED-CLAUDE-OR-CONFIG NONE; `p4-t33-closure` shows both `MERGE-BASE..HEAD` and `origin/main...HEAD` listings over the four code trees as exactly the six Write Set code paths. Clause 2: the changed set is the six Write Set code paths plus feature-folder documents plus one additional file, `docs/features/potential/promoted/2026-09-28-tests-depend-on-uncontrolled-environment.md`. That file was written by the promotion-lifecycle commit e13757267 before any plan task ran, is a mandatory output of the feature-promotion lifecycle for every full-bug item, and is not something execution created or could omit. The plan's footprint rule (D-11; Execution Conventions "Inherited paths", Clause A) defines the AC15 gate as the anchored diff minus paths inherited before the first task, and `p4-t11` records the subtraction explicitly as `INHERITED-PROMOTION-RECORD`. Reviewer reasoning: the criterion's purpose is scope containment of the fix (no production, governance or configuration change and no stray file), which is met; the literal enumeration omits a lifecycle record the workflow guarantees will be present, which is a wording defect in the spec, not a delivery defect. Graded PASS with the residual recorded as PA-1 (non-blocking) in the policy audit. Alternative reading, for the record: a strictly literal reading would grade PARTIAL on clause 2; it is not adopted because the plan ratified the subtraction before execution and the evidence discloses it. | +| AC16 | PASS | No | `parallel-suite-quickfiler-test.md`: 1468/1468/0, `TaskMaster.runsettings`, `/InIsolation`, "Filter: no test-case filter". `parallel-suite-utilitiescs-test.md`: 4922/4922/0, same runsettings and switch, filter verbatim excluding exactly `HelperClasses.ShellUtilities_Tests`, `HelperClasses.ShellUtilitiesStatic_Tests`, `HelperClasses.SysImageListHelperTests`, `EmailIntelligence.OSBrowser_Tests`, with the pre-existing-on-main note. Post-merge twins 1469/0 and 4924/0. Diff (read in full) adds no `[DoNotParallelize]`, no Workers/Scope change, no retry, no `Thread.Sleep`/`Task.Delay`/timeout/wall-clock wait; `p4-t11` seven `ADDED-` counts 0; runsettings SHA-256 unchanged in every run. | +| AC17 | PASS | No | `toolchain-pass.md` records, in CLAUDE.md order: csharpier check exit 0 (after format with REWRITTEN 0); analyzer rebuild exit 0, SKIP_CORECOMPILE_LINES 0; TreatWarningsAsErrors rebuild exit 0, SKIP_CORECOMPILE_LINES 0; MSTest-with-coverage route exit 0 with both floors met; ITERATIONS 1, LOOP: CLEAN PASS. Per-step artifacts `p4-t2`, `p4-t3`, `p4-t4`, `coverage-final.md` corroborate each figure. `post-merge-toolchain-pass.md` repeats all gates at 55a50e922 with the same results. The recorded commands add `/nodeReuse:false` (plan D-16, disclosed, additive). | +| AC18 | PASS (with disclosure) | No | `coverage-baseline.md` and `coverage-final.md` each contain a PROJECTION block (nine packages with LINE and BRANCH counters) and the one-line first-party summary. Final MEASUREMENT 2 versus baseline: UtilitiesCS LINE 38816/43424 both (0.893884), BRANCH 9411/11269 both (0.835123); QuickFiler LINE 10461/12754 both (0.820213), BRANCH 2518/3217 both (0.782717): all NOT-LOWER=True. The reviewer read the git-ignored `coverage/baseline-931.jacoco.xml` and `coverage/final-931.jacoco.xml` and confirmed the committed projections counter for counter. MEASUREMENT 1 read QuickFiler LINE one line lower on an identical denominator with zero production change; the plan's D-7 rule repeats the measurement once (a measurement, not a test retry) and MEASUREMENT 2 equals baseline; the difference is collector variance. Disclosure on the post-merge run (`post-merge-coverage-final.md`, not an AC18 subject): UtilitiesCS LINE 38811/43423 (0.893789) reads below the pre-merge baseline. AC18's text compares `coverage-final.md` against `coverage-baseline.md`, a like-for-like pre-merge pair, so the post-merge figure does not bear on the criterion. Reviewer assessment of the post-merge drop: the raw Cobertura class rows confirm `ILGlobals.cs` 38/40 -> 36/38 and `UiThread.cs` 130/133 -> 131/134, both production edits origin/main contributed (issue #930), which alone yield 38815/43423 = 0.893881 < 0.893884; the remaining 4 covered lines move between unchanged files between two identical measurements (variance). This item changed zero production lines, so no changed-line regression exists. Recorded as PA-4 (non-blocking). | +| AC19 | PASS | No | Glob `**/*.{trx,xml,coverage}` under the feature folder: no match; the diff name list contains no such extension. Grep of the feature folder for the developer account name, the `:/Users` and `:\Users` prefixes and the 8.3 short-name alias of the profile directory: no match; Grep for `Program Files`, `DESKTOP-`, `LAPTOP-`, `runUser`, `MachineName`, UNC prefixes: no value match (one `$env:COMPUTERNAME` token reference in the plan's sweep command). A `-o` sweep of every drive-letter path returns only the fixture literal `C:\Repo`. Placeholders `` appear in the mutation projections' stack traces and stream names. `p4-t12-hygiene-sweep` and `p4-t33` step 1 report all six match counts 0 and RAW-DOCUMENT-FILES 0. | + +## Acceptance Criteria Check-off + +- Newly checked off by this review: none (all 19 items were already `[x]` in spec.md; each was verified individually above before being left checked). +- Unchecked by this review: none (no criterion evaluated PARTIAL, FAIL or UNVERIFIED). +- spec.md was not modified by this review. + +### Acceptance Criteria Status +- Source: docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/spec.md +- Total AC items: 19 +- Checked off (delivered): 19 +- Remaining (unchecked): 0 +- Items remaining: none + +## Summary + +Verdict: PASS. 19 of 19 acceptance criteria verified on evidence; 17 clean PASS, 2 PASS with a recorded disclosure (AC15 promoted-record wording, AC18 post-merge package-rate observation attributable to origin/main). Blocking findings: 0. Non-blocking findings: 2 (carried as PA-1 and PA-4 in the policy audit). No remediation-inputs artifact is required. Follow-ups: the four potential entries in `evidence/qa-gates/p4-t13-follow-up-handoff.2026-09-29T09-46.md` remain for the orchestrator to file from a separate branch and to name in the PR body, per the caller's instruction and spec `## Rollout & Follow-up`. diff --git a/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/issue.md b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/issue.md new file mode 100644 index 000000000..0193e3f35 --- /dev/null +++ b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/issue.md @@ -0,0 +1,69 @@ +# tests-depend-on-uncontrolled-environment (Issue #931) + +- Date captured: 2026-09-28 +- Author: Dan Moisan +- Status: Promoted -> docs/features/active/tests-depend-on-uncontrolled-environment/ (Issue #931) + +> Automation note: Keep the section headings below unchanged; the promotion tooling maps each of them into the GitHub bug issue template. + +- Issue: #931 +- Issue URL: https://github.com/drmoisan/TaskMaster/issues/931 +- Last Updated: 2026-09-28 +- Work Mode: full-bug + +## Summary +Consolidates #905 and #906. The shared root cause: a unit test depends on environment state it does not control, so its result depends on scheduling or on other processes rather than on the code under test. + +1. **#905:** tests use `Task.Run` as the "other thread". `Task.Run` guarantees only a thread-pool thread, never a different one, so under parallel execution the guard under test can go unexercised. PR #904 (#900) fixed two instances. Remaining: + - `QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.cs:332`. The file is 490 lines, so the fix requires a split to stay under 500. + - Candidates to triage: + - `BreadcrumbSelectorToggleUiBoundaryTests.cs:75` + - `BreadcrumbPopupControlDispatchTests.cs:29,111` + - `BreadcrumbPopupBoundaryCoverageTests.cs:58` + - `BreadcrumbPopupBoundaryCoverageTests.Part2.cs:192` + - `BreadcrumbUiThreadDispatchTests.cs:90,301` + - `Task.Run(() => tcs.SetResult(...))` calls that only complete a task are not affected. +2. **#906:** `UtilitiesCS.Test/HelperClasses/FileInfoWrapper_Tests.cs:56-62` opens the repository's own `TaskMaster.sln`, found by `GetSolutionFile()` at lines 340-352, as a fixture. Resident MSBuild node-reuse workers can hold that file open, so the outcome depends on build history. + +## Environment +- OS/version: Windows 11 Pro 10.0.26200 +- Python version: not applicable (C#, MSTest, net48) +- Command/flags used: parallel regime `/Settings:TaskMaster.runsettings` (Workers 0, Scope ClassLevel) +- Data source or fixture: files listed above, `main` at `177b6d78e` + +## Steps to Reproduce +1. Inspect the cited `Task.Run` sites and confirm that each asserts a thread-identity property against the thread it obtained. +2. Inspect `FileInfoWrapper_Tests.GetSolutionFile()` and confirm it resolves the repository's own solution file. + +## Expected Behavior +- A test that needs a distinct thread uses a dedicated `Thread` that is joined, and asserts inside that thread that it is distinct (for example `CheckAccess() == false`) before exercising the guard. This is the #900 pattern. +- A file-handle test uses a stream the test owns, supplied through the wrapper's seam or an in-memory stream. It never uses a repository file. +- **Temporary files are prohibited by the unit-test policy and must not be used.** + +## Actual Behavior +The guard under test can pass without being exercised, and the file-open test can fail or pass depending on MSBuild worker residency. + +## Logs / Screenshots +- [ ] Attached minimal logs or screenshot +- Snippet: none (static findings, verified present on 2026-09-28) + +## Impact / Severity +- [ ] Blocker +- [ ] High +- [x] Medium +- [ ] Low + +## Suspected Cause / Notes +Both patterns were copied from earlier tests and survived because they usually pass. Tests must run in parallel. Do not fix either defect with `Workers=1`, `[DoNotParallelize]` or retries. + +## Proposed Fix / Validation Ideas +- [ ] Apply the #900 dedicated-thread pattern at every triaged site. Split `ItemViewerBreadcrumbThreadAffinityTests.cs` so it stays at or under 500 lines, and register any new file in `QuickFiler.Test.csproj`. +- [ ] Replace the `TaskMaster.sln` fixture with a test-owned stream or an injected seam. Add a seam to the wrapper only if one does not already exist. +- [ ] Each rewritten test must be shown to fail against a deliberately broken guard, so the test demonstrably exercises the guard. +- [ ] Run the full `QuickFiler.Test` and `UtilitiesCS.Test` suites in the parallel regime. + +## Next Step +- [x] Promote to GitHub issue (bug-report template) +- [ ] Move to active fix folder / branch + +Consolidates: #905, #906. \ No newline at end of file diff --git a/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/plan.2026-09-28T20-01.md b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/plan.2026-09-28T20-01.md new file mode 100644 index 000000000..27568affc --- /dev/null +++ b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/plan.2026-09-28T20-01.md @@ -0,0 +1,975 @@ +# 2026-09-28-tests-depend-on-uncontrolled-environment (Plan) + +- **Issue:** #931 (consolidates #905 and #906) +- **Parent (optional):** none +- **Owner:** drmoisan +- **Last Updated:** 2026-09-28 +- **Status:** Ready for preflight, revision round 4 applied (the seventeen deltas of the first executor preflight, the five deltas of the second, the three deltas of the third and the three deltas of the fourth are incorporated; planner internal review passed; executor validation-only preflight pending) +- **Version:** 1.0 +- **Work Mode:** full-bug (persisted marker `- Work Mode: full-bug` at line 12 of the feature issue document; the feature spec is the sole acceptance-criteria source, AC1 through AC19; no user-story document exists or is required) +- **Requirements source:** `docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/spec.md`, section `## Acceptance Criteria` +- **Branch:** bug/tests-depend-on-uncontrolled-environment-931 +- **Base:** origin/main. Every diff in this plan is anchored to `MERGE-BASE`, the merge base of HEAD and origin/main computed by P0-T3 at execution time after a fetch, never to a bare local main ref and never to a SHA pinned by this document. +- **Execution session requirement:** the executor runs later, non-isolated, from the item worktree, with `pwsh` available. Every command-bearing task runs either a `git` invocation or one `pwsh -NoProfile -Command` process whose first statement is `Set-Location -LiteralPath "WORKTREE"`, where `WORKTREE` is the absolute worktree root the delegation prompt supplies (no trailing separator). P0-T4 probes that channel and stops the run with `CHANNEL UNAVAILABLE` if it is refused; the executor never edits hook or permission configuration to obtain a channel. +- **Pre-implementation gate requirement:** the hook at .claude/hooks/enforce-orchestration-preimplementation-gate.ps1 classifies every edit of a `.cs` or `.csproj` file and every non-exempt `git add` or `git commit` as implementation and denies them unless artifacts/orchestration/orchestrator-state.json is seeded and ready (fact 22). P0-T3 records that readiness read-only and stops with `PRE-IMPLEMENTATION GATE NOT SEEDED` when it is absent. Seeding the checkpoint is the orchestrator's responsibility; the executor never creates, edits, or works around it. +- **Task Count:** 66 (Phase 0: 13, Phase 1: 1, Phase 2: 10, Phase 3: 9, Phase 4: 33) + +**Fail-closed evidence rule:** Include explicit baseline artifact tasks, final-QA artifact tasks, and coverage-comparison tasks for each in-scope language when policy requires coverage. If any required baseline artifact, QA artifact, or coverage-comparison artifact is missing, the audit verdict must be BLOCKED or INCOMPLETE, never PASS. One scoping carve-out: in a task that both commits and records that commit, the artifact's `EXIT_CODE:` field is scoped to the pre-commit observation the task names, and the commit's own exit code is reported under a separately named field or in the executor's final message, because an artifact cannot record the exit code of the commit that includes it. + +**Evidence accounting rule:** Record the expected artifact path or location in each evidence-producing task. Do not mark evidence-backed work complete without the artifact. + +--- + +## Binding Operator Constraints (received; encoded below) + +- Temporary files are prohibited in tests and in plan helper steps inside the repository. The file-handle fix uses the test-owned `FileStream` over the running test host's own loaded assembly image through the existing internal `FileInfoWrapper(IFileInfo)` seam; no repository file and no temporary file is opened, created, written or deleted by any test. Every scratch output this plan writes lives under the git-ignored coverage directory (.gitignore line 144) and is never an acceptance input except by its presence flags. +- Tests stay in the parallel regime: no task adds `[DoNotParallelize]`, changes Workers or Scope, adds a retry attribute or loop, or adds `Thread.Sleep`, `Task.Delay`, a timeout or a wall-clock wait. P4-T11 gates the added lines of the diff for those tokens. +- Only the two AFFECTED sites the spec names are rewritten; the twenty UNAFFECTED sites are byte-identical to the merge base (P4-T11). +- The 500-line ceiling is gated on every `.cs` file in the Write Set after the repository-wide format (P4-T9). +- Each negative control is applied, built, observed failing, reverted with `git checkout` of the mutated file, proven reverted by an anchored `git diff --exit-code HEAD` span plus a pathspec-scoped porcelain span plus a SHA-256 equality, rebuilt, and observed passing, inside one artifact per control (Phase 3). The final diff contains no production change (P3-T9, P4-T11). +- Committed evidence is Markdown projections only. No `.trx`, `.xml` or `.coverage` file is added anywhere (P4-T12). No committed text carries an absolute host path, an account name or a host name; the placeholders ``, ``, `` and `` are used instead (P4-T12 sweeps before the final commit). +- Evidence paths are canonical: FEATURE/evidence/baseline/, FEATURE/evidence/regression-testing/ and FEATURE/evidence/qa-gates/ only. No task writes under artifacts/ except that the orchestrator-owned checkpoint under artifacts/orchestration/ is read by P0-T3. + +## Execution Conventions + +- `FEATURE` denotes `docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931`. `WORKTREE` denotes the absolute worktree root supplied by the delegation prompt, without a trailing separator. Both tokens are expanded by the executor; `WORKTREE` is never written into any artifact, commit message or console transcript that is copied into an artifact. +- **Evidence paths.** Every artifact is written under FEATURE/evidence/baseline/, FEATURE/evidence/regression-testing/ or FEATURE/evidence/qa-gates/. Nothing is written to artifacts/baseline/, artifacts/baselines/, artifacts/qa/, artifacts/qa-gates/, artifacts/evidence/, artifacts/coverage/, artifacts/regression-testing/ or artifacts/post-change/. +- **Artifact filenames.** The spec fixes ten projection names without a timestamp suffix: test-run-baseline.md and coverage-baseline.md under baseline/; mutation-owner-only-dispatcher-guard.md, mutation-null-owner-escape.md, mutation-inline-precondition.md, mutation-openread-sentinel.md, parallel-suite-quickfiler-test.md and parallel-suite-utilitiescs-test.md under regression-testing/; toolchain-pass.md and coverage-final.md under qa-gates/. The atomic-plan contract fixes phase0-instructions-read.md under baseline/. Every other artifact is named `-..md` where `` is the executor's write time in `yyyy-MM-ddTHH-mm`, equal to the artifact's own `Timestamp:` field; later tasks locate it with the glob `-.*.md`, which must match exactly one file. A task re-run inside a loop (the P2-T7 through P2-T9 repair iteration of P2-T9, and the Phase 4 restart from P4-T1) writes one new `-..md` artifact per iteration, each carrying an `ITERATION:` line (1 for the first run); for such a task the glob may match more than one file, and every later comparison reads the artifact with the highest `ITERATION:` value, in particular the P2-T7 post-format hashes that P2-T10 compares against and the P4-T1 values that P4-T9 and P4-T10 read. A fixed-name artifact written inside a loop is rewritten by each iteration and records the same `ITERATION:` line. +- **Artifact fields.** Every command-step artifact carries `Timestamp:`, `Command:`, `EXIT_CODE:` and `Output Summary:`. `ExpectedExitCode:` is written only where a task says so, at most once per artifact, and always equal to the observed value it explains. An artifact that records several commands names the one invocation its `EXIT_CODE:` row is scoped to and records the other exit codes as named `Output Summary:` lines. `Command:` records the plan's canonical form with every token except `WORKTREE` substituted. +- **Command channel.** Every `pwsh` payload is written as `pwsh -NoProfile -Command ''`: outer single quotes so the calling shell performs no interpolation, the payload's lines joined by `; `, its first statement `Set-Location -LiteralPath "WORKTREE"`. No payload contains a single-quote character; every string literal is double-quoted; a double quote inside a double-quoted literal is doubled; no double-quoted literal ends with a backslash immediately before its closing quote. A script that must run as its own process is started from inside a payload as `& pwsh -NoProfile -File (Join-Path (Get-Location).Path "scripts\vscode\.ps1")`, that is by absolute script path resolved at run time; `pwsh -WorkingDirectory` is never used because it does not resolve the `-File` path. The child's exit code is read from `$LASTEXITCODE` after merging its error stream with `2>&1`; a PowerShell try/catch cannot observe an external process failure. +- **Working directory.** Every path inside a payload is repository-relative after the `Set-Location`; cmdlets take `-LiteralPath`; .NET static file APIs are never given a relative path because `Set-Location` does not update the .NET current directory. +- **File hashes.** Every SHA-256 this plan records or compares is the `Hash` property of `Get-FileHash -Algorithm SHA256 -LiteralPath `; the `Path` property is never recorded because it is absolute. +- **Tool resolution inside payloads.** vswhere.exe is `Join-Path ${env:ProgramFiles(x86)} "Microsoft Visual Studio\Installer\vswhere.exe"`; vstest.console.exe is `& $vswhere -latest -products * -find "Common7\IDE\Extensions\TestPlatform\vstest.console.exe" | Select-Object -First 1`; MSBuild.exe is `& $vswhere -latest -requires Microsoft.Component.MSBuild -find "MSBuild\**\Bin\MSBuild.exe" | Select-Object -First 1` (the shape scripts/vscode/Invoke-Restore.ps1 uses at its line 31). The `Command:` field records the CLAUDE.md-canonical `msbuild ...` and `vstest.console.exe ...` forms plus the note `resolved through vswhere`. +- **MSBuild node reuse.** Every msbuild invocation this plan issues or starts carries the additional switch /nodeReuse:false so the plan's own worker nodes exit when the build ends and hold no repository file open during a later test run (D-16). The switch changes no diagnostic, target or property; the `Command:` field records the CLAUDE.md-canonical command and the note `plus /nodeReuse:false`. The one msbuild invocation this plan starts but does not issue itself, the restore inside scripts\vscode\Invoke-Restore.ps1 (P0-T5), receives the same instruction through the environment variable MSBUILDDISABLENODEREUSE set to 1 in the P0-T5 payload before the script is started; a child process inherits it and MSBuild reads it as the equivalent of the switch. +- **Console counters.** On an all-green run vstest.console.exe prints `Test Run Successful.`, `Total tests:`, `Passed:`, `Total time:` and no `Failed:` or `Skipped:` line; on a failing run it prints `Test Run Failed.`. Per-test outcomes and failure messages are read from the TRX the run writes (the ResultSummary/Counters attributes `total`, `executed`, `passed`, `failed`; the `UnitTestResult` attributes `testName` and `outcome`; the Output/ErrorInfo/Message element). TRX files are written under coverage\test-results\931\\ (git-ignored by .gitignore line 144) and are never copied into FEATURE/evidence/. Every direct run passes an explicit results directory and a quoted trx logger with an explicit log file name, so no file name carries an account or machine name. +- **Runsettings for direct runs.** Every direct vstest.console.exe run in this plan uses the repository-root runsettings file TaskMaster.runsettings (Workers 0, Scope ClassLevel, lines 4 through 7) with the isolation switch, as spec AC16 requires; its SHA-256 is recorded once by P0-T4 as `RUNSETTINGS-HASH:` and re-read by every run as `RUNSETTINGS-HASH-NOW:`. That file also declares a Code Coverage data collector (lines 9 through 29); when the installed test platform activates it, the collector's own output lands under the run's results directory inside the git-ignored coverage tree, and the count of console lines mentioning `Code Coverage` is recorded as `COLLECTOR_LINES:` (an observation, not a gate). The coverage route (P0-T11, P4-T7) uses the CLI twin scripts/vscode/TaskMaster.cli.runsettings through the runner script, exactly as CLAUDE.md step 4 does. +- **Token census.** Every occurrence count in this plan is produced by `CMD-CENSUS` (Command Reference), which reads one file and counts case-sensitive, non-overlapping occurrences with `[regex]::Matches($content, [regex]::Escape($token)).Count`. Counts are occurrences, not lines. `Select-String` is never used for a count because it is case-insensitive by default and counts lines. +- **Refs.** P0-T3 records `BASE-SHA:` (HEAD before any task edits a file) and `MERGE-BASE:` (the output of `git merge-base HEAD origin/main` after `git fetch origin main`). P2-T10 records `FIX-HEAD:` and one `FIX-HASH-:` per Write Set code file, each taken after that task's line-ending restore (fact 23), so that the bytes a later `git checkout` of the path writes under `core.autocrlf=true` equal the anchored bytes. Command spans write the bare tokens; the executor substitutes the recorded values, because no shell variable survives a task boundary. +- **Inherited paths (rule, not list).** Clause A: every path already changed relative to `MERGE-BASE` before the first task ran, captured mechanically by P0-T3 as `INHERITED-CLAUSE-A:` (the union of `git diff --name-only MERGE-BASE...HEAD` and `git status --porcelain --untracked-files=all`). Clause B: every path under the prefix .claude/agent-memory/, which is tracked and is written by agents while this plan executes. Footprint gates subtract the recorded Clause A set and the Clause B prefix, record the subtraction explicitly, and never subtract a Write Set path. +- **Git gates are pathspec-scoped.** Every `git diff` and `git status` gate carries an explicit pathspec so that agent-memory writes and sibling-worktree residue cannot fail it. No gate asserts an empty unscoped porcelain output. Every `git diff` carries a ref operand; every name-listing diff is paired with a porcelain span in the same task. +- **Commits.** This plan commits three times, each as `git add -- ` followed by a separate `git commit` invocation, one command segment per invocation, never chained. One rule governs every commit message in this plan (P0-T13, P2-T10, P4-T33): no `-m` paragraph contains an angle bracket, a dollar sign or a backtick character, and no such character appears anywhere on a `git commit` line. When the executing session requires an attribution trailer, it is supplied as a second `-m` paragraph written without angle brackets around the address, in the form Co-Authored-By: Claude Opus 5.5 noreply@anthropic.com (the model name is whatever the session supplies; the address is written bare); a session line is added the same way. P0-T13 stages only the exempt tree docs/features/active/ and keeps the issue #539 exemption form (the `-m` paragraphs, one pathspec operand after `--`, no other option). P2-T10 and P4-T33 stage paths outside every exempt tree, so they run only in a session whose pre-implementation checkpoint P0-T3 recorded as ready. No `git add` span in this plan names a path under .claude or under config, and the executor never stages or commits a path under either tree on this branch, whatever the porcelain output shows there; P4-T11 records the anchored committed diff of those two trees as `COMMITTED-CLAUDE-OR-CONFIG:` and requires `NONE`. Pathspecs inside backticked spans that name a path outside the Write Set are written with backslash separators, which git on Windows accepts; the executor issues git from a PowerShell prompt where a backslash is literal, and if it must issue git from a POSIX shell it encloses each backslash-bearing pathspec in single quotes so the separators survive. A PreToolUse refusal of any `git add`, `git commit`, `.cs` edit or `.csproj` edit is reported verbatim as `PRE-IMPLEMENTATION GATE BLOCKED` and stops the run; the executor does not modify hooks, checkpoints, permission configuration or another item's state. +- **Stall handling and the two recorded selections.** P0-T9 runs the four UtilitiesCS.Test shell-icon classes alone under the hang-dump blame collector and records exactly one `STALL-PROBE:` value: `CLEAR` (exit 0, `failed` 0, no Sequence document) or `REPRODUCES` (anything else). That value fixes two later substitutions: `UCS-FILTERARG` is empty under `CLEAR` and is the quoted exclusion filter of the four classes under `REPRODUCES` (the single filter spec AC16 permits, recorded verbatim in every artifact that uses it); `COVERAGE-ROUTE` is `RUNNER` under `CLEAR` (the CLAUDE.md step 4 runner script) and `DIRECT` under `REPRODUCES` (the runner's own inner collector invocation with the same four-class exclusion appended, because the runner hard-codes its filter at line 91 and offers no extension point). Every direct vstest and DIRECT-route run carries the hang-dump blame argument so a stalled test is named in a `Sequence_*.xml` document rather than left silent; a run that produces one is recorded as failed with that test name. On the coverage route, P0-T11 and P4-T7 treat a Sequence document, or an absent stage TRX, as `COVERAGE RUN ABORTED` (their branch (d0)): the executor reports the collector log's last lines and the named test, does not run the post-processing payload, and does not re-run. The RUNNER route passes no blame argument (the runner's argument list is fixed), so P0-T11 and P4-T7 bound it by wall clock instead: a RUNNER run still in progress after 120 minutes is `COVERAGE RUN STALLED`, stop and report. +- **Negative-control mechanics.** Each control: apply the mutation with the Edit tool; run `CMD-CENSUS` on the mutated file; build with `CMD-BUILD`; run with `CMD-VSTEST` under the control's filter; record the failing run (`ExpectedExitCode: 1`); revert with `git checkout -- `; prove the revert with `git diff --exit-code HEAD -- ` (exit 0), `git status --porcelain -- ` (empty) and the file's SHA-256 equal to its FIX-HASH-n: anchor from P2-T10 (recorded after that task's line-ending restore, fact 23, so a `git checkout` under `core.autocrlf=true` reproduces it byte for byte) or, for a production file, to its P0-T12 SHA256 (the two production files are never edited between P0-T12 and Phase 3 and are outside the scoped P2-T7 format); rebuild; run the same filter again and record the passing run. The fix commit P2-T10 precedes every control so that `git checkout` restores exactly the committed fixed state. +- **Artifact hygiene.** Before any text is written into an artifact, an absolute path is replaced by `` (or `` when it lies under the user profile), the account name by `` and the machine name by ``. The `ASSEMBLY:` lines the direct coverage route prints are already worktree-relative (they begin with a backslash). + +## Write Set + +Code and project files (six; one line each, exactly the spec's Write Set): + +- `QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.cs` (modify: becomes partial; loses the three cross-thread tests, `ClearViewerDispatcher` and `RunOnDedicatedWorkerThread`) +- `QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.Part2.cs` (create: partial continuation with the three cross-thread tests and `ClearViewerDispatcher`) +- `QuickFiler.Test/TestSupport/DedicatedWorkerThread.cs` (create: shared dedicated-thread helper) +- `QuickFiler.Test/Viewers/BreadcrumbPopupBoundaryCoverageTests.cs` (modify: rewrite `Dispatcher_OwnerOnlyWorker_ReportsWithoutRunningAction`) +- `QuickFiler.Test/QuickFiler.Test.csproj` (modify: two Compile Include entries) +- `UtilitiesCS.Test/HelperClasses/FileInfoWrapper_Tests.cs` (modify: rewrite four tests; remove `GetSolutionFile`) + +Evidence directories (three; created by this run): + +- `docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/baseline/` +- `docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/regression-testing/` +- `docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/qa-gates/` + +Feature documents whose checkbox state this run edits (admitted by spec AC15's feature-folder clause; no criterion text or plan text changes): + +- `docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/spec.md` +- `docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/plan.2026-09-28T20-01.md` + +## Outside the Write Set (plain prose; none of these paths is modified by the final diff) + +Production files, never changed in the final diff: QuickFiler/Viewers/BreadcrumbUiDispatcher.cs (P3-T1 edits lines 276 to 277 temporarily; P3-T2 reverts it) and QuickFiler/Viewers/ItemViewer.Breadcrumb.cs (P3-T3 edits lines 435 to 438 temporarily; P3-T4 reverts it); every other file under QuickFiler/ and UtilitiesCS/, including UtilitiesCS/HelperClasses/FileSystem/FileInfoWrapper.cs whose internal seam at lines 21 to 24 is used as is. + +The twenty UNAFFECTED Task.Run sites in QuickFiler.Test (spec Triage table): Helper Classes/EmailMoveMonitorTests.cs, Viewers/BreadcrumbCoordinatorLifecycleTests.cs, Viewers/BreadcrumbPopupControlDispatchTests.cs, Viewers/BreadcrumbPopupBoundaryCoverageTests.Part2.cs, Viewers/BreadcrumbSelectorToggleUiBoundaryTests.cs, Viewers/BreadcrumbSelectorOpenRetryTests.cs, Viewers/BreadcrumbUiThreadDispatchTests.cs, Controllers/QfcItemController.UiThreadDispatcherFixtureTests.cs; and the two pre-existing `[DoNotParallelize]` attributes in Helper Classes/EmailMoveMonitorTests.cs line 24 and Helper Classes/ViewerQueueStaticWrapperTests.cs line 11. + +Configuration read but never written: TaskMaster.runsettings, scripts/vscode/TaskMaster.cli.runsettings, coverage.config, every script under scripts/vscode/, the .claude directory, config/blast-radius.json, config/orchestration-routing.json, artifacts/orchestration/orchestrator-state.json. + +Local, git-ignored, never staged: everything under coverage/ (.gitignore line 144; coverage/.gitkeep is tracked so the directory exists), packages/, .dotnet-sdk/, every bin and obj directory. + +## Verified Repository Facts + +Each fact was re-derived against the assigned worktree on 2026-09-28 during plan authoring with the Read and Grep tools. + +1. `QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.cs` is 490 lines (the terminating newline is not a line) and declares `[TestClass] public sealed class ItemViewerBreadcrumbThreadAffinityTests` at lines 29 to 30 with seven `[TestMethod]` members at lines 38, 88, 128, 167, 218, 268, 318 (method declarations at 39, 89, 129, 168, 219, 269, 319). Its using block is lines 1 to 12 (`System`, `System.Collections.Generic`, `System.Drawing`, `System.Reflection`, `System.Threading`, `System.Threading.Tasks`, `System.Windows.Threading`, `FluentAssertions`, `Microsoft.VisualStudio.TestTools.UnitTesting`, `Moq`, `QuickFiler.Viewers`, `UtilitiesCS.OutlookObjects.Folder`); the class remarks are lines 21 to 28. It carries no nullable directive. +2. In that file: the two dedicated-thread tests span lines 199 to 251 and 253 to 304 (documentation through closing brace); the null-owner test spans 306 to 346 with its `Task.Run` at 332 and its blocking wait at 335 to 336, and its remark at 311 to 317; line 347 is blank; `InertOperations` is 348 to 355; line 356 is blank; `ClearViewerDispatcher` is 357 to 371; line 372 is blank; `RunOnDedicatedWorkerThread` is 373 to 403 (remark 377 to 384, body 385 to 403, `action();` at 392); line 404 is blank; `InertDropDownHost` 405 to 434; `DrainableSynchronizationContext` 436 to 461; `ViewerScope` 463 to 488. The precondition reason literal `the dedicated worker thread must not be the thread that constructed ` sits on one line at 234 and 283; the remark literal `distinct from every live thread by construction` sits on one line at 380. +3. Pre-edit occurrence counts in that file (positive controls; P0-T12 re-records them): `Task.Run(` 1; `.GetAwaiter()` 1; `RunOnDedicatedWorkerThread` 5; `ClearViewerDispatcher(` 2; `partial class` 0; `[TestMethod]` 7; `action();` 1; `new Thread(` 1; `IsBackground = true` 1; `thread.Join();` 1; `using System.Reflection;` 1; `dedicated worker thread must not be` 2; `distinct from every live thread by construction` 1; `unconditionally` 0; `owner.CheckAccess()` 0; `.BeNull(` 0; `DedicatedWorkerThread.Run(` 0; `using QuickFiler.Test.TestSupport;` 0; `Thread.Sleep`, `Task.Delay`, `[Timeout`, `DoNotParallelize` 0 each. +4. `QuickFiler.Test/Viewers/BreadcrumbPopupBoundaryCoverageTests.cs` is 361 lines, declares `public sealed partial class BreadcrumbPopupBoundaryCoverageTests` at line 17 (its continuation partial carries the same declaration at line 23 of its Part2 file and no `[TestClass]`), and holds `Dispatcher_OwnerOnlyWorker_ReportsWithoutRunningAction` at lines 52 to 62 with `Task.Run(` at 58 and the blocking wait at 59; `CreateOwnerOnlyDispatcher` is 124 to 134 and passes `Environment.CurrentManagedThreadId` as the owner at 133. Its using block is lines 1 to 12 with `using Moq;` at 11 and `using QuickFiler.Viewers;` at 12. Pre-edit counts: `Task.Run(` 1; `.GetAwaiter()` 4 (59, 297, 305, 308); `DedicatedWorkerThread.Run(` 0; `using QuickFiler.Test.TestSupport;` 0; `ownerThreadId` 0; `.NotBe(` 0; `.BeNull(` 0; `executions.Should().Be(0)` 1; `cannot marshal` 1; `dedicated worker thread must not be` 0; `partial class` 1; `Thread.Sleep`, `Task.Delay`, `[Timeout`, `DoNotParallelize` 0 each. The test name is unique in QuickFiler.Test. +5. `UtilitiesCS.Test/HelperClasses/FileInfoWrapper_Tests.cs` is 359 lines, declares `[TestClass] public class FileInfoWrapper_Tests` at 12 to 13 with the class opening brace at 14, eight `[TestMethod]` members (15, 25, 41, 55, 69, 83, 183, 297), the four rewritten tests at 25 to 39, 41 to 53, 55 to 67, 69 to 81, a blank line at 338, and `GetSolutionFile` at 339 to 357 (the `AppDomain` walk-up at 341, the `TaskMaster.sln` literal at 345, `File.Exists(` at 346); line 358 closes the class. It carries no nullable directive and already uses `using var` (line 62) and the rooted literal C:\Repo (line 87). Pre-edit counts: `GetSolutionFile` 5; `TaskMaster.sln` 1; `AppDomain` 1; `File.Exists(` 1; `File.Create`, `File.WriteAll`, `File.Delete`, `Path.GetTemp` 0 each; `FileMode.` 12; `FileMode.Open` 12; `FileAccess.` 10; `FileAccess.Read` 10; `FileShare.ReadWrite` 8; `Assembly.Location` 6; `FixturePath` 0; `using var sentinel = new FileStream(` 0; `BeSameAs(sentinel)` 0; `.Returns(decoy)` 0; `wrapper.OpenRead()` 2; `stream.CanRead.Should().BeTrue()` 1; `stream.Length.Should().BeGreaterThan(0)` 1; `[TestMethod]` 8; `Thread.Sleep`, `Task.Delay`, `[Timeout`, `DoNotParallelize` 0 each. The name `OpenRead_ShouldReturnReadableStreamForWrappedFile` is unique in UtilitiesCS.Test. +6. `QuickFiler.Test/QuickFiler.Test.csproj` uses explicit Compile Include entries: the affinity file at line 98, the boundary file and its Part2 at 105 to 106, the TestSupport entries at 227 to 228 (WinFormsPumpHost.cs, WinFormsPumpHostTests.cs). It defaults Platform to `AnyCPU` at line 12, sets OutputPath bin\Debug\ at 36, and references QuickFiler.csproj by ProjectReference at line 510 (UtilitiesCS.csproj at 514). UtilitiesCS.Test/UtilitiesCS.Test.csproj (outside the Write Set) registers FileInfoWrapper_Tests.cs at line 234, defaults Platform to `AnyCPU` at 11, OutputPath bin\Debug\ at 51, and references UtilitiesCS.csproj by ProjectReference at 963. QuickFiler.csproj and UtilitiesCS.csproj are libraries with AssemblyName `QuickFiler` and `UtilitiesCS` (lines 9 and 12; 12 and 15), so a project build of a test project rebuilds its production project when a production file changed, and the compiler echo for each carries the output path obj\Debug\.dll after its out switch. QuickFiler.csproj sets no TreatWarningsAsErrors property. +7. QuickFiler.Test/TestSupport/WinFormsPumpHost.cs declares `namespace QuickFiler.Test.TestSupport` at line 9; the new helper joins that namespace. +8. QuickFiler/Viewers/BreadcrumbUiDispatcher.cs: `Dispatch(Action)` is lines 71 to 151 and calls `IsCurrentBoundary()` at 78; the null-context rejection path reports `The owner-thread-only test dispatcher cannot marshal cross-thread UI work.` and returns `Task.CompletedTask` at 97 to 105; `IsCurrentBoundary()` is 255 to 278; the executing-callback branch returns true at 260; the captured-context branch is 269 to 272; the owner-thread-id branch is lines 276 to 277 (`return _ownerThreadId.HasValue` / `&& Environment.CurrentManagedThreadId == _ownerThreadId.Value;`). The file carries `#nullable enable`. +9. QuickFiler/Viewers/ItemViewer.Breadcrumb.cs: `ThrowIfOffUiBoundary` is lines 432 to 447; the null-owner escape is 435 to 438 (`if (owning == null)` / `{` / `return;` / `}`); the throw is 440 to 446. Its using block (lines 1 to 9) does not import `System.Threading`, so mutation M2 names `System.Threading.SynchronizationContext.Current` fully qualified. QuickFiler/Viewers/ItemViewer.cs captures `_uiDispatcher = Dispatcher.CurrentDispatcher;` at 27, declares `public SynchronizationContext UiSyncContext` at 59 and `public Dispatcher UiDispatcher` at 65 to 67; `Dispatcher.CheckAccess()` is thread-object identity. +10. UtilitiesCS/HelperClasses/FileSystem/FileInfoWrapper.cs: public constructor 14 to 19 wraps a `PhysicalFileInfoAdapter`; internal seam constructor 21 to 24; `OpenRead()` 153 to 156; `ToString()` 191 to 194; explicit `DirectoryInfoWrapper` cast 211 to 214. UtilitiesCS/Properties/AssemblyInfo.cs line 19 carries `InternalsVisibleTo("UtilitiesCS.Test")` (line 18 `DynamicProxyGenAssembly2`). UtilitiesCS/Interfaces/IHelperClasses/IFileInfo.cs line 26 declares `FileStream OpenRead();`. UtilitiesCS/HelperClasses/FileSystem/PhysicalFileInfoAdapter.cs: `Directory` at 104 wraps `FileInfo.Directory`, `DirectoryName` at 108, `OpenRead()` at 154 (unseamed), `ToString()` at 181; all are path computations or metadata reads for a non-existent rooted path. +11. TaskMaster.runsettings (30 lines): `Workers` 0 at line 5, `Scope` ClassLevel at line 6, a `Code Coverage` data collector at lines 9 to 29. scripts/vscode/TaskMaster.cli.runsettings (9 lines): the same Workers and Scope, no collector, no logger. +12. scripts/vscode/Invoke-MSTestWithCoverage.ps1 (439 lines): parameters `SearchRoot`, `Configuration`, `CoverageOutput` (default coverage\coverage.cobertura.xml), `NoExecute` only; no test-filter and no runsettings parameter. `Get-DotnetCoverageArgumentList` (41 to 95) hard-codes the LiveOutlook category test-case filter at 91 and passes the results directory and trx logger at 92 to 93; `ConvertTo-DerivedCoverageSettingsXml` 97 to 134 appends the test-assembly module exclusion; `Invoke-DotnetCoverageCollection` throws `MSTest with coverage failed with exit code ` at 261 to 263 on a non-zero collector exit, before any post-processing; the entry point resolves the repository root from its own location (305), the CLI runsettings from its directory (312), discovers `*.Test.dll` under bin\\ excluding obj, ref and .claude relative paths (330 to 337), fixes ResultsDirectory coverage\test-results and LogFileName mstest-coverage-run.trx (283 to 284), post-processes the Cobertura in place (382 to 384), asserts the 80 percent line and 75 percent branch floors (386 to 387), prints the one-line first-party summary (388), writes the JaCoCo package projection beside the Cobertura as coverage\coverage.cobertura.jacoco.xml (393 to 401), writes coverage\test-results\mstest-coverage-run.summary.txt (417 to 425), retains the raw document because it lies directly in the coverage directory (427 to 431), and is guarded for dot-sourcing at 437. Dot-sourcing it sets strict mode and `$ErrorActionPreference = 'Stop'` (271 to 272). +13. scripts/vscode/Invoke-MSTestWithCoverage.Helpers.ps1 dot-sources the ClosureFilter, PackageRate, Threshold, FirstParty and Projection part files at lines 2 to 6 and declares `ConvertTo-KoverageCoberturaXml` at 407. The Projection part file declares `ConvertTo-JacocoPackageProjection` (14 to 81: one `package` element per source package with exactly two counters, LINE then BRANCH, attributes `missed` and `covered`), `Assert-JacocoProjectionReconciliation` (83 to 146) and `Test-RawCoverageDocumentRetained` (148 to 197). The FirstParty part file declares `Get-CoberturaFirstPartyCoverageReport` (123 to 162), whose output line has the shape `First-party coverage: lines / (

%), branches / (

%)`. The Threshold part file throws `Cobertura line coverage

% is below the required 80% threshold.` and `Cobertura branch coverage

% is below the required 75% threshold.`. scripts/vscode/Invoke-MSTest.TrxSummary.ps1 declares `Get-TrxRunSummary` (12 to 101) and `Format-TrxRunSummary` (103 to 150), whose five rendered lines begin `Test run outcome:`, `Total `, `Skipped `, `Figures reported verbatim by the test platform:` and `Failed tests:`. +14. scripts/vscode/Invoke-Restore.ps1 runs `msbuild /t:Restore /p:Configuration=Debug "/p:Platform=Any CPU" /p:RestorePackagesConfig=true /m` with parameters `SolutionPath`, `Configuration`, `Platform`. scripts/vscode/Install-RepoDotNetSdk.ps1 installs SDK 8.0.205 into .dotnet-sdk at the repository root (line 36), which global.json names in its `paths` array; dotnet-tools.json at the repository root pins csharpier 1.2.6 (commands `format` and `check`; a clean `check .` prints `Checked N files in Xms.` and `format .` prints `Formatted N files in Xms.`, N being the processed count in both). +15. .csharpierignore (18 lines) excludes every evidence tree, cobertura XML, coverage, coveragexml and trx documents, csproj, props and targets files, and every packages.config and app.config; CSharpier 1.2.6 processes C# source and XML files. .gitignore ignores every test-results directory (line 39), coverage and coveragexml documents (140 to 141), everything under the coverage directory except its gitkeep marker (144 to 145) and the packages directory (190); a trx document is not ignored by any pattern, which is why every TRX in this plan is written under coverage\. +16. The four UtilitiesCS.Test classes that stall a local vstest run on this workstation (observed 2026-09-04, excluded by the issue #781 and #900 plans) are `HelperClasses.ShellUtilities_Tests`, `HelperClasses.ShellUtilitiesStatic_Tests`, `HelperClasses.SysImageListHelperTests` and `EmailIntelligence.OSBrowser_Tests`; `DictionaryExtensions_Tests.TryAddValuesAsync_UpdatesExistingValue` is a known intermittent failure under parallel coverage runs (issue #780). Neither QuickFiler.Test nor UtilitiesCS.Test carries a `LiveOutlook` test category (grep over both trees: zero hits), so the two full-assembly parallel-suite runs need no category filter. +17. The #900 precedent run recorded exactly the #906 failure: its final coverage iteration 1 failed on `FileInfoWrapper_Tests.OpenRead_ShouldReturnReadableStreamForWrappedFile` with the solution file held by a resident MSBuild node-reuse worker left by the plan's own multi-process rebuilds (docs/features/active/breadcrumb-thread-affinity-tests-assume-taskrun-distinct-thread-900/evidence/qa-gates/p5-t5-mstest-coverage.2026-09-17T02-35.md, loop-context section). +18. The feature issue document line 12 is `- Work Mode: full-bug`. The spec's `## Acceptance Criteria` holds nineteen lines beginning `- [ ] AC1. ` through `- [ ] AC19. ` (lines 236 to 254), each on one line; the check-off edit changes only `- [ ] ACn.` to `- [x] ACn.`. No user-story document exists. The spec's header line 9 declares full-bug and names the spec the sole AC source. +19. .claude/hooks/validate-planner-output.ps1 requires every task's opening line to carry a slash- or backslash-bearing path token (line 95), the phase heading form with an em dash (238), sequential task ids per phase (299 to 302), a policy-read and a baseline task in Phase 0 (325 to 330) and QA vocabulary in the final phase (339). +20. .claude/rules/plan-acceptance-gates.md: G8 reports a `git diff` with no ref operand; G8b reports a name-listing diff with neither a `git add` nor a `git status --porcelain` companion in the same task; G7 reads a fixed six-entry write-mode register that does not include CSharpier, so the csharpier tasks carry their own before-and-after tree observation rather than relying on the exit code. +21. FluentAssertions failure phrases relied on by the negative controls: `Be(0)` on an integer prints `to be 0, but found 1`; `BeNull()` on a reference prints `to be ` followed by `but found` and the found object's type name; `BeFalse(reason)` and `NotBe(value, reason)` append `because `; `BeSameAs(expected)` prints `to refer to`. The phrase `to contain` was observed verbatim in the #900 P3-T1 artifact for `Contain`, which confirms the family's message shape on this FluentAssertions version. +22. .claude/hooks/enforce-orchestration-preimplementation-gate.ps1 reads its checkpoint from artifacts/orchestration/orchestrator-state.json (line 31) and its readiness predicate requires the string properties `issue-num` and `feature-folder` (235 to 236), `route_id` or, when absent, `path_selected` (237 to 239), and a truthy `lifecycle_ready` (242 to 243); an absent file or a missing property denies with the reason at 429. The helpers file's issue #539 exemption admits only `git add` and `git commit` segments whose every operand lies under docs/features/epics/, docs/features/parallel/, docs/features/active/, docs/features/potential/ or artifacts/orchestration/, models only `-m` and `--message` forms, requires at least one pathspec operand, and denies any line containing `$`, backtick, `>` or `<`. +23. Line endings. .gitattributes line 4 declares `* text=auto`, so git stores every text file with LF endings in the index and writes the working-tree ending that `core.autocrlf` selects; the executing session's `core.autocrlf` is `true` (observed during the round-4 preflight: `git config --show-origin --get-all core.autocrlf` issued against this worktree reports `true` from the Git for Windows system configuration file, and `git ls-files --eol` reports i/lf and w/crlf for the four existing Write Set files and for the two production files the controls mutate; P2-T10 still observes the effect through the working-tree column, the w/ column, rather than relying on the setting). A file the Write tool creates has LF endings, so its working-tree bytes differ from the bytes `git checkout` writes for the same path even though `git diff --exit-code HEAD` exits 0, because the diff compares normalized content. .editorconfig line 669, inside the `[*.{cs,vb}]` section that opens at line 619, sets `end_of_line = crlf`; no .csharpierrc file exists anywhere in the worktree, and CSharpier documents reading `end_of_line` from .editorconfig, so a CRLF `.cs` file is a fixed point of `csharpier format`, and P2-T7's scoped format may already have converted the two new files (P2-T10 records which case occurred under `EOL-RESTORED:`; either case satisfies P2-T10). A CRLF file restored by P2-T10 is therefore not rewritten by P4-T1; whether the pinned formatter did rewrite any Write Set file is observed through P4-T1's `REWRITTEN:` count, never assumed. + +## Decisions + +- **D-1 Helper contract is identical to the removed private helper.** `DedicatedWorkerThread.Run(Action)` carries the body of `RunOnDedicatedWorkerThread` unchanged (no null-argument guard is added, because the spec requires the identical body and AC7 requires no assertion), the removed remark's text, with its issue reference widened to name issue #931 and its lines re-wrapped so the census literal sits on one line, plus one added sentence stating that the helper asserts nothing itself (Target Source A is the exact content). Every local name is pinned (`captured`, `thread`, `error`) so count gates cannot drift. +- **D-2 Part2 partial carries no `[TestClass]`.** The repository convention for continuation partials (fact 4) puts the attribute on the primary declaration only; the attribute applies to the whole type. The primary file keeps `[TestClass]` and gains `partial`. +- **D-3 Direct runs use the root runsettings file.** Spec AC16 names the root file; its Workers and Scope equal the CLI twin's. The collector it declares is recorded, not gated (Execution Conventions). +- **D-4 Coverage route is selected by a recorded observation.** `COVERAGE-ROUTE: RUNNER` runs scripts/vscode/Invoke-MSTestWithCoverage.ps1 verbatim (CLAUDE.md step 4). `COVERAGE-ROUTE: DIRECT` runs the runner's inner collector invocation directly, built from the same functions (`ConvertTo-DerivedCoverageSettingsXml`, the same assembly discovery filter, the CLI runsettings, the isolation switch, the LiveOutlook category filter) with the four-class exclusion appended, and post-processes with the runner's own helpers, so both routes yield the same three committed forms: the JaCoCo package projection, the one-line first-party summary and the TRX-derived summary. Under either route the raw Cobertura and the TRX stay under coverage\ and are never copied into the feature folder. +- **D-5 The fix is committed before the controls (P2-T10).** Each revert is then anchored: `git checkout -- ` restores exactly the committed fixed state, `git diff --exit-code HEAD -- ` exits 0 and the SHA-256 equals `FIX-HASH-:`. +- **D-6 Predicted failing assertion per control.** M1 (P3-T1): `Dispatch` runs the action inline on the worker, `captured` is null, and `executions.Should().Be(0)` fails with a message containing `to be 0, but found 1`. M2 (P3-T3): the restored context-reference guard throws on the worker (its ambient context is null and the captured context is not), so `captured.Should().BeNull(...)` fails with a message containing `InvalidOperationException` and `but found`. M3 (P3-T5): `action()` runs on the caller before the thread starts, so each of the four in-thread preconditions fails with a message containing `dedicated worker thread must not be` and the thread is never started. M4 (P3-T7): the mock returns a second stream, so `stream.Should().BeSameAs(sentinel)` fails with a message containing `to refer to`. Any other failing assertion, or a passing control, is `MUTATION PREDICTION MISMATCH`: stop and report the message text; do not adjust the test. +- **D-7 Coverage comparison (AC18).** The gated quantities are per-package: for the packages named `UtilitiesCS` and `QuickFiler` in the two JaCoCo projections, the final LINE rate and BRANCH rate, each computed as covered divided by covered plus missed, must be no lower than the baseline rate. The repository-wide first-party totals are recorded with a comparability note only, because the collector's cross-assembly merge is order-sensitive. If a package rate reads lower, the final coverage command is re-run once, identically, as a second measurement (recorded as such); if it still reads lower, `AC18: NOT MET` is recorded and the run stops with the two projections quoted. No test is retried by this rule; only the measurement is repeated. +- **D-8 Analyzer and nullable gates.** Each rebuild must exit 0 (AC17), print zero `Skipping target "CoreCompile"` lines (the spec's non-vacuity clause), echo at least one compiler line for each of QuickFiler.Test.dll and UtilitiesCS.Test.dll, and carry no warning or error line naming any Write Set `.cs` file. Warning counts are recorded against the Phase 0 values as an observation. +- **D-9 Follow-ups are handed off, not filed, by the executor.** The executor has no MCP surface; P4-T13 writes the four potential-entry bodies from the spec's Rollout list and the orchestrator files them. +- **D-10 No production change and no UNAFFECTED-site change.** P4-T11 requires the anchored name-listing diff of QuickFiler.Test to list exactly the four QuickFiler.Test Write Set paths and the anchored diff of QuickFiler/, UtilitiesCS/, the two runsettings files and config/ to be empty. +- **D-11 AC15 footprint.** `THIS-ITEM-FOOTPRINT:` is the anchored diff plus porcelain union minus `INHERITED-CLAUSE-A:` minus the Clause B prefix, with both subtractions listed in the artifact; it must equal the six code files plus paths under FEATURE/. +- **D-12 Known #780 flake is not retried.** `DictionaryExtensions_Tests.TryAddValuesAsync_UpdatesExistingValue` (fact 16) is admissible only in the Phase 0 baseline failed sets, as one member of `BASELINE-ADMISSIBLE` (D-17). Spec AC16 requires zero failed tests and admits no filter other than the shell-icon exclusion, AC17 requires the coverage route to pass, and the operator prohibits retries, so no task in this plan re-runs a command because that test failed: its failure in P4-T5 or P4-T6 is `AC16: NOT MET` and in P4-T7 is `AC17: NOT MET`, by design, and the run stops and reports the `MESSAGE` or `Failed tests:` line. The P4-T8 second measurement repeats a coverage measurement whose tests all passed; it is not a retry of a failed test. +- **D-13 Fail-before evidence.** No committed test can deterministically reproduce either defect without mutating process-global thread-pool state or spawning a handle-holding external process, both prohibited; P1-T1 writes the exception dossier and the four controls are the deterministic observed-failing evidence. +- **D-14 Rooted fixture literal.** The three metadata tests read the rooted literal C:\Repo\fixture.sln through the private constant `FixturePath`; the assertions mirror `file.Exists` and never read `Length`, so the outcome is identical whether or not the path exists on a machine. +- **D-15 Wrap-tolerant tokens.** CSharpier may break a `.Should()` chain across lines, so gates use `.BeNull(`, `.NotBe(`, `owner.CheckAccess()` and the reason-text literals rather than a whole chain; the reason literals are pinned to sit on one source line each. +- **D-16 Node reuse off.** Every msbuild invocation this plan issues or starts carries /nodeReuse:false, or for the restore script the environment variable MSBUILDDISABLENODEREUSE set to 1 (Execution Conventions), so that the node-reuse workers which produced the #906 failure in the #900 run (fact 17) do not exist when the parallel-suite runs execute. The switch is additive to the canonical commands and changes no diagnostic. +- **D-17 Baseline runs are recorded, not gated on exit 0.** P0-T10 and P0-T11 record failed-test sets so the final runs can report `NEWLY-FAILING:`. `BASELINE-ADMISSIBLE` is exactly: Properties_ShouldMirrorWrappedFileInfo, ExplicitDirectoryCast_ShouldReturnWrappedContainingDirectory, OpenRead_ShouldReturnReadableStreamForWrappedFile, ToString_ShouldDelegateToWrappedFileInfo, Dispatcher_OwnerOnlyWorker_ReportsWithoutRunningAction, TryAddValuesAsync_UpdatesExistingValue (the five rewritten tests whose pre-edit forms can fail because of the defect, plus the #780 flake; the sixth rewritten test, InitializeBreadcrumbPipeline_NullOwningDispatcher_DoesNotThrow, is not admissible, because its pre-edit form passes on any thread while its owner is null, so a baseline failure of it is not this defect). A baseline failed set that is a subset of `BASELINE-ADMISSIBLE` is recorded and the task completes (P0-T11 records `FAILED-SET:` and post-processes with the `RAW` rule of `CMD-COVERAGE-POST`); a set containing any other name is `BASELINE NOT GREEN` and stops the run, because AC16 and AC17 cannot then be met by a test-only change. + +## Command Reference + +Each block is a payload in the sense of the command-channel convention: one complete statement per line (a braced block on one line is one statement), no line continuations, no single-quote characters, no comment lines. Uppercase tokens `WORKTREE`, `PATH`, `TASKID`, `TESTPROJECT`, `PRODUCTION`, `GATEARGS`, `ASSEMBLY`, `FILTERARG`, `NAMES`, `STAGE`, `RAW` and `MERGE-BASE` (the P0-T3 value, used by `CMD-ADDED-LINES`) are substituted by the executor as each task states. Names for the `NAMES` token: `NAMES-AFFINITY` is the seven quoted method names of fact 1 (`"InitializeBreadcrumbPipeline_ConstructedInsideDispatcherOperation_SucceedsUnderDifferentAmbientContext", "InitializeBreadcrumbPipeline_OwningThreadNullAmbientContext_DoesNotThrow", "InitializeBreadcrumbPipeline_OwningThreadDifferentPlainContext_DoesNotThrow", "ConfigureBreadcrumbDropDown_OwningThreadInsideDispatcherOperation_DoesNotThrow", "InitializeBreadcrumbPipeline_WorkerThread_ThrowsBoundaryDiagnostic", "ConfigureBreadcrumbDropDown_WorkerThread_ThrowsBoundaryDiagnostic", "InitializeBreadcrumbPipeline_NullOwningDispatcher_DoesNotThrow"`); `NAMES-QF` is `NAMES-AFFINITY` plus `"Dispatcher_OwnerOnlyWorker_ReportsWithoutRunningAction"`; `NAMES-FOUR` is the last three names of `NAMES-AFFINITY` plus `"Dispatcher_OwnerOnlyWorker_ReportsWithoutRunningAction"`; `NAMES-FIW` is the eight quoted method names of fact 5 (`"Constructor_WhenFileInfoIsNull_ThrowsArgumentNullException", "Properties_ShouldMirrorWrappedFileInfo", "ExplicitDirectoryCast_ShouldReturnWrappedContainingDirectory", "OpenRead_ShouldReturnReadableStreamForWrappedFile", "ToString_ShouldDelegateToWrappedFileInfo", "PropertyDelegates_ShouldMirrorMockedIFileInfo", "StreamAndCopyMethods_ShouldDelegateToWrappedIFileInfo", "AccessControlAndLifecycleMethods_ShouldDelegateToWrappedIFileInfo"`); `NAMES-NONE` is the empty list `@()` written as an empty `NAMES` substitution. + +Filters (each is substituted for `FILTERARG` as one double-quoted argument beginning with the test-case-filter switch): `FILTER-FOUR` is `FullyQualifiedName~InitializeBreadcrumbPipeline_WorkerThread_ThrowsBoundaryDiagnostic|FullyQualifiedName~ConfigureBreadcrumbDropDown_WorkerThread_ThrowsBoundaryDiagnostic|FullyQualifiedName~InitializeBreadcrumbPipeline_NullOwningDispatcher_DoesNotThrow|FullyQualifiedName~Dispatcher_OwnerOnlyWorker_ReportsWithoutRunningAction` (expected total 4); `FILTER-DISPATCHER` is `FullyQualifiedName~Dispatcher_OwnerOnlyWorker_ReportsWithoutRunningAction` (total 1); `FILTER-NULLOWNER` is `FullyQualifiedName~InitializeBreadcrumbPipeline_NullOwningDispatcher_DoesNotThrow` (total 1); `FILTER-OPENREAD` is `FullyQualifiedName~OpenRead_ShouldReturnReadableStreamForWrappedFile` (total 1); `FILTER-AFFINITY-CLASS` is `FullyQualifiedName~ItemViewerBreadcrumbThreadAffinityTests` (total 7); `FILTER-FIW-CLASS` is `FullyQualifiedName~FileInfoWrapper_Tests` (total 8); `FILTER-STALL` is `FullyQualifiedName~HelperClasses.ShellUtilities_Tests|FullyQualifiedName~HelperClasses.ShellUtilitiesStatic_Tests|FullyQualifiedName~HelperClasses.SysImageListHelperTests|FullyQualifiedName~EmailIntelligence.OSBrowser_Tests`; `FILTER-UCS-EXCLUDE` is `FullyQualifiedName!~HelperClasses.ShellUtilities_Tests&FullyQualifiedName!~HelperClasses.ShellUtilitiesStatic_Tests&FullyQualifiedName!~HelperClasses.SysImageListHelperTests&FullyQualifiedName!~EmailIntelligence.OSBrowser_Tests`. `UCS-FILTERARG` (fixed by P0-T9) is empty under `STALL-PROBE: CLEAR` and is `FILTER-UCS-EXCLUDE` under `REPRODUCES`. A run whose `total` differs from the expected value is a failure of that task, not a pass: vstest.console.exe reports a zero-match filter without a non-zero exit. + +Assemblies: `ASSEMBLY-QF` is QuickFiler.Test\bin\Debug\QuickFiler.Test.dll; `ASSEMBLY-UCS` is UtilitiesCS.Test\bin\Debug\UtilitiesCS.Test.dll. + +**CMD-CENSUS** (token census over one file; `PATH` substituted; prints one `TOKEN = ` line per token, `LINES = ` and `SHA256 = `): + + Set-Location -LiteralPath "WORKTREE" + $path = "PATH" + $content = Get-Content -LiteralPath $path -Raw -Encoding UTF8 + $tokens = @("Task.Run(", ".GetAwaiter()", "DedicatedWorkerThread.Run(", "RunOnDedicatedWorkerThread", "ClearViewerDispatcher(", "partial class", "[TestMethod]", "action();", "new Thread(", "IsBackground = true", "thread.Join();", "Join(", "Join()", "using System.Reflection;", "using QuickFiler.Test.TestSupport;", "namespace QuickFiler.Test.TestSupport", "internal static class DedicatedWorkerThread", "internal static Exception Run(Action action)", ".Should()", "dedicated worker thread must not be", "distinct from every live thread by construction", "unconditionally", "owner.CheckAccess()", ".NotBe(", "ownerThreadId", ".BeNull(", "executions.Should().Be(0)", "cannot marshal", "GetSolutionFile", "TaskMaster.sln", "AppDomain", "File.Exists(", "File.Create", "File.WriteAll", "File.Delete", "Path.GetTemp", "FileMode.", "FileMode.Open", "FileAccess.", "FileAccess.Read", "FileShare.ReadWrite", "Assembly.Location", "FixturePath", "using var sentinel = new FileStream(", "BeSameAs(sentinel)", ".Returns(decoy)", "wrapper.OpenRead()", "stream.CanRead.Should().BeTrue()", "stream.Length.Should().BeGreaterThan(0)", "return true;", "_ownerThreadId.HasValue", "System.Threading.SynchronizationContext.Current", "Include=""Viewers\ItemViewerBreadcrumbThreadAffinityTests.cs""", "Include=""Viewers\ItemViewerBreadcrumbThreadAffinityTests.Part2.cs""", "Include=""TestSupport\DedicatedWorkerThread.cs""", "Thread.Sleep", "Task.Delay", "[Timeout", "DoNotParallelize", "Retry") + foreach ($t in $tokens) { Write-Output ("TOKEN " + $t + " = " + [regex]::Matches($content, [regex]::Escape($t)).Count) } + Write-Output ("LINES = " + @(Get-Content -LiteralPath $path).Count) + Write-Output ("SHA256 = " + (Get-FileHash -Algorithm SHA256 -LiteralPath $path).Hash) + +The three project-file tokens are referred to in prose as INCLUDE-AFFINITY, INCLUDE-PART2 and INCLUDE-HELPER (the doubled quotes are PowerShell's escape for a quote inside a double-quoted literal). + +**CMD-BUILD** (project build after a source edit; `TESTPROJECT` is `QuickFiler.Test` or `UtilitiesCS.Test`, `PRODUCTION` is `QuickFiler` or `UtilitiesCS`, `TASKID` is the lower-case task id): + + Set-Location -LiteralPath "WORKTREE" + $vswhere = Join-Path ${env:ProgramFiles(x86)} "Microsoft Visual Studio\Installer\vswhere.exe" + $msbuild = & $vswhere -latest -requires Microsoft.Component.MSBuild -find "MSBuild\**\Bin\MSBuild.exe" | Select-Object -First 1 + $dll = "TESTPROJECT\bin\Debug\TESTPROJECT.dll" + $before = if (Test-Path -LiteralPath $dll) { (Get-Item -LiteralPath $dll).LastWriteTimeUtc } else { [datetime]::MinValue } + New-Item -ItemType Directory -Path "coverage\logs" -Force | Out-Null + $global:LASTEXITCODE = 0 + & $msbuild "TESTPROJECT\TESTPROJECT.csproj" /t:Build /m /nodeReuse:false /p:Configuration=Debug /p:Platform=AnyCPU "/flp:LogFile=coverage\logs\TASKID.msbuild.log;Verbosity=normal" + Write-Output ("MSBUILD_EXIT_CODE: " + $LASTEXITCODE) + $log = Get-Content -LiteralPath "coverage\logs\TASKID.msbuild.log" -Raw -Encoding UTF8 + Write-Output ("CSC_OUT_LINES: " + [regex]::Matches($log, [regex]::Escape("/out:obj\Debug\TESTPROJECT.dll")).Count) + Write-Output ("PROD_CSC_OUT_LINES: " + [regex]::Matches($log, [regex]::Escape("/out:obj\Debug\PRODUCTION.dll")).Count) + Write-Output ("ZERO_ERRORS_LINES: " + [regex]::Matches($log, [regex]::Escape(" 0 Error(s)")).Count) + Write-Output ("DLL_ADVANCED: " + ((Get-Item -LiteralPath $dll).LastWriteTimeUtc -gt $before)) + +`ZERO_ERRORS_LINES` counts the literal with its leading space because `0 Error(s)` is a substring of `10 Error(s)`. `CMD-BUILD-QF` denotes this block with `QuickFiler.Test` and `QuickFiler`; `CMD-BUILD-UCS` denotes it with `UtilitiesCS.Test` and `UtilitiesCS`. + +**CMD-REBUILD** (solution rebuild gate; `GATEARGS` is either `/p:EnableNETAnalyzers=true /p:EnforceCodeStyleInBuild=true` (analyzer gate) or the single TreatWarningsAsErrors property switch (nullable gate); `TASKID` substituted): + + Set-Location -LiteralPath "WORKTREE" + $vswhere = Join-Path ${env:ProgramFiles(x86)} "Microsoft Visual Studio\Installer\vswhere.exe" + $msbuild = & $vswhere -latest -requires Microsoft.Component.MSBuild -find "MSBuild\**\Bin\MSBuild.exe" | Select-Object -First 1 + New-Item -ItemType Directory -Path "coverage\logs" -Force | Out-Null + $global:LASTEXITCODE = 0 + & $msbuild TaskMaster.sln /t:Rebuild /m /nodeReuse:false /p:Configuration=Debug "/p:Platform=Any CPU" GATEARGS "/flp:LogFile=coverage\logs\TASKID.msbuild.log;Verbosity=normal" + Write-Output ("MSBUILD_EXIT_CODE: " + $LASTEXITCODE) + $log = Get-Content -LiteralPath "coverage\logs\TASKID.msbuild.log" -Raw -Encoding UTF8 + Write-Output ("SKIP_CORECOMPILE_LINES: " + [regex]::Matches($log, [regex]::Escape("Skipping target ""CoreCompile""")).Count) + Write-Output ("QF_TEST_CSC_OUT_LINES: " + [regex]::Matches($log, [regex]::Escape("/out:obj\Debug\QuickFiler.Test.dll")).Count) + Write-Output ("UCS_TEST_CSC_OUT_LINES: " + [regex]::Matches($log, [regex]::Escape("/out:obj\Debug\UtilitiesCS.Test.dll")).Count) + Write-Output ("ZERO_ERRORS_LINES: " + [regex]::Matches($log, [regex]::Escape(" 0 Error(s)")).Count) + Write-Output ("WARNINGS: " + [regex]::Match($log, "(\d+) Warning\(s\)").Groups[1].Value) + Write-Output ("ERRORS: " + [regex]::Match($log, "(\d+) Error\(s\)").Groups[1].Value) + Write-Output ("WRITESET_DIAGNOSTIC_LINES: " + @(Get-Content -LiteralPath "coverage\logs\TASKID.msbuild.log" | Where-Object { ($_ -like "*ItemViewerBreadcrumbThreadAffinityTests*" -or $_ -like "*DedicatedWorkerThread.cs*" -or $_ -like "*BreadcrumbPopupBoundaryCoverageTests.cs*" -or $_ -like "*FileInfoWrapper_Tests.cs*") -and ($_ -like "*warning *" -or $_ -like "*error *") }).Count) + +**CMD-VSTEST** (one assembly under the root runsettings with the isolation switch; `ASSEMBLY`, `FILTERARG` (may be empty), `NAMES`, `TASKID` substituted; the results directory is private to the task): + + Set-Location -LiteralPath "WORKTREE" + $vswhere = Join-Path ${env:ProgramFiles(x86)} "Microsoft Visual Studio\Installer\vswhere.exe" + $vstest = & $vswhere -latest -products * -find "Common7\IDE\Extensions\TestPlatform\vstest.console.exe" | Select-Object -First 1 + $assembly = (Resolve-Path -LiteralPath "ASSEMBLY").Path + $settings = (Resolve-Path -LiteralPath "TaskMaster.runsettings").Path + $results = Join-Path (Get-Location).Path "coverage\test-results\931\TASKID" + if (Test-Path -LiteralPath $results) { Remove-Item -LiteralPath $results -Recurse -Force } + New-Item -ItemType Directory -Path "coverage\logs" -Force | Out-Null + Write-Output ("RUNSETTINGS-HASH-NOW: " + (Get-FileHash -Algorithm SHA256 -LiteralPath "TaskMaster.runsettings").Hash) + $names = @(NAMES) + $global:LASTEXITCODE = 0 + & $vstest $assembly "/Settings:$settings" /InIsolation FILTERARG "/ResultsDirectory:$results" "/Logger:trx;LogFileName=TASKID.trx" "/Blame:CollectHangDump;TestTimeout=4min;HangDumpType=None" 2>&1 | Tee-Object -FilePath "coverage\logs\TASKID.vstest.log" + Write-Output ("VSTEST_EXIT_CODE: " + $LASTEXITCODE) + $trxPath = Join-Path $results "TASKID.trx" + Write-Output ("TRX_PRESENT: " + (Test-Path -LiteralPath $trxPath)) + Write-Output ("SEQUENCE_FILES: " + @(Get-ChildItem -LiteralPath $results -Recurse -Filter "Sequence_*.xml" -ErrorAction SilentlyContinue).Count) + Write-Output ("COLLECTOR_LINES: " + @(Get-Content -LiteralPath "coverage\logs\TASKID.vstest.log" | Where-Object { $_ -like "*Code Coverage*" }).Count) + if (-not (Test-Path -LiteralPath $trxPath)) { Write-Output "TRX ABSENT: the run aborted before writing its result document"; exit 3 } + [xml]$trx = Get-Content -LiteralPath $trxPath -Raw -Encoding UTF8 + $ns = New-Object System.Xml.XmlNamespaceManager($trx.NameTable) + $ns.AddNamespace("t", "http://microsoft.com/schemas/VisualStudio/TeamTest/2010") + $counters = $trx.SelectSingleNode("//t:ResultSummary/t:Counters", $ns) + Write-Output ("COUNTERS total=" + $counters.GetAttribute("total") + " executed=" + $counters.GetAttribute("executed") + " passed=" + $counters.GetAttribute("passed") + " failed=" + $counters.GetAttribute("failed")) + $all = @($trx.SelectNodes("//t:UnitTestResult", $ns)) + Write-Output ("RESULT_COUNT: " + $all.Count) + foreach ($r in $all) { if ($names -contains $r.GetAttribute("testName")) { Write-Output ("RESULT " + $r.GetAttribute("testName") + " = " + $r.GetAttribute("outcome")) } } + foreach ($r in $all) { if ($r.GetAttribute("outcome") -eq "Failed") { Write-Output ("MESSAGE " + $r.GetAttribute("testName") + " :: " + $r.SelectSingleNode("t:Output/t:ErrorInfo/t:Message", $ns).InnerText) } } + +The failed-result filter is applied in PowerShell rather than in XPath so no payload needs an embedded quote. The `Command:` field records `vstest.console.exe "/Settings:TaskMaster.runsettings" /InIsolation "/ResultsDirectory:coverage\test-results\931\" "/Logger:trx;LogFileName=.trx" "/Blame:CollectHangDump;TestTimeout=4min;HangDumpType=None"` with the note `resolved through vswhere`. + +**CMD-COVERAGE-RUNNER** (CLAUDE.md step 4 route; `STAGE` is `baseline` or `final`; used when `COVERAGE-ROUTE: RUNNER`): + + Set-Location -LiteralPath "WORKTREE" + New-Item -ItemType Directory -Path "coverage\logs" -Force | Out-Null + foreach ($f in @("coverage\coverage.cobertura.xml", "coverage\coverage.cobertura.jacoco.xml", "coverage\test-results\mstest-coverage-run.trx", "coverage\test-results\mstest-coverage-run.summary.txt")) { if (Test-Path -LiteralPath $f) { Remove-Item -LiteralPath $f -Force } } + foreach ($f in @("coverage\STAGE-931.cobertura.xml", "coverage\STAGE-931.trx", "coverage\STAGE-931.jacoco.xml")) { if (Test-Path -LiteralPath $f) { Remove-Item -LiteralPath $f -Force } } + $script = Join-Path (Get-Location).Path "scripts\vscode\Invoke-MSTestWithCoverage.ps1" + $global:LASTEXITCODE = 0 + & pwsh -NoProfile -File $script 2>&1 | Tee-Object -FilePath "coverage\logs\STAGE-931.runner.log" + Write-Output ("RUNNER_EXIT_CODE: " + $LASTEXITCODE) + $log = Get-Content -LiteralPath "coverage\logs\STAGE-931.runner.log" -Raw -Encoding UTF8 + Write-Output ("DISCOVERED_LINE: " + [regex]::Match($log, "Discovered \d+ test assemblies\.").Value) + Write-Output ("FIRST_PARTY_LINE: " + [regex]::Match($log, "First-party coverage: [^\r\n]*").Value) + Write-Output ("THRESHOLD_MESSAGE: " + [regex]::Match($log, "Cobertura (line|branch) coverage [^\r\n]*threshold\.").Value) + Write-Output ("COLLECT_FAILURE_MESSAGE: " + [regex]::Match($log, "MSTest with coverage failed with exit code \d+").Value) + Write-Output ("DOCUMENT_PRESENT: " + (Test-Path -LiteralPath "coverage\coverage.cobertura.xml")) + Write-Output ("PROJECTION_PRESENT: " + (Test-Path -LiteralPath "coverage\coverage.cobertura.jacoco.xml")) + Write-Output ("SUMMARY_PRESENT: " + (Test-Path -LiteralPath "coverage\test-results\mstest-coverage-run.summary.txt")) + Write-Output ("TRX_PRESENT: " + (Test-Path -LiteralPath "coverage\test-results\mstest-coverage-run.trx")) + if (Test-Path -LiteralPath "coverage\coverage.cobertura.xml") { Copy-Item -LiteralPath "coverage\coverage.cobertura.xml" -Destination "coverage\STAGE-931.cobertura.xml" -Force } + if (Test-Path -LiteralPath "coverage\test-results\mstest-coverage-run.trx") { Copy-Item -LiteralPath "coverage\test-results\mstest-coverage-run.trx" -Destination "coverage\STAGE-931.trx" -Force } + if (Test-Path -LiteralPath "coverage\coverage.cobertura.jacoco.xml") { Write-Output ("RUNNER_PROJECTION_SHA256: " + (Get-FileHash -Algorithm SHA256 -LiteralPath "coverage\coverage.cobertura.jacoco.xml").Hash) } + +The runner's own log line naming the resolved vstest path and its `Coverage output:` line carry absolute paths and stay in the git-ignored log; only the named `_LINE`, `_MESSAGE` and `_PRESENT` values are transcribed. The `Command:` field records `pwsh -NoProfile -File scripts\vscode\Invoke-MSTestWithCoverage.ps1` (the payload resolves the absolute script path at run time through `Join-Path (Get-Location).Path`, per the command-channel convention). The stale-output removal (the runner's four fixed-name outputs: the Cobertura document, its JaCoCo projection, the TRX and the TRX summary, fact 12) makes each `_PRESENT` value an observation of this run; the baseline documents were already copied under their stage names. The second removal line clears this stage's own copies (`STAGE` is substituted, so the `final` run never touches a `baseline` copy) for the same reason: a stage copy left by an earlier session would otherwise satisfy the (d0) stage-TRX test of P0-T11 and P4-T7 and be read by `CMD-COVERAGE-POST`. + +**CMD-COVERAGE-DIRECT** (the runner's inner invocation issued directly with the four-class exclusion; used when `COVERAGE-ROUTE: DIRECT`; `STAGE` substituted): + + Set-Location -LiteralPath "WORKTREE" + . (Join-Path (Get-Location).Path "scripts\vscode\Invoke-MSTestWithCoverage.ps1") + $ErrorActionPreference = "Continue" + $repo = (Get-Location).Path + New-Item -ItemType Directory -Path "coverage\logs" -Force | Out-Null + foreach ($f in @("coverage\STAGE-931.cobertura.xml", "coverage\STAGE-931.trx", "coverage\STAGE-931.jacoco.xml")) { if (Test-Path -LiteralPath $f) { Remove-Item -LiteralPath $f -Force } } + $canonical = Get-Content -LiteralPath "coverage.config" -Raw -Encoding UTF8 + $derived = ConvertTo-DerivedCoverageSettingsXml -CanonicalSettingsXml $canonical + $effective = Join-Path $repo "coverage\effective-coverage-931.config" + Set-Content -LiteralPath $effective -Value $derived -Encoding UTF8 -NoNewline + $vswhere = Join-Path ${env:ProgramFiles(x86)} "Microsoft Visual Studio\Installer\vswhere.exe" + $vstest = & $vswhere -latest -products * -find "Common7\IDE\Extensions\TestPlatform\vstest.console.exe" | Select-Object -First 1 + $rootLen = $repo.TrimEnd([char]92).Length + $asm = @(Get-ChildItem -Path $repo -Recurse -Filter "*.Test.dll" | Where-Object { $_.FullName -like "*\bin\Debug\*" -and $_.FullName -notlike "*\obj\*" -and $_.FullName -notlike "*\ref\*" -and $_.FullName.Substring($rootLen) -notlike "\.claude\*" } | Select-Object -ExpandProperty FullName) + $filter = "TestCategory!=LiveOutlook&FullyQualifiedName!~HelperClasses.ShellUtilities_Tests&FullyQualifiedName!~HelperClasses.ShellUtilitiesStatic_Tests&FullyQualifiedName!~HelperClasses.SysImageListHelperTests&FullyQualifiedName!~EmailIntelligence.OSBrowser_Tests" + $output = Join-Path $repo "coverage\STAGE-931.cobertura.xml" + $settings = Join-Path $repo "scripts\vscode\TaskMaster.cli.runsettings" + $results = Join-Path $repo "coverage\test-results\931\STAGE" + if (Test-Path -LiteralPath $results) { Remove-Item -LiteralPath $results -Recurse -Force } + $global:LASTEXITCODE = 0 + & dotnet-coverage collect --output $output --output-format cobertura --settings $effective -- $vstest @asm "/Settings:$settings" /InIsolation "/TestCaseFilter:$filter" "/ResultsDirectory:$results" "/Logger:trx;LogFileName=STAGE-931.trx" "/Blame:CollectHangDump;TestTimeout=4min;HangDumpType=None" 2>&1 | Tee-Object -FilePath "coverage\logs\STAGE-931.collect.log" + Write-Output ("COLLECT_EXIT_CODE: " + $LASTEXITCODE) + Write-Output ("ASSEMBLY_COUNT: " + $asm.Count) + $asm | ForEach-Object { Write-Output ("ASSEMBLY: " + $_.Substring($rootLen)) } + Write-Output ("SEQUENCE_FILES: " + @(Get-ChildItem -LiteralPath $results -Recurse -Filter "Sequence_*.xml" -ErrorAction SilentlyContinue).Count) + if (Test-Path -LiteralPath (Join-Path $results "STAGE-931.trx")) { Copy-Item -LiteralPath (Join-Path $results "STAGE-931.trx") -Destination "coverage\STAGE-931.trx" -Force } + Write-Output ("TRX_PRESENT: " + (Test-Path -LiteralPath "coverage\STAGE-931.trx")) + +The stage-copy removal at the top of the block makes `TRX_PRESENT:` an observation of this run, and the results-directory removal does the same for `SEQUENCE_FILES:`. The `ASSEMBLY:` lines print the path relative to the worktree root (the leading backslash is the first character). The discovery filter uses `-like` wildcards and `[char]92` rather than a regex ending in a backslash, so no payload line ends a literal with a backslash before its closing quote. The `Command:` field records `dotnet-coverage collect --output coverage\-931.cobertura.xml --output-format cobertura --settings coverage\effective-coverage-931.config -- vstest.console.exe "/Settings:scripts\vscode\TaskMaster.cli.runsettings" /InIsolation "/TestCaseFilter:" "/ResultsDirectory:coverage\test-results\931\" "/Logger:trx;LogFileName=-931.trx" "/Blame:CollectHangDump;TestTimeout=4min;HangDumpType=None"`. + +**CMD-COVERAGE-POST** (post-process, project and summarise one stage's documents with the runner's own functions; `STAGE` substituted). `RAW` is `True` under the DIRECT route, and under the RUNNER route whenever the run's `COLLECT_FAILURE_MESSAGE:` is non-empty (the runner throws at its lines 261 to 263 before post-processing, so the document a failed run leaves behind is raw collector output); `RAW` is `False` only for a RUNNER run whose log carries no collector-failure message, because the runner post-processed that document in place before asserting the floors. The TRX summary and `FAILED-SET:` are printed before the floor checks and before the projection, so a `throw` from `Assert-JacocoProjectionReconciliation` (the one call in this payload that is not wrapped) cannot suppress them: + + Set-Location -LiteralPath "WORKTREE" + . (Join-Path (Get-Location).Path "scripts\vscode\Invoke-MSTestWithCoverage.Helpers.ps1") + . (Join-Path (Get-Location).Path "scripts\vscode\Invoke-MSTest.TrxSummary.ps1") + $ErrorActionPreference = "Continue" + $repo = (Get-Location).Path + $summary = Get-TrxRunSummary -TrxContent (Get-Content -LiteralPath "coverage\STAGE-931.trx" -Raw -Encoding UTF8) + Write-Output "SUMMARY-BEGIN" + Write-Output (Format-TrxRunSummary -Summary $summary) + Write-Output "SUMMARY-END" + Write-Output ("FAILED-SET: " + (@($summary.FailedTestName) -join ", ")) + $doc = Get-Content -LiteralPath "coverage\STAGE-931.cobertura.xml" -Raw -Encoding UTF8 + if ("RAW" -eq "True") { $doc = ConvertTo-KoverageCoberturaXml -XmlContent $doc -RepoRoot $repo; Set-Content -LiteralPath "coverage\STAGE-931.cobertura.xml" -Value $doc -Encoding UTF8 -NoNewline } + try { Assert-CoberturaLineCoverageThreshold -CoberturaXml $doc; Write-Output "LINE-FLOOR: MET" } catch { Write-Output ("LINE-FLOOR: NOT MET " + $_.Exception.Message) } + try { Assert-CoberturaBranchCoverageThreshold -CoberturaXml $doc; Write-Output "BRANCH-FLOOR: MET" } catch { Write-Output ("BRANCH-FLOOR: NOT MET " + $_.Exception.Message) } + Write-Output (Get-CoberturaFirstPartyCoverageReport -CoberturaXml $doc) + [xml]$xml = $doc + $projection = ConvertTo-JacocoPackageProjection -XmlDocument $xml + Assert-JacocoProjectionReconciliation -XmlDocument $xml -ProjectionXml $projection + Set-Content -LiteralPath "coverage\STAGE-931.jacoco.xml" -Value $projection -Encoding UTF8 + Write-Output "PROJECTION-BEGIN" + Write-Output $projection + Write-Output "PROJECTION-END" + +The order of the printed blocks is therefore summary, `FAILED-SET:`, floors, first-party line, projection. The projection printed between `PROJECTION-BEGIN` and `PROJECTION-END` and the five summary lines between `SUMMARY-BEGIN` and `SUMMARY-END` are the two committed forms; they carry package names, counters and test names only. + +**CMD-PACKAGE-COMPARE** (per-package comparison of the two JaCoCo projections for AC18): + + Set-Location -LiteralPath "WORKTREE" + [xml]$b = Get-Content -LiteralPath "coverage\baseline-931.jacoco.xml" -Raw -Encoding UTF8 + [xml]$f = Get-Content -LiteralPath "coverage\final-931.jacoco.xml" -Raw -Encoding UTF8 + $q = [char]39 + foreach ($name in @("UtilitiesCS", "QuickFiler")) { foreach ($type in @("LINE", "BRANCH")) { $xp = "/report/package[@name=" + $q + $name + $q + "]/counter[@type=" + $q + $type + $q + "]"; $bc = $b.SelectSingleNode($xp); $fc = $f.SelectSingleNode($xp); if ($null -eq $bc -or $null -eq $fc) { Write-Output ("PACKAGE " + $name + " " + $type + " MISSING baseline=" + ($null -ne $bc) + " final=" + ($null -ne $fc)); continue }; $bCov = [int]$bc.GetAttribute("covered"); $bVal = $bCov + [int]$bc.GetAttribute("missed"); $fCov = [int]$fc.GetAttribute("covered"); $fVal = $fCov + [int]$fc.GetAttribute("missed"); $bRate = if ($bVal -gt 0) { [math]::Round($bCov / $bVal, 6) } else { 0 }; $fRate = if ($fVal -gt 0) { [math]::Round($fCov / $fVal, 6) } else { 0 }; Write-Output ("PACKAGE " + $name + " " + $type + " baseline=" + $bCov + "/" + $bVal + " rate=" + $bRate + " final=" + $fCov + "/" + $fVal + " rate=" + $fRate + " NOT-LOWER=" + ($fRate -ge $bRate)) } } + +**CMD-ADDED-LINES** (the seven `ADDED-` counts of P4-T11 over the added lines of the anchored `-U0` diff of the two test projects; `MERGE-BASE` substituted with the P0-T3 value; each count is the number of added lines, lines beginning with a single `+`, whose text contains the pattern under the ordinal, case-sensitive `String.Contains` comparison): + + Set-Location -LiteralPath "WORKTREE" + $added = @(git diff -U0 MERGE-BASE -- QuickFiler.Test UtilitiesCS.Test | Where-Object { $_.StartsWith("+") -and -not $_.StartsWith("+++") }) + Write-Output ("ADDED-LINE-COUNT: " + $added.Count) + Write-Output ("ADDED-DONOTPARALLELIZE: " + @($added | Where-Object { $_.Contains("DoNotParallelize") }).Count) + Write-Output ("ADDED-THREAD-SLEEP: " + @($added | Where-Object { $_.Contains("Thread.Sleep") }).Count) + Write-Output ("ADDED-TASK-DELAY: " + @($added | Where-Object { $_.Contains("Task.Delay") }).Count) + Write-Output ("ADDED-TIMEOUT: " + @($added | Where-Object { $_.Contains("[Timeout") -or $_.Contains("Timeout=") }).Count) + Write-Output ("ADDED-RETRY: " + @($added | Where-Object { $_.Contains("Retry") }).Count) + Write-Output ("ADDED-WORKERS: " + @($added | Where-Object { $_.Contains("") -or $_.Contains("Workers =") }).Count) + Write-Output ("ADDED-SCOPE: " + @($added | Where-Object { $_.Contains("") -or $_.Contains("ExecutionScope") }).Count) + +`ADDED-LINE-COUNT:` is the positive control: it is at least 1 by construction (the fix adds lines), so seven zeros over an empty diff cannot pass. The two element-tag arguments are the opening tags a runsettings edit would carry. Re-derived against the pre-edit primary affinity file: the two remarks moved verbatim into the Part2 file (pre-edit lines 210 and 266) carry the text Workers=0 inside a code element, which contains neither element-tag argument nor `Workers =`; the only occurrences of the text Scope in that file are the `ViewerScope` identifier (lines 92, 132, 171, 222, 272, 322, 440, 467, 471), which contains neither the Scope element tag nor `ExecutionScope`; no line of that file contains `Retry` or `Timeout`; and none of the Target Source A, B, D, E or F content contains any of the seven patterns. The block, not the prose, is the definition of each count. + +**CMD-SWEEP** (host-identifier and raw-document sweep over the feature folder; the tokens are derived at run time and never written into an artifact): + + Set-Location -LiteralPath "WORKTREE" + $folder = "docs\features\active\2026-09-28-tests-depend-on-uncontrolled-environment-931" + $all = @(Get-ChildItem -LiteralPath $folder -Recurse -File) + $evidence = @(Get-ChildItem -LiteralPath (Join-Path $folder "evidence") -Recurse -File) + @(Get-Item -LiteralPath (Join-Path $folder "plan.2026-09-28T20-01.md")) + $account = [regex]::Escape($env:USERNAME) + $profileLeaf = [regex]::Escape((Split-Path -Leaf $env:USERPROFILE)) + $machine = [regex]::Escape($env:COMPUTERNAME) + $root = [regex]::Escape((Get-Location).Path) + Write-Output ("FILES: " + $all.Count) + Write-Output ("ACCOUNT-TOKEN-MATCHES: " + @($evidence | Select-String -Pattern ("(?i)\b" + $account + "\b")).Count) + Write-Output ("PROFILE-LEAF-MATCHES: " + @($evidence | Select-String -Pattern ("(?i)\b" + $profileLeaf + "\b")).Count) + Write-Output ("MACHINE-TOKEN-MATCHES: " + @($evidence | Select-String -Pattern ("(?i)\b" + $machine + "\b")).Count) + Write-Output ("WORKTREE-ROOT-MATCHES: " + @($all | Select-String -Pattern ("(?i)" + $root)).Count) + Write-Output ("USERS-PATH-MATCHES: " + @($all | Select-String -Pattern "(?i)[a-z]:[\\/]users[\\/]").Count) + Write-Output ("RAW-DOCUMENT-FILES: " + @($all | Where-Object { $_.Extension -in @(".trx", ".xml", ".coverage") }).Count) + +The account, profile-leaf and machine sweeps run over the evidence tree and this plan (the committed evidence AC19 governs); the two path sweeps and the raw-document sweep run over the whole feature folder, because a drive-rooted user-profile path or a raw document is prohibited wherever it appears. The spec's `Owner:` field is a GitHub handle authored by the maintainer and is outside the evidence tree. + +## Token Census Expectations + +File aliases: `AFF` is the primary affinity file, `PART2` its continuation, `HELPER` the new helper, `BND` the boundary-coverage file, `FIW` the FileInfoWrapper test file, `CSPROJ` the QuickFiler.Test project file, `DISP` QuickFiler/Viewers/BreadcrumbUiDispatcher.cs, `IVB` QuickFiler/Viewers/ItemViewer.Breadcrumb.cs. `pre` is the pre-edit value P0-T12 records; `post` is the value after Phase 2 (P2-T7) that holds through the end of the run except where a control column says otherwise. Tokens not listed for a file are recorded and not gated. + +| File | Token | pre | post | under control | +| --- | --- | --- | --- | --- | +| AFF | `LINES` | 490 | at most 500 (expected 294: 490 minus 197 deleted lines 199 to 347 and 357 to 404, minus the `using System.Reflection;` line, plus two remark lines; the line 30 replacement is net zero) | unchanged | +| AFF | `Task.Run(` | 1 | 0 | | +| AFF | `.GetAwaiter()` | 1 | 0 | | +| AFF | `RunOnDedicatedWorkerThread` | 5 | 0 | | +| AFF | `ClearViewerDispatcher(` | 2 | 0 | | +| AFF | `partial class` | 0 | 1 | | +| AFF | `[TestMethod]` | 7 | 4 | | +| AFF | `action();`, `new Thread(`, `IsBackground = true`, `thread.Join();` | 1 each | 0 each | | +| AFF | `using System.Reflection;` | 1 | 0 | | +| AFF | `dedicated worker thread must not be` | 2 | 0 | | +| AFF | `distinct from every live thread by construction` | 1 | 0 | | +| AFF | `DedicatedWorkerThread.Run(`, `using QuickFiler.Test.TestSupport;`, `Thread.Sleep`, `Task.Delay`, `[Timeout`, `DoNotParallelize` | 0 each | 0 each | | +| PART2 | `LINES` | (absent) | at most 500 (expected about 202 before formatting: 22 header lines, the 53- and 52-line moved tests, the 55-line null-owner test, the 15-line `ClearViewerDispatcher`, three separating blank lines and two closing braces) | | +| PART2 | `Task.Run(`, `.GetAwaiter()`, `RunOnDedicatedWorkerThread`, `action();`, `Thread.Sleep`, `Task.Delay`, `[Timeout`, `DoNotParallelize`, `Retry` | (absent) | 0 each | | +| PART2 | `DedicatedWorkerThread.Run(` | (absent) | 3 | | +| PART2 | `ClearViewerDispatcher(` | (absent) | 2 | | +| PART2 | `partial class`, `using QuickFiler.Test.TestSupport;`, `using System.Reflection;`, `owner.CheckAccess()`, `.BeNull(`, `unconditionally` | (absent) | 1 each | | +| PART2 | `[TestMethod]` | (absent) | 3 | | +| PART2 | `dedicated worker thread must not be` | (absent) | 3 | | +| HELPER | `LINES` | (absent) | at most 500 (expected about 50) | M3: one more | +| HELPER | `namespace QuickFiler.Test.TestSupport`, `internal static class DedicatedWorkerThread`, `internal static Exception Run(Action action)`, `new Thread(`, `IsBackground = true`, `thread.Join();`, `distinct from every live thread by construction` | (absent) | 1 each | | +| HELPER | `action();` | (absent) | 1 | M3 (P3-T5): 2; after P3-T6: 1 | +| HELPER | `Join(` equals `Join()` | (absent) | equal | | +| HELPER | `.Should()`, `Task.Run(`, `Thread.Sleep`, `Task.Delay`, `[Timeout`, `DoNotParallelize`, `Retry` | (absent) | 0 each | | +| BND | `LINES` | 361 | at most 500 (expected about 386: 361 minus the 11 replaced lines plus the 35-line replacement plus one using line) | | +| BND | `Task.Run(` | 1 | 0 | | +| BND | `.GetAwaiter()` | 4 | 3 | | +| BND | `DedicatedWorkerThread.Run(`, `using QuickFiler.Test.TestSupport;`, `.NotBe(`, `.BeNull(`, `dedicated worker thread must not be` | 0 each | 1 each | | +| BND | `ownerThreadId` | 0 | 2 | | +| BND | `executions.Should().Be(0)`, `cannot marshal`, `partial class` | 1 each | 1 each | | +| BND | `Thread.Sleep`, `Task.Delay`, `[Timeout`, `DoNotParallelize` | 0 each | 0 each | | +| FIW | `LINES` | 359 | at most 500 (expected about 357: 359 minus the 20 deleted lines 338 to 357, minus the 57 replaced lines 25 to 81, plus the 70-line replacement, plus the 4-line constant block and its blank line) | M4: six more (the second six-line `using var` declaration) | +| FIW | `GetSolutionFile` | 5 | 0 | | +| FIW | `TaskMaster.sln`, `AppDomain`, `File.Exists(` | 1 each | 0 each | | +| FIW | `File.Create`, `File.WriteAll`, `File.Delete`, `Path.GetTemp`, `Thread.Sleep`, `Task.Delay`, `[Timeout`, `DoNotParallelize` | 0 each | 0 each | | +| FIW | `FileMode.` equals `FileMode.Open` | 12 = 12 | 13 = 13 | | +| FIW | `FileAccess.` equals `FileAccess.Read` | 10 = 10 | 11 = 11 | | +| FIW | `FileShare.ReadWrite` | 8 | 9 | M4: 10 | +| FIW | `Assembly.Location` | 6 | 7 | M4 (P3-T7): 8; after P3-T8: 7 | +| FIW | `FixturePath` | 0 | 4 | | +| FIW | `using var sentinel = new FileStream(`, `BeSameAs(sentinel)` | 0 each | 1 each | | +| FIW | `.Returns(decoy)` | 0 | 0 | M4: 1; after P3-T8: 0 | +| FIW | `wrapper.OpenRead()` | 2 | 2 | | +| FIW | `stream.CanRead.Should().BeTrue()`, `stream.Length.Should().BeGreaterThan(0)` | 1 each | 1 each | | +| FIW | `[TestMethod]` | 8 | 8 | | +| CSPROJ | INCLUDE-AFFINITY | 1 | 1 | | +| CSPROJ | INCLUDE-PART2, INCLUDE-HELPER | 0 each | 1 each | | +| DISP | `return true;` | recorded as N1 | N1 | M1 (P3-T1): N1 plus 1; after P3-T2: N1 | +| DISP | `_ownerThreadId.HasValue` | recorded as N2 (at least 1) | N2 | M1: N2 minus 1; after P3-T2: N2 | +| IVB | `System.Threading.SynchronizationContext.Current` | 0 | 0 | M2 (P3-T3): 1; after P3-T4: 0 | + +The remarks the executor writes must not contain the literals `Task.Run(`, `.GetAwaiter()` or `RunOnDedicatedWorkerThread`; they refer to those members as `Task.Run`, `GetResult()` and `DedicatedWorkerThread.Run`. The BND remark must not contain the phrase `cannot marshal`. No remark or comment contains the identifier `FixturePath`, the word `unconditionally` outside the one pinned sentence in PART2, or the phrase `dedicated worker thread must not be` outside the pinned reason literals. + +## Target Source + +Each block is shown with its outermost lines at column 0; the executor writes members at the file's member indentation and P2-T7's format pass normalizes whitespace. Whitespace is not a counted token. + +### A. New file QuickFiler.Test/TestSupport/DedicatedWorkerThread.cs (entire content) + + using System; + using System.Threading; + + namespace QuickFiler.Test.TestSupport + { + ///

+ /// Runs a delegate on a dedicated, joined background thread for tests that must exercise a + /// thread-identity guard from a thread that is provably not the calling thread. + /// + /// + /// Issue #900 and issue #931: a Task.Run work item is not guaranteed to run on a + /// thread other than the caller's, so it cannot stand in for a different thread in a + /// thread-identity test. A thread this method constructs is + /// distinct from every live thread by construction. The untimed Join() is a + /// completion wait on one bounded synchronous call, not a sleep or a wall-clock wait, and + /// the waiting thread and the waited-for thread are never both thread-pool workers, so + /// the wait cannot starve the pool under parallel execution. The helper asserts nothing + /// itself: each test states its own distinctness precondition inside its delegate so that + /// a failure names the guard under test rather than the helper. + /// + internal static class DedicatedWorkerThread + { + /// + /// Runs on a dedicated background thread, joins it, and + /// returns the exception it threw, or when it completed + /// normally. + /// + internal static Exception Run(Action action) + { + Exception captured = null; + var thread = new Thread(() => + { + try + { + action(); + } + catch (Exception error) + { + captured = error; + } + }); + thread.IsBackground = true; + thread.Start(); + thread.Join(); + return captured; + } + } + } + +The remark line `/// distinct from every live thread by construction. The untimed Join() is a` is written exactly as shown so the census token sits on one line (D-15); the file carries no nullable directive, matching the file the helper came from. + +### B. New file QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.Part2.cs (entire content) + + using System; + using System.Drawing; + using System.Reflection; + using System.Windows.Threading; + using FluentAssertions; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using Moq; + using QuickFiler.Test.TestSupport; + using QuickFiler.Viewers; + using UtilitiesCS.OutlookObjects.Folder; + + namespace QuickFiler.Test.Viewers + { + /// + /// Continuation partial of holding the + /// three cross-thread cases, each of which runs its guarded call on a dedicated thread + /// created by . The owner-thread admission cases, the + /// shared InertOperations factory and the nested helper types live in the primary + /// partial so that each file stays under the 500-line limit (issue #931). + /// + public sealed partial class ItemViewerBreadcrumbThreadAffinityTests + { + ` (pre-edit + line 228) becomes `DedicatedWorkerThread.Run(() =>`> + + + + /// + /// A viewer with no owning dispatcher stays inert, which is what keeps + /// FormatterServices.GetUninitializedObject-built viewers in other test files from + /// throwing. This is the only test covering the null-owner escape. + /// + /// + /// Issue #931: the guarded call is made from a dedicated thread created by + /// DedicatedWorkerThread.Run, and the delegate asserts through the owner captured + /// before the dispatcher is cleared that it is not on the owner thread. The call is + /// therefore off the owner thread unconditionally, so the test discriminates against the + /// pre-#781 context-reference guard: that guard would read the non-null captured context, + /// find the worker's null ambient context different from it, and reject the call, whereas + /// the null-owner escape admits it. Seeding first and repeating the same provider are + /// still required: a first-time initialization under a null ambient context would throw + /// at BreadcrumbUiDispatcher.CaptureCurrent() regardless of the guard, and only the + /// already-initialized early return can witness the escape. + /// + [TestMethod] + public void InitializeBreadcrumbPipeline_NullOwningDispatcher_DoesNotThrow() + { + // Arrange + using (var scope = new ViewerScope()) + { + BreadcrumbPopupUiOperations operations = InertOperations(); + var provider = new Mock(MockBehavior.Strict); + scope.Viewer.InitializeBreadcrumbPipeline(provider.Object, operations); + object before = scope.Viewer.BreadcrumbCoordinator; + Dispatcher owner = scope.Viewer.UiDispatcher; + owner.Should().NotBeNull("the viewer must own a dispatcher before it is cleared"); + ClearViewerDispatcher(scope.Viewer); + + // Act + Exception captured = DedicatedWorkerThread.Run(() => + { + bool isOwnerThread = owner.CheckAccess(); + isOwnerThread + .Should() + .BeFalse( + "the dedicated worker thread must not be the owner thread, or the " + + "null-owner escape would be witnessed on the owner thread and " + + "the test would pass vacuously" + ); + scope.Viewer.InitializeBreadcrumbPipeline(provider.Object, operations); + }); + + // Assert + captured + .Should() + .BeNull( + "a viewer with no owning dispatcher has no boundary to enforce and must " + + "stay inert" + ); + scope.Viewer.BreadcrumbCoordinator.Should().BeSameAs(before); + } + } + + + } + } + +The word `unconditionally` appears exactly once, in the remark above. The reason literal segment `"the dedicated worker thread must not be the owner thread, or the "` sits on one line. The two moved remarks carry the pre-edit lines 210 and 266, each containing the text Workers=0 inside a code element; they are moved verbatim, and the P4-T11 `ADDED-WORKERS:` count is defined so that this text does not match it. + +### C. Edits to QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.cs (pre-edit numbering; apply bottom-up) + +1. Delete lines 357 through 404 (`ClearViewerDispatcher`, the blank line, `RunOnDedicatedWorkerThread` with its remark, and the following blank line), so that the blank line 356 is followed by the `InertDropDownHost` summary that was at 405. +2. Delete lines 199 through 347 (the three cross-thread tests and the blank line after the third), so that the blank line 198 is followed by the `InertOperations` summary that was at 348. +3. Replace line 30 ` public sealed class ItemViewerBreadcrumbThreadAffinityTests` with ` public sealed partial class ItemViewerBreadcrumbThreadAffinityTests`. +4. Insert one remark line after line 27 (`/// needs no message pump.`) reading `/// The three cross-thread cases and ClearViewerDispatcher live in the continuation` and, on the following line, `/// partial file (issue #931).` (two lines, matching the existing three-slash remark indentation). +5. Delete line 4 `using System.Reflection;` (the only member that used it moved to the Part2 file). + +No other line changes. The resulting file holds the four owner-thread admission tests, `InertOperations`, `InertDropDownHost`, `DrainableSynchronizationContext` and `ViewerScope`. + +### D. Edits to QuickFiler.Test/Viewers/BreadcrumbPopupBoundaryCoverageTests.cs (pre-edit numbering) + +Replace lines 52 through 62 with: + + /// + /// An owner-only dispatcher (null context) reached from a thread that is not its owner + /// must report a marshalling failure and must not run the action. + /// + /// + /// Issue #931: the worker is a dedicated thread created by + /// DedicatedWorkerThread.Run, never a Task.Run work item. A blocking wait on + /// a pool work item queued from a pool thread can run the delegate inline on the owner + /// thread, in which case the owner-thread-id branch of IsCurrentBoundary() admits + /// the call, the action runs, and the test fails spuriously. The delegate asserts it is + /// off the owner thread before it dispatches. The rejection path reports and returns a + /// completed task synchronously, so no task wait is needed. + /// + [TestMethod] + public void Dispatcher_OwnerOnlyWorker_ReportsWithoutRunningAction() + { + var errors = new List(); + int ownerThreadId = Environment.CurrentManagedThreadId; + BreadcrumbUiDispatcher dispatcher = CreateOwnerOnlyDispatcher(errors.Add); + int executions = 0; + Exception captured = DedicatedWorkerThread.Run(() => + { + Environment + .CurrentManagedThreadId.Should() + .NotBe( + ownerThreadId, + "the dedicated worker thread must not be the owner thread the dispatcher " + + "was built for, or the rejection path would never be reached" + ); + dispatcher.Dispatch(() => executions++); + }); + captured.Should().BeNull(); + executions.Should().Be(0); + errors.Should().ContainSingle().Which.Message.Should().Contain("cannot marshal"); + } + +Then insert `using QuickFiler.Test.TestSupport;` as a new line between line 11 (`using Moq;`) and line 12 (`using QuickFiler.Viewers;`). The last two statements of the test are the two pre-existing assertions, unchanged. `System.Threading.Tasks` stays imported because other tests in the file use `Task`. + +### E. Edits to UtilitiesCS.Test/HelperClasses/FileInfoWrapper_Tests.cs (pre-edit numbering; apply bottom-up) + +1. Delete lines 338 through 357 (the blank line and `GetSolutionFile`), so that line 337 (` }`) is followed by the class-closing brace that was at 358. +2. Replace lines 25 through 81 (the four tests) with: + + [TestMethod] + public void Properties_ShouldMirrorWrappedFileInfo() + { + // Arrange + var file = new FileInfo(FixturePath); + var wrapper = new FileInfoWrapper(file); + + // Assert + wrapper.Exists.Should().Be(file.Exists); + wrapper.FullName.Should().Be(file.FullName); + wrapper.Name.Should().Be(file.Name); + wrapper.Extension.Should().Be(".sln"); + wrapper.DirectoryName.Should().Be(file.DirectoryName); + wrapper.Directory.FullName.Should().Be(file.Directory.FullName); + } + + [TestMethod] + public void ExplicitDirectoryCast_ShouldReturnWrappedContainingDirectory() + { + // Arrange + var wrapper = new FileInfoWrapper(new FileInfo(FixturePath)); + + // Act + var directoryWrapper = (DirectoryInfoWrapper)wrapper; + + // Assert + directoryWrapper.FullName.Should().Be(wrapper.Directory.FullName); + directoryWrapper.Name.Should().Be(wrapper.Directory.Name); + } + + [TestMethod] + public void OpenRead_ShouldReturnReadableStreamForWrappedFile() + { + // Arrange: the sentinel is a stream this test opens and owns over the running test + // host's own loaded assembly image, read-only with read-write sharing, so no other + // process's handle can deny the open and no repository or temporary file is involved + // (issue #931). The seam's OpenRead member returns the concrete FileStream type, so a + // MemoryStream cannot stand in for it through the seam. + using var sentinel = new FileStream( + typeof(FileInfoWrapper_Tests).Assembly.Location, + FileMode.Open, + FileAccess.Read, + FileShare.ReadWrite + ); + var fileInfo = new Mock(MockBehavior.Strict); + fileInfo.Setup(x => x.OpenRead()).Returns(sentinel); + var wrapper = new FileInfoWrapper(fileInfo.Object); + + // Act + FileStream stream = wrapper.OpenRead(); + + // Assert + stream.Should().BeSameAs(sentinel); + stream.CanRead.Should().BeTrue(); + stream.Length.Should().BeGreaterThan(0); + } + + [TestMethod] + public void ToString_ShouldDelegateToWrappedFileInfo() + { + // Arrange + var file = new FileInfo(FixturePath); + var wrapper = new FileInfoWrapper(file); + + // Act + var result = wrapper.ToString(); + + // Assert + result.Should().Be(file.ToString()); + } + +3. Insert after line 14 (the class opening brace) the constant and its comment, followed by one blank line: + + // Issue #931: a rooted literal that is not expected to exist and does not point into the + // repository. The three metadata tests assert only path computations and Exists, so no + // file handle is opened and the outcome does not depend on any other process. + private const string FixturePath = @"C:\Repo\fixture.sln"; + +The comment names the constant nowhere, so the `FixturePath` census is exactly the declaration plus three uses. The Arrange comment of the OpenRead test contains none of `FileShare.ReadWrite`, `OpenRead()`, `Assembly.Location`, `FileMode.`, `FileAccess.` or `.Length`, so the FIW census after this edit is `FileShare.ReadWrite` 9 (eight pre-existing plus the sentinel argument), `Assembly.Location` 7, `FileMode.` 13 and `FileAccess.` 11, and the `ADDED-OPENREAD:` count of P2-T6 is exactly the two code lines that call the member. No other line changes. + +### F. Project-file edits to QuickFiler.Test/QuickFiler.Test.csproj + +Insert ` ` immediately after the entry for the primary affinity file (line 98), and ` ` immediately after the entry for TestSupport\WinFormsPumpHost.cs (line 227 pre-edit, 228 after the first insertion). Both use the project-relative backslash form of the neighbouring entries. + +### G. Negative-control mutations (each applied by the Edit tool and reverted by `git checkout -- `) + +- M1 (P3-T1), QuickFiler/Viewers/BreadcrumbUiDispatcher.cs: replace lines 276 to 277 (`return _ownerThreadId.HasValue` and `&& Environment.CurrentManagedThreadId == _ownerThreadId.Value;`) with the single line `return true;`. +- M2 (P3-T3), QuickFiler/Viewers/ItemViewer.Breadcrumb.cs: replace line 437 (`return;`) with the four lines `if (!ReferenceEquals(System.Threading.SynchronizationContext.Current, UiSyncContext))`, `{`, `throw new InvalidOperationException("mutation 931: pre-781 context-reference guard");`, `}` followed by `return;` on a fifth line, all inside the `if (owning == null)` block at 435 to 438. +- M3 (P3-T5), QuickFiler.Test/TestSupport/DedicatedWorkerThread.cs: insert `action();` as the first statement of `Run`, immediately before `Exception captured = null;`. +- M4 (P3-T7), UtilitiesCS.Test/HelperClasses/FileInfoWrapper_Tests.cs: in `OpenRead_ShouldReturnReadableStreamForWrappedFile`, insert a second `using var decoy = new FileStream(` declaration with the same four arguments immediately after the `sentinel` declaration, and change `.Returns(sentinel)` to `.Returns(decoy)`. + +## Planner Self-Review Record (initial authoring, 2026-09-28) + +This section persists the planner's adversarial self-review so a reader can locate it without the conversation transcript. Every citation below was re-derived against the assigned worktree during this authoring pass; none is carried forward from the research record without a fresh read. Sibling regions re-checked: the whole of each of the three edited test files (all 490, 361 and 359 lines were read), lines 60 to 285 of BreadcrumbUiDispatcher.cs, lines 1 to 30 and 420 to 459 of ItemViewer.Breadcrumb.cs, lines 1 to 40 and 150 to 217 of FileInfoWrapper.cs, the whole runner script and its Projection, FirstParty, Threshold and TrxSummary part files. + +Findings that changed the plan relative to the spec's text: (1) the runner script now writes the JaCoCo package projection and the TRX summary itself and asserts both floors before doing so (fact 12), so the coverage tasks name those outputs and carry a threshold branch; (2) the runner's hard-coded filter (fact 12, line 91) and the documented local stall (fact 16) make the coverage route a recorded selection (P0-T9), with the direct route built from the runner's own functions; (3) the #906 failure was produced in the #900 run by the plan's own node-reuse workers (fact 17), so every msbuild invocation here turns node reuse off (D-16); (4) ItemViewer.Breadcrumb.cs does not import `System.Threading` (fact 9), so mutation M2 uses the fully qualified name; (5) the FluentAssertions subject name in a failure message depends on caller identification, so every predicted message is gated on its outcome phrase, not on a subject name (fact 21). No acceptance-criterion text was amended. + +Revision round 1 (2026-09-28), applied after the first executor preflight returned seventeen deltas. Every citation the round's edits touched was re-derived against the assigned worktree in the same pass, together with its sibling region: `QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.cs` lines 199 to 304 re-read (the removed helper name occurs once in each moved remark, at 204 and 259, and once in each call, at 228 and 277, so Target Source B names one remark substitution per test, not two; the moved remarks carry the text Workers=0 inside a code element at 210 and 266, which fixed the P4-T11 `ADDED-WORKERS:` definition); `UtilitiesCS.Test/HelperClasses/FileInfoWrapper_Tests.cs` lines 55 to 84 re-read (line 66 is `stream.Length.Should().BeGreaterThan(0);`, byte-identical to the rewritten assertion, so `ADDED-LENGTH:` is defined alignment-independently and expected 0; line 62 `using var stream = wrapper.OpenRead();` differs from the rewritten `FileStream stream = wrapper.OpenRead();`, so `ADDED-OPENREAD:` is deterministically 2 once the Arrange comment names no member); the spec's AC16 line 251 re-read (zero failed tests, the shell-icon exclusion the only permitted filter, no admission of the #780 flake, which fixed D-12 as no re-run and `AC16: NOT MET` / `AC17: NOT MET` by design); the promoted record docs/features/potential/promoted/2026-09-28-tests-depend-on-uncontrolled-environment.md confirmed present in the worktree; the Token Census Expectations line expectations re-derived arithmetically (AFF 294, PART2 about 202, BND about 386, FIW about 357, M4 six more lines); and the whole plan swept once more for backticked forward-slash repository paths outside the Write Set, the feature folder and the two feature documents (four spans converted: the runner `Command:` form, the P0-T5 `Command:` form, the three P3-T2 pathspecs and the P4-T11 CLI runsettings pathspec; `origin/main` at two sites is a ref, not a path, and is unchanged). + +Revision round 2 (2026-09-28), applied after the second executor preflight confirmed fifteen of the seventeen round-1 fixes and returned five deltas (R2-D1 through R2-D5). Every citation the round's edits touched was re-derived against the assigned worktree in the same pass, together with its sibling region: `QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.cs` lines 199 to 404 re-read (the pre-edit null-owner test at 306 to 346 runs its guarded call through `Task.Run` against a viewer whose owner is null, and the null-owner escape returns on any thread, so its pre-edit form cannot fail from the defect, which fixed the D-17 admissibility note; the pre-edit helper remark at 377 to 384 matches Target Source A's remark text apart from the widened issue reference and the re-wrap, which fixed D-1; the only occurrences of the text Scope in the file are the `ViewerScope` identifier at 92, 132, 171, 222, 272, 322, 440, 467 and 471, the only occurrences of Workers are the code-element text at 210 and 266, and no line contains Retry or Timeout, which fixed the `ADDED-SCOPE:` definition and the new `CMD-ADDED-LINES` block); scripts/vscode/Invoke-MSTestWithCoverage.ps1 lines 275 to 304 and 375 to 439 re-read (the fixed output names `coverage\coverage.cobertura.xml`, `coverage\test-results` and `mstest-coverage-run.trx` at 282 to 284, the projection path beside the Cobertura at 393 to 395, the summary at 408 to 425; the runner removes no stale output itself, so the stale-output removal added to `CMD-COVERAGE-RUNNER` is the only thing that makes each `_PRESENT` value an observation of this run); the ordering of the baseline stage copies (`CMD-COVERAGE-RUNNER` lines that copy to `coverage\baseline-931.cobertura.xml` and `coverage\baseline-931.trx` in P0-T11, and `CMD-COVERAGE-POST` writing `coverage\baseline-931.jacoco.xml`) against the P4-T7 removal, which names the runner's fixed-name outputs and this stage's own `final-931` copies only, so no baseline document is removed by the final run; and a sibling finding the deltas did not name: a stage copy left by an earlier session would pre-satisfy the new (d0) stage-TRX test under both routes, so both coverage payloads now clear this stage's own copies before the run and the DIRECT payload prints `TRX_PRESENT:` after its copy, which makes `TRX_PRESENT:` a both-routes observation (recorded as such in the P0-T11 Output Summary list in place of the delta's RUNNER-only annotation). The P4-T7 acceptance count after the two prepended items is eight (the delta's figure); the floor-line acceptance item is now route-independent because `CMD-COVERAGE-POST` prints the floor lines on every run. The plan was grepped for the DIRECT-only floor wording and the six-count wording after the edits: none remains. + +Revision round 3 (2026-09-28), applied after the third executor preflight confirmed the round-2 fixes and returned three deltas (R3-D1: line endings against the revert hash anchors; R3-D2: the runner's summary file in the stale-output removal; R3-D3: the (c) wording of P0-T11). Every citation the round's edits touched was re-derived against the assigned worktree in the same pass, together with its sibling region: .gitattributes read in full (`* text=auto` at line 4; the only other attribute line is the issue #400 whitespace exception at line 9; every other non-blank line is a comment); the .editorconfig section headers enumerated (`[*.cs]` at 1, `[*.vb]` at 597, `[*.{cs,vb}]` at 619) and lines 655 to 677 read (`end_of_line = crlf` at 669 lies in the `[*.{cs,vb}]` section and so governs every `.cs` file); the worktree globbed for a .csharpierrc file (none); scripts/vscode/Invoke-MSTestWithCoverage.ps1 lines 405 to 426 re-read (the summary path at 418 to 420 is the results directory joined with the TRX base name plus `.summary.txt`, so the fixed name is coverage\test-results\mstest-coverage-run.summary.txt and the R3-D2 array entry is correct); every plan site that compares a hash against a `FIX-HASH-` anchor enumerated by grep (Execution Conventions Refs and Negative-control mechanics, D-5, P2-T10, P3-T6, P3-T8, P3-T9, P4-T9) and re-read, so each anchor is now defined as the post-restore value; `SUMMARY_PRESENT:` occurs at one site (`CMD-COVERAGE-RUNNER`) and its removal entry now precedes the run; P0-T11 (c) was the only site carrying the replaced `RAW` sentence (P4-T7 (c) already read `if it has not already run`). One finding the deltas did not name: because `end_of_line = crlf` governs `.cs` files and CSharpier documents reading that key from .editorconfig, P2-T7's scoped format may already have converted the two new files to CRLF, in which case `EOL-RESTORED:` reads `NONE` and the R3-D1 restore is a no-op; the delta is conditional on the working-tree column, the w/ column, of `git ls-files --eol`, so it is applied unchanged and the record decides which case occurred (fact 23). The value `core.autocrlf=true` is the round-3 preflight's statement; it was re-read from the worktree in round 4 (system configuration file, value `true`), and the `EOL-BEFORE:` and `EOL-AFTER:` spans of P2-T10 are the in-plan observations that replace the assumption. The CSharpier .editorconfig behaviour is documented behaviour and was not observed in this pass; the plan does not depend on it, because P4-T1's `REWRITTEN:` count and P4-T9's `POST-FORMAT-HASH-:` branch record whatever the pinned formatter does. Acceptance counts after the round: P2-T10 four (unchanged), P0-T11 five (unchanged), P4-T7 eight (unchanged), P3-T6 five, P3-T8 five, P3-T9 three, P4-T9 three, P4-T1 three (all unchanged); the task count remains 66. + +Revision round 4 (2026-09-28), applied after the fourth executor preflight confirmed the round-3 fixes and returned three deltas (R4-D1: the P4-T9 hash comparison keyed to a single `REWRITTEN:` value although the loop may iterate; R4-D2: artifact naming and selection for tasks re-run inside a loop; R4-D3: the provenance of the `core.autocrlf` value). Every plan site the round's edits touched was re-read in the same pass together with its siblings: the P4-T9 third acceptance item now compares each `.cs` hash with the after-format hash of the final P4-T1 iteration, and equality with the `FIX-HASH-` anchor is asserted only when every iteration recorded `REWRITTEN: 0`; P4-T1 was re-read as the sibling that supplies that value: its payload captures the SHA-256 of each of the five Write Set `.cs` files immediately before and immediately after the command and its `Output Summary:` records the ten hashes, so the per-file after-format hash was already recorded and the one permitted sibling addition labels the ten values per file and as before or after, which changes no count (P4-T1 acceptance stays three); P4-T10 was re-read as the other consumer named by R4-D2 and already records `ITERATIONS:` as the number of times the loop started from P4-T1, which is the quantity the new P4-T9 clause conditions on; the Artifact filenames convention now defines the per-iteration `ITERATION:` line and the highest-value selection rule, and P2-T9's repair sentence (`re-run P2-T7 through P2-T9, and record each iteration`) and the Phase 4 preamble (`the loop restarts from P4-T1 and P4-T10 records the iteration count`) were re-read as the two loops the convention names; P2-T10's clause `has a hash equal to its P2-T7 post-format hash` was re-read as the comparison the convention resolves to the highest-`ITERATION:` P2-T7 artifact. Fact 23 now attributes the `core.autocrlf` value to the round-4 preflight's observation (`git config --show-origin --get-all core.autocrlf` against this worktree, reporting `true` from the Git for Windows system configuration file, and `git ls-files --eol` reporting i/lf and w/crlf for the four existing Write Set files and the two production files the controls mutate), and the round-3 record's sentence claiming the value could not be re-read from inside the worktree is corrected: a system-level or global git configuration value is readable from any worktree, and the round-3 claim confused the common git directory (which holds the repository-level configuration file) with the configuration scopes git reads. The planner's file-only tool surface in this round could not itself issue the git command, so the fact records the preflight's observation as such; the plan continues to gate on the in-plan `EOL-BEFORE:` and `EOL-AFTER:` spans of P2-T10 rather than on the setting. Acceptance counts after the round: P4-T9 three, P4-T1 three, P4-T10 four, P2-T10 four (all unchanged); the task count remains 66. + +SELF-REVIEW: RE-DERIVED THIS PASS +- `QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.cs` | lines 1 to 490 read in full: `[TestMethod]` at 38, 88, 128, 167, 218, 268, 318; `Task.Run(` only at 332; `RunOnDedicatedWorkerThread` at 204 (remark), 228 (call), 259 (remark), 277 (call), 385; `Workers=0` inside a code element at 210 and 266; `ClearViewerDispatcher(` at 328 and 361; `action();` at 392; reason literal on one line at 234 and 283; `distinct from every live thread by construction` on one line at 380; blank lines at 198, 347, 356, 372, 404; no `partial`, no nullable directive. +- `QuickFiler.Test/Viewers/BreadcrumbPopupBoundaryCoverageTests.cs` | lines 1 to 140 and 340 to 361 read; `Task.Run(` at 58 only; `.GetAwaiter()` at 59, 297, 305, 308 (grep over the file); `partial class` at 17; `using Moq;` at 11; `CreateOwnerOnlyDispatcher` 124 to 134; no `.NotBe(`, `.BeNull(`, `ownerThreadId`, `Thread.Sleep`, `Task.Delay`, `[Timeout` (grep); total 361 lines. +- `UtilitiesCS.Test/HelperClasses/FileInfoWrapper_Tests.cs` | lines 1 to 359 read in full; `[TestMethod]` at 15, 25, 41, 55, 69, 83, 183, 297; `GetSolutionFile` at 29, 45, 59, 73, 339; `FileMode.` on 12 lines and `FileAccess.` on 10 lines (grep enumerated); `Assembly.Location` at 190, 196, 202, 208, 214, 220; `wrapper.OpenRead()` at 62 (`using var stream = wrapper.OpenRead();`) and 287; `stream.Length.Should().BeGreaterThan(0);` at 66; blank line 338; class close at 358. +- docs/features/potential/promoted/2026-09-28-tests-depend-on-uncontrolled-environment.md | present in the assigned worktree (Glob); the caller states it was committed on this branch deliberately before execution, so P4-T11 records it under `INHERITED-PROMOTION-RECORD:` and Clause A subtracts it. +- `QuickFiler.Test/QuickFiler.Test.csproj` | Compile Include for the affinity file at 98, boundary pair at 105 to 106, TestSupport pair at 227 to 228; Platform default at 12; OutputPath at 36; ProjectReference to QuickFiler at 510. +- UtilitiesCS.Test/UtilitiesCS.Test.csproj | FileInfoWrapper_Tests.cs at 234; Platform default at 11; OutputPath at 51; ProjectReference to UtilitiesCS at 963. +- QuickFiler/QuickFiler.csproj and UtilitiesCS/UtilitiesCS.csproj | OutputType Library and AssemblyName at 9 and 12, 12 and 15; no TreatWarningsAsErrors property in QuickFiler.csproj. +- QuickFiler/Viewers/BreadcrumbUiDispatcher.cs | `Dispatch` 71 to 151, `IsCurrentBoundary()` call at 78, null-context rejection 97 to 105, `IsCurrentBoundary` 255 to 278, `return true;` at 260, context branch 269 to 272, owner-id branch 276 to 277. +- QuickFiler/Viewers/ItemViewer.Breadcrumb.cs | usings 1 to 9 (no `System.Threading`); `ThrowIfOffUiBoundary` 432 to 447; null-owner escape 435 to 438; throw 440 to 446. +- QuickFiler/Viewers/ItemViewer.cs | `_uiDispatcher = Dispatcher.CurrentDispatcher;` at 27; `UiSyncContext` at 59; `UiDispatcher` at 65 to 67 (grep). +- UtilitiesCS/HelperClasses/FileSystem/FileInfoWrapper.cs | public constructor 14 to 19; seam 21 to 24; `OpenRead()` 153 to 156; `ToString()` 191 to 194; explicit cast 211 to 214. +- UtilitiesCS/HelperClasses/FileSystem/PhysicalFileInfoAdapter.cs | `Directory` 104, `DirectoryName` 108, `OpenRead()` 154, `ToString()` 181. +- UtilitiesCS/Interfaces/IHelperClasses/IFileInfo.cs | `FileStream OpenRead();` at 26. +- UtilitiesCS/Properties/AssemblyInfo.cs | `InternalsVisibleTo("UtilitiesCS.Test")` at 19. +- QuickFiler.Test/TestSupport/WinFormsPumpHost.cs | `namespace QuickFiler.Test.TestSupport` at 9. +- QuickFiler.Test/Viewers/BreadcrumbPopupBoundaryCoverageTests.Part2.cs | continuation partial declaration without `[TestClass]` at 23. +- TaskMaster.runsettings | 30 lines; Workers 0 at 5; Scope ClassLevel at 6; Code Coverage collector 9 to 29. scripts/vscode/TaskMaster.cli.runsettings | 9 lines; same Workers and Scope; no collector. +- scripts/vscode/Invoke-MSTestWithCoverage.ps1 | parameters 1 to 13; filter at 91; results directory and logger at 92 to 93; `ConvertTo-DerivedCoverageSettingsXml` 97 to 134; collector throw 261 to 263; strict mode and stop preference 271 to 272; fixed results names 283 to 284; repo root 305; runsettings 312; discovery 330 to 337; post-process 382 to 384; floors 386 to 387; first-party line 388; projection 393 to 401; summary 408 to 425; retention 427 to 431; entry guard 437. +- scripts/vscode/Invoke-MSTestWithCoverage.Helpers.ps1 | part-file dot-sources 2 to 6; `ConvertTo-KoverageCoberturaXml` at 407. scripts/vscode/Invoke-MSTestWithCoverage.Projection.ps1 | `ConvertTo-JacocoPackageProjection` 14 to 81; `Assert-JacocoProjectionReconciliation` 83 to 146; `Test-RawCoverageDocumentRetained` 148 to 197. scripts/vscode/Invoke-MSTestWithCoverage.FirstParty.ps1 | `Format-CoberturaFirstPartyCoverageSummary` 95 to 121 (line shape at 117 to 120); `Get-CoberturaFirstPartyCoverageReport` 123 to 162. scripts/vscode/Invoke-MSTestWithCoverage.Threshold.ps1 | line floor message at 54; branch floor message at 124. scripts/vscode/Invoke-MSTest.TrxSummary.ps1 | `Get-TrxRunSummary` 12 to 101; `Format-TrxRunSummary` 103 to 150. +- scripts/vscode/Invoke-Restore.ps1 | parameters 1 to 10; vswhere shape at 31. scripts/vscode/Install-RepoDotNetSdk.ps1 | install directory at 36. global.json | SDK 8.0.205 and `.dotnet-sdk` path. +- .csharpierignore | 18 lines as listed in fact 15. .gitignore | lines 39, 140 to 141, 144 to 145, 190. +- .claude/hooks/validate-planner-output.ps1 | path regex 95; phase regex 238; task regex 239; sequential check 299 to 302; Phase 0 checks 325 to 330; final-phase check 339. +- .claude/hooks/enforce-orchestration-preimplementation-gate.ps1 | checkpoint path 31; readiness properties 235 to 243; deny reason 429. +- docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/spec.md | header line 9 (full-bug, sole AC source); Write Set 222 to 232; AC lines 236 to 254, all unchecked; Rollout follow-ups 274 to 278. +- docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/issue.md | line 12 `- Work Mode: full-bug`. +- docs/features/active/breadcrumb-thread-affinity-tests-assume-taskrun-distinct-thread-900/evidence/qa-gates/p5-t5-mstest-coverage.2026-09-17T02-35.md | loop-context section recording the iteration-1 `OpenRead_ShouldReturnReadableStreamForWrappedFile` failure caused by a node-reuse worker. +- This plan | every task line carries a path token; phase headings use the em dash; task ids are sequential per phase (13, 1, 10, 9, 33); the final phase title and tasks carry the QA vocabulary the hook requires. +- Round 2: `QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.cs` | lines 199 to 404 re-read: null-owner test 306 to 346 (`Task.Run(` 332, `.GetAwaiter()` 335, `ClearViewerDispatcher(` 328); helper remark 377 to 384 with the census literal on line 380; `ViewerScope` at 92, 132, 171, 222, 272, 322, 440, 467, 471; `Workers=0` inside a code element at 210 and 266; no `Retry`, no `Timeout`, no `ExecutionScope`, no Scope or Workers element tag. +- Round 2: scripts/vscode/Invoke-MSTestWithCoverage.ps1 | lines 275 to 304 and 375 to 439 re-read: defaults `coverage\coverage.cobertura.xml`, `coverage\test-results`, `mstest-coverage-run.trx` at 282 to 284; helper dot-sources 289 and 295; post-process 381 to 384; floors 386 to 387; first-party line 388; projection path 393 to 395 and write 396 to 400; summary 408 to 425; retention 427 to 431; entry guard 437 to 439; no stale-output removal anywhere in the script. +- Round 2: this plan | P0-T11 and P4-T7 were the only two tasks carrying the DIRECT-only floor wording (grep before the edit: four hits at P0-T11, two at P4-T7; none after); P4-T11 and P4-T29 were the only two sites carrying the six-count wording (none after); the P4-T7 acceptance list holds eight items after the edit; `CMD-COVERAGE-RUNNER` copies the stage documents at its two `Copy-Item` lines before any later removal can run, and the removal lists name no `baseline` document when `STAGE` is `final`. +- Round 3: .gitattributes | 69 lines read in full; `* text=auto` at 4; `whitespace=-blank-at-eof` for the issue #400 record at 9; every other non-blank line is a `#` comment. +- Round 3: .editorconfig | section headers `[*.cs]` 1, `[*.vb]` 597, `[*.{cs,vb}]` 619 (grep); lines 655 to 677 read: `end_of_line = crlf` at 669 inside the `[*.{cs,vb}]` section; no .csharpierrc file anywhere in the worktree (Glob). +- Round 3: scripts/vscode/Invoke-MSTestWithCoverage.ps1 | lines 405 to 426 re-read: `Get-TrxRunSummary` call 409 to 412; summary path 418 to 420 (results directory joined with the TRX base name plus `.summary.txt`); `Set-Content` 421 to 424; the `Test-result summary:` line 425. +- Round 3: this plan | grep for `FIX-HASH`: Execution Conventions Refs, Negative-control mechanics, D-5, P2-T10, P3-T6, P3-T8, P3-T9, P4-T9, each now naming the post-restore anchor; `SUMMARY_PRESENT` once, in `CMD-COVERAGE-RUNNER`, whose removal array now carries the summary path; the replaced P0-T11 (c) sentence occurred once; P2-T10 acceptance still reads `all four required`; P0-T11 still reads `all five required`. +- Round 4: this plan, P4-T1 | task re-read in full: the payload hashes each of the five Write Set `.cs` files before and after `dotnet tool run csharpier format .`; the `Output Summary:` lists the ten hashes (now labelled per file and as before or after), `REWRITTEN:`, `FORMAT_CHANGED_TREE:` and the porcelain output; acceptance count three; the restart sentence and the `FORMAT NOT IDEMPOTENT` stop are unchanged. +- Round 4: this plan, P4-T9 | third acceptance item replaced with the R4-D1 text; the item count remains three; `POST-FORMAT-HASH-:` occurs at this one acceptance site, in the round-3 record and in this entry. +- Round 4: this plan, P4-T10 | `ITERATIONS:` is defined as the number of times the loop started from P4-T1 and the task is re-run after a restart; acceptance count four; unchanged. +- Round 4: this plan, Execution Conventions, Artifact filenames | R4-D2 sentences appended; the `-..md` span contains no slash and is not a path token; `ITERATION:` occurs in this bullet and in the round-4 review text only, as a convention that governs every loop re-run; no task field list was edited to repeat it. +- Round 4: this plan, P2-T9 and P2-T10 | P2-T9 closes with `re-run P2-T7 through P2-T9, and record each iteration`; P2-T10 compares unrestored `.cs` hashes with `its P2-T7 post-format hash`; both sentences unchanged and now resolved by the convention to the highest-`ITERATION:` artifact. +- Round 4: this plan, fact 23 and the round-3 record | the parenthetical beginning `(stated by the round-3 preflight;` occurred once and was replaced; the sentence `it cannot be re-read from inside the worktree` occurred once and was replaced; no other plan sentence claims the setting is unreadable from the worktree (grep for `common git directory` after the edits: the round-4 record's corrective sentence and this entry only; the fact-23 and round-3 sites no longer carry the phrase). +- Round 4: .claude/agent-memory/atomic-planner/project_931_uncontrolled_environment_tests_plan_seams.md | entry 13(c) corrected to record that `git config --show-origin --get-all core.autocrlf` is readable from inside a worktree. + +PLANNER-INTERNAL-REVIEW: PASS +CITATION-TO-TREE: PASS +AC-TRACEABILITY: PASS +SCOPE-BOUNDARY: PASS +CITATION: QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.cs | 490 lines; tests 199-251, 253-304, 306-346; `Task.Run(` 332; `ClearViewerDispatcher` 357-371; `RunOnDedicatedWorkerThread` 373-403, remark occurrences 204 and 259, calls 228 and 277; `Workers=0` remarks 210 and 266 +CITATION: QuickFiler.Test/Viewers/BreadcrumbPopupBoundaryCoverageTests.cs | 361 lines; test 52-62; `Task.Run(` 58; `CreateOwnerOnlyDispatcher` 124-134; usings 1-12 +CITATION: UtilitiesCS.Test/HelperClasses/FileInfoWrapper_Tests.cs | 359 lines; tests 25-81; `wrapper.OpenRead()` 62; `stream.Length.Should().BeGreaterThan(0);` 66; `GetSolutionFile` 339-357; sentinel pattern 189-224 +CITATION: docs/features/potential/promoted/2026-09-28-tests-depend-on-uncontrolled-environment.md | present; pre-committed promoted record recorded by P4-T11 +CITATION: QuickFiler.Test/QuickFiler.Test.csproj | Compile Include 98, 105-106, 227-228; Platform 12; ProjectReference 510 +CITATION: UtilitiesCS.Test/UtilitiesCS.Test.csproj | Compile Include 234; Platform 11; ProjectReference 963 +CITATION: QuickFiler/Viewers/BreadcrumbUiDispatcher.cs | `Dispatch` 71-151; rejection 97-105; `IsCurrentBoundary` 255-278; owner-id branch 276-277 +CITATION: QuickFiler/Viewers/ItemViewer.Breadcrumb.cs | usings 1-9; `ThrowIfOffUiBoundary` 432-447; escape 435-438 +CITATION: QuickFiler/Viewers/ItemViewer.cs | `_uiDispatcher` 27; `UiSyncContext` 59; `UiDispatcher` 65-67 +CITATION: UtilitiesCS/HelperClasses/FileSystem/FileInfoWrapper.cs | constructors 14-24; `OpenRead` 153-156; `ToString` 191-194; cast 211-214 +CITATION: UtilitiesCS/HelperClasses/FileSystem/PhysicalFileInfoAdapter.cs | `Directory` 104; `OpenRead` 154; `ToString` 181 +CITATION: UtilitiesCS/Interfaces/IHelperClasses/IFileInfo.cs | `FileStream OpenRead();` 26 +CITATION: UtilitiesCS/Properties/AssemblyInfo.cs | InternalsVisibleTo 18-19 +CITATION: QuickFiler.Test/TestSupport/WinFormsPumpHost.cs | namespace 9 +CITATION: QuickFiler.Test/Viewers/BreadcrumbPopupBoundaryCoverageTests.Part2.cs | partial declaration 23 +CITATION: TaskMaster.runsettings | Workers 5; Scope 6; collector 9-29 +CITATION: scripts/vscode/TaskMaster.cli.runsettings | 9 lines +CITATION: scripts/vscode/Invoke-MSTestWithCoverage.ps1 | filter 91; derived settings 97-134; throw 261-263; fixed output names 282-284; discovery 330-337; post-process 381-384; floors 386-387; projection 393-401; summary 408-425; retention 427-431; guard 437 +CITATION: scripts/vscode/Invoke-MSTestWithCoverage.Helpers.ps1 | dot-sources 2-6; `ConvertTo-KoverageCoberturaXml` 407 +CITATION: scripts/vscode/Invoke-MSTestWithCoverage.Projection.ps1 | 14-81; 83-146; 148-197 +CITATION: scripts/vscode/Invoke-MSTestWithCoverage.FirstParty.ps1 | 95-121; 123-162 +CITATION: scripts/vscode/Invoke-MSTestWithCoverage.Threshold.ps1 | messages 54 and 124 +CITATION: scripts/vscode/Invoke-MSTest.TrxSummary.ps1 | 12-101; 103-150 +CITATION: scripts/vscode/Invoke-Restore.ps1 | parameters 1-10; vswhere 31 +CITATION: scripts/vscode/Install-RepoDotNetSdk.ps1 | install directory 36 +CITATION: .csharpierignore | 18 lines +CITATION: .gitignore | 39, 140-141, 144-145, 190 +CITATION: .gitattributes | `* text=auto` 4 +CITATION: .editorconfig | `[*.{cs,vb}]` 619; `end_of_line = crlf` 669 +CITATION: .claude/hooks/validate-planner-output.ps1 | 95, 238-239, 299-302, 325-330, 339 +CITATION: .claude/hooks/enforce-orchestration-preimplementation-gate.ps1 | 31, 235-243, 429 +CITATION: docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/spec.md | header 9; Write Set 222-232; AC 236-254; Rollout 274-278 +CITATION: docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/issue.md | line 12 +CITATION: docs/features/active/breadcrumb-thread-affinity-tests-assume-taskrun-distinct-thread-900/evidence/qa-gates/p5-t5-mstest-coverage.2026-09-17T02-35.md | loop-context section +AC-INVENTORY: AC1, AC2, AC3, AC4, AC5, AC6, AC7, AC8, AC9, AC10, AC11, AC12, AC13, AC14, AC15, AC16, AC17, AC18, AC19 +AC-MAPPING: AC1 | IMPLEMENTATION: P2-T5 (Target Source D) | TESTS: P3-T1 control and P4-T5 full run | EVIDENCE: p4-t9 census of BND, mutation-owner-only-dispatcher-guard.md, parallel-suite-quickfiler-test.md; check-off P4-T14 +AC-MAPPING: AC2 | IMPLEMENTATION: P2-T3 (Target Source B null-owner test) | TESTS: P3-T3 control and P4-T5 | EVIDENCE: p4-t9 census of PART2, mutation-null-owner-escape.md, parallel-suite-quickfiler-test.md; check-off P4-T15 +AC-MAPPING: AC3 | IMPLEMENTATION: D-10 (no edit outside the four QuickFiler.Test Write Set paths) | TESTS: P4-T11 anchored name-listing diff of QuickFiler.Test | EVIDENCE: p4-t11-scope-boundary; check-off P4-T16 +AC-MAPPING: AC4 | IMPLEMENTATION: P2-T3 and P2-T4 (split into partials) | TESTS: P4-T5 seven affinity results | EVIDENCE: p4-t9 census of AFF and PART2, parallel-suite-quickfiler-test.md; check-off P4-T17 +AC-MAPPING: AC5 | IMPLEMENTATION: P2-T1 through P2-T7 | TESTS: P4-T9 post-format line counts | EVIDENCE: p4-t9-post-format-census; check-off P4-T18 +AC-MAPPING: AC6 | IMPLEMENTATION: P2-T2 (Target Source F) | TESTS: P4-T5 four named results | EVIDENCE: p4-t9 census of CSPROJ, parallel-suite-quickfiler-test.md; check-off P4-T19 +AC-MAPPING: AC7 | IMPLEMENTATION: P2-T1 (Target Source A) | TESTS: P3-T5 control | EVIDENCE: p4-t9 census of HELPER, mutation-inline-precondition.md; check-off P4-T20 +AC-MAPPING: AC8 | IMPLEMENTATION: P2-T6 (Target Source E) | TESTS: P4-T6 full run | EVIDENCE: p4-t9 census of FIW, parallel-suite-utilitiescs-test.md; check-off P4-T21 +AC-MAPPING: AC9 | IMPLEMENTATION: P2-T6 (OpenRead test) | TESTS: P3-T7 control and P4-T6 | EVIDENCE: p4-t9 census of FIW, mutation-openread-sentinel.md; check-off P4-T22 +AC-MAPPING: AC10 | IMPLEMENTATION: P2-T6 (three metadata tests) | TESTS: P4-T6 | EVIDENCE: p2-t6 added-line counts, parallel-suite-utilitiescs-test.md; check-off P4-T23 +AC-MAPPING: AC11 | IMPLEMENTATION: P3-T1 and P3-T2 | TESTS: FILTER-DISPATCHER runs | EVIDENCE: mutation-owner-only-dispatcher-guard.md; check-off P4-T24 +AC-MAPPING: AC12 | IMPLEMENTATION: P3-T3 and P3-T4 | TESTS: FILTER-NULLOWNER runs | EVIDENCE: mutation-null-owner-escape.md; check-off P4-T25 +AC-MAPPING: AC13 | IMPLEMENTATION: P3-T5 and P3-T6 | TESTS: FILTER-FOUR runs | EVIDENCE: mutation-inline-precondition.md; check-off P4-T26 +AC-MAPPING: AC14 | IMPLEMENTATION: P3-T7 and P3-T8 | TESTS: FILTER-OPENREAD runs | EVIDENCE: mutation-openread-sentinel.md; check-off P4-T27 +AC-MAPPING: AC15 | IMPLEMENTATION: P3-T9, D-11 and the Commits convention (no .claude or config path is ever staged) | TESTS: P4-T11 footprint, COMMITTED-CLAUDE-OR-CONFIG and INHERITED-PROMOTION-RECORD | EVIDENCE: p3-t9-post-control-clean-tree, p4-t11-scope-boundary; check-off P4-T28 +AC-MAPPING: AC16 | IMPLEMENTATION: P4-T5 and P4-T6 under TaskMaster.runsettings | TESTS: full QuickFiler.Test and UtilitiesCS.Test runs | EVIDENCE: parallel-suite-quickfiler-test.md, parallel-suite-utilitiescs-test.md, p4-t11 added-line counts; check-off P4-T29 +AC-MAPPING: AC17 | IMPLEMENTATION: P4-T1 through P4-T7 loop | TESTS: P4-T7 coverage route | EVIDENCE: toolchain-pass.md; check-off P4-T30 +AC-MAPPING: AC18 | IMPLEMENTATION: P0-T11, P4-T7, P4-T8 | TESTS: CMD-PACKAGE-COMPARE | EVIDENCE: coverage-baseline.md, coverage-final.md; check-off P4-T31 +AC-MAPPING: AC19 | IMPLEMENTATION: P4-T12 and the artifact-hygiene convention | TESTS: CMD-SWEEP | EVIDENCE: p4-t12-hygiene-sweep; check-off P4-T32 +UNRESOLVED-GAPS: NONE +PREFLIGHT: VALIDATION REQUESTED (DIRECTIVE: PREFLIGHT VALIDATION ONLY through atomic-executor; the planner-side record above is not executor clearance) + +--- + +### Phase 0 — Policy Reads, Preconditions and Toolchain Baseline + +- [x] [P0-T1] Read, in this exact order, the policy and instruction files CLAUDE.md, .claude/rules/general-code-change.md, .claude/rules/general-unit-test.md, .claude/rules/quality-tiers.md, .claude/rules/csharp.md, .claude/rules/tonality.md, .claude/rules/plan-acceptance-gates.md, .claude/skills/atomic-plan-contract/SKILL.md, .claude/skills/acceptance-criteria-tracking/SKILL.md, .claude/skills/evidence-and-timestamp-conventions/SKILL.md, then FEATURE/spec.md, FEATURE/issue.md and FEATURE/research/2026-09-28T20-15-tests-depend-on-uncontrolled-environment-research.md, and write FEATURE/evidence/baseline/phase0-instructions-read.md with `Timestamp:`, `Policy Order:` (the ordered list above) and one line per file giving its repository-relative path and line count. The filename carries no timestamp suffix because the atomic-plan contract names it exactly. Acceptance: the artifact exists at that exact path and lists all thirteen files, each with an integer line count. + +- [x] [P0-T2] Verify the full-bug preconditions read-only and record them in FEATURE/evidence/baseline/p0-t2-mode-preconditions..md. Acceptance, all five required: FEATURE/issue.md contains the exact line `- Work Mode: full-bug`; FEATURE/spec.md contains a heading line whose text is exactly `## Acceptance Criteria`; the box-state-independent inventory regex `^- \[[ x]\] AC([1-9]|1[0-9])\. ` matches exactly 19 lines of FEATURE/spec.md and every one of them begins `- [ ] ` (none is checked before execution starts); FEATURE/user-story.md does not exist; the spec's `## Write Set` section lists the six code and project paths of this plan's Write Set and no other code path. Any failure is `MODE PRECONDITION FAILED`: stop and report; the executor does not edit FEATURE/spec.md to repair it. + +- [x] [P0-T3] Record the working context, the diff anchors, the inherited-path set and the pre-implementation gate readiness in FEATURE/evidence/baseline/p0-t3-worktree-context..md. This task uses only `git` invocations and the Read tool. Run, in this order: `git rev-parse --abbrev-ref HEAD`; `git rev-parse HEAD` (recorded as `BASE-SHA:`); `git fetch origin main` (recorded as `FETCH-EXIT:`; a non-zero value is recorded and the task continues with the origin/main ref already present); `git merge-base HEAD origin/main` (recorded as `MERGE-BASE:`); `git diff --name-only MERGE-BASE...HEAD` and `git status --porcelain --untracked-files=all`, whose union of paths is recorded under `INHERITED-CLAUSE-A:` (the name-listing diff cannot see untracked paths, which is why the porcelain span is its companion). Record `TOPLEVEL CONTAINS FEATURE:` as `YES` or `NO` by testing that FEATURE/spec.md exists beneath the `git rev-parse --show-toplevel` value, and `TOPLEVEL LEAF:` as only that value's final path segment; do not record the value itself. Then read artifacts/orchestration/orchestrator-state.json with the Read tool (never `git add`, never edit) and record `CHECKPOINT-EXISTS:`, `CHECKPOINT-ISSUE-NUM:`, `CHECKPOINT-FEATURE-FOLDER:`, `CHECKPOINT-ROUTE:` (the `route_id` value, else `path_selected`, else `ABSENT`) and `CHECKPOINT-LIFECYCLE-READY:` (fact 22), and `PRE-IMPLEMENTATION GATE READY:` as `YES` only when the file exists, `CHECKPOINT-ISSUE-NUM:` is `931`, `CHECKPOINT-FEATURE-FOLDER:` begins docs/features/active/, `CHECKPOINT-ROUTE:` is not `ABSENT` and `CHECKPOINT-LIFECYCLE-READY:` is `true`; otherwise `NO`. Acceptance, all six required: the abbreviated branch name equals bug/tests-depend-on-uncontrolled-environment-931 (otherwise report `BRANCH MISMATCH` and stop; do not create or switch branches); `BASE-SHA:` and `MERGE-BASE:` are each a 40-character hexadecimal value; `INHERITED-CLAUSE-A:` is present and lists none of the six Write Set code paths (a listed one is `WRITE SET ALREADY DIRTY`: stop and report); `TOPLEVEL CONTAINS FEATURE: YES`; `PRE-IMPLEMENTATION GATE READY:` is recorded, and when it is `NO` the executor reports `PRE-IMPLEMENTATION GATE NOT SEEDED` with the five `CHECKPOINT-` values and stops at this task; the artifact contains no absolute filesystem path (any absolute value the checkpoint carries is recorded as ``). + +- [x] [P0-T4] Probe the command channel, then bootstrap and prove the C# toolchain, writing FEATURE/evidence/baseline/p0-t4-channel-and-toolchain..md. Part 1: run `pwsh -NoProfile -Command 'Set-Location -LiteralPath "WORKTREE"; Write-Output ("PROBE-OK " + (Test-Path -LiteralPath "TaskMaster.sln"))'` and record `CHANNEL:` as `COMMAND` when the line `PROBE-OK True` is observed, else `UNAVAILABLE` with the refusal or error text verbatim (absolute paths and account names replaced before writing). When `CHANNEL: UNAVAILABLE`, report `CHANNEL UNAVAILABLE` and stop at this task; the executor does not modify hook or permission configuration to obtain a channel. Part 2 (only under `CHANNEL: COMMAND`): record `RUNSETTINGS-HASH:` (TaskMaster.runsettings), `CLI-RUNSETTINGS-HASH:` (scripts/vscode/TaskMaster.cli.runsettings), `PRE-EDIT-HASH-AFF:`, `PRE-EDIT-HASH-BND:`, `PRE-EDIT-HASH-FIW:` and `PRE-EDIT-HASH-CSPROJ:` (the four existing Write Set files) as `Get-FileHash -Algorithm SHA256 -LiteralPath` values, and `NEW-FILES-ABSENT:` as whether neither `QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.Part2.cs` nor `QuickFiler.Test/TestSupport/DedicatedWorkerThread.cs` exists; then run, inside payloads, `& pwsh -NoProfile -File (Join-Path (Get-Location).Path "scripts\vscode\Install-RepoDotNetSdk.ps1")` (idempotent), test that the directory .dotnet-sdk\sdk\8.0.205 exists (`SDK-MARKER:`), `dotnet --version`, `dotnet tool restore`, `dotnet tool list --local` (the `csharpier` row must read `1.2.6`), resolve MSBuild.exe and vstest.console.exe through vswhere (recorded as `MSBUILD-RESOLVED:` and `VSTEST-RESOLVED:`, `YES` or `NO`, plus only the path segment after `Microsoft Visual Studio\`), and `dotnet-coverage --version` (if not found, run `dotnet tool install --global dotnet-coverage` and then `dotnet-coverage --version` again in a separate invocation). Acceptance, all nine required: `CHANNEL: COMMAND`; the six hashes are each 64-character hexadecimal values; `NEW-FILES-ABSENT: True`; `SDK-MARKER: YES`; `dotnet --version` and `dotnet tool restore` exit 0; the `csharpier` row shows `1.2.6`; `MSBUILD-RESOLVED: YES` and `VSTEST-RESOLVED: YES`; `dotnet-coverage --version` exits 0 with its version string recorded; the artifact contains no absolute filesystem path. + +- [x] [P0-T5] Restore NuGet packages by running one payload whose statements after the `Set-Location` are, in this order: `New-Item -ItemType Directory -Path "coverage\logs" -Force | Out-Null` (the log directory does not exist in a fresh worktree and `Tee-Object` creates no parent directory); `$env:MSBUILDDISABLENODEREUSE = "1"` (D-16: the restore script issues its own msbuild invocation without the /nodeReuse:false switch, and the inherited environment variable is MSBuild's equivalent); `& pwsh -NoProfile -File (Join-Path (Get-Location).Path "scripts\vscode\Invoke-Restore.ps1") 2>&1 | Tee-Object -FilePath "coverage\logs\p0-t5.restore.log"`; `Write-Output ("RESTORE_EXIT_CODE: " + $LASTEXITCODE)`; `Write-Output ("PACKAGE-DIR-COUNT: " + @(Get-ChildItem -LiteralPath packages -Directory).Count)`. Write FEATURE/evidence/baseline/p0-t5-nuget-restore..md with `Timestamp:`, `Command:` (`pwsh -NoProfile -File scripts\vscode\Invoke-Restore.ps1`, with the note that the payload resolved the absolute script path at run time through `Join-Path (Get-Location).Path` and set MSBUILDDISABLENODEREUSE to 1), `EXIT_CODE:` (the printed `RESTORE_EXIT_CODE:`) and an `Output Summary:` recording `PACKAGE-DIR-COUNT:`. A fresh agent worktree has no packages directory and no bin\Debug output, so every later build and test task depends on this step. Acceptance, both required: `EXIT_CODE: 0`; `PACKAGE-DIR-COUNT:` is at least 1. + +- [x] [P0-T6] Capture the baseline formatting state by running `dotnet tool run csharpier check .` inside a payload from the worktree root and write FEATURE/evidence/baseline/p0-t6-csharpier-check..md with `Timestamp:`, `Command:`, `EXIT_CODE:` and an `Output Summary:` quoting the tool's final summary line verbatim (shape `Checked N files in Xms.`; the check command never prints a `Formatted` line) and recording `CHECKED-FILES:` as that N. Acceptance, both required: `EXIT_CODE: 0`; `CHECKED-FILES:` is a positive integer. A non-zero exit means pre-existing drift the repository owns, and the later repository-wide format would fold that repair into this branch: stop and report `FORMAT BASELINE NOT CLEAN` with the tool's file list; do not run `format` to repair it. + +- [x] [P0-T7] Capture the baseline analyzer state with `CMD-REBUILD` using the analyzer `GATEARGS` and `TASKID` `p0-t7` (`Command:` records `msbuild TaskMaster.sln /t:Rebuild /m /p:Configuration=Debug "/p:Platform=Any CPU" /p:EnableNETAnalyzers=true /p:EnforceCodeStyleInBuild=true`, resolved through vswhere, plus /nodeReuse:false) and write FEATURE/evidence/baseline/p0-t7-msbuild-analyzers..md with `Timestamp:`, `Command:`, `EXIT_CODE:` (the printed `MSBUILD_EXIT_CODE:`, also recorded as `ANALYZE-BASELINE-EXIT:`), and an `Output Summary:` recording `SKIP_CORECOMPILE_LINES:`, `QF_TEST_CSC_OUT_LINES:`, `UCS_TEST_CSC_OUT_LINES:`, `ZERO_ERRORS_LINES:`, `WARNINGS:` (also `ANALYZE-BASELINE-WARNINGS:`), `ERRORS:` and `WRITESET_DIAGNOSTIC_LINES:`. Acceptance, all five required: `EXIT_CODE: 0`; `SKIP_CORECOMPILE_LINES: 0`; `QF_TEST_CSC_OUT_LINES:` and `UCS_TEST_CSC_OUT_LINES:` are each at least 1 (the two test projects were compiled, not skipped); `ERRORS: 0`; `WRITESET_DIAGNOSTIC_LINES: 0` (a non-zero value is `WRITE SET DIAGNOSTIC BASELINE NOT CLEAN`: stop and report the matching log lines, because P4-T3 requires the same value to be 0 and a pre-existing diagnostic in a Write Set file could not be attributed to this change). A non-zero exit or a non-zero error count is `ANALYZER BASELINE NOT CLEAN`: stop and report, because AC17 cannot then be met by a test-only change. + +- [x] [P0-T8] Capture the baseline nullable and type-check state with `CMD-REBUILD` using the nullable `GATEARGS` and `TASKID` `p0-t8` (`Command:` records `msbuild TaskMaster.sln /t:Rebuild /m /p:Configuration=Debug "/p:Platform=Any CPU" /p:TreatWarningsAsErrors=true`, resolved through vswhere, plus /nodeReuse:false; no Nullable property override, no incremental Build target) and write FEATURE/evidence/baseline/p0-t8-msbuild-nullable..md with the P0-T7 field set (`NULLABLE-BASELINE-EXIT:`, `NULLABLE-BASELINE-WARNINGS:`) plus `QF-TEST-DLL-EXISTS:` and `UCS-TEST-DLL-EXISTS:` as whether QuickFiler.Test\bin\Debug\QuickFiler.Test.dll and UtilitiesCS.Test\bin\Debug\UtilitiesCS.Test.dll exist after the command. Acceptance, all six required: `EXIT_CODE: 0`; `SKIP_CORECOMPILE_LINES: 0`; both `_CSC_OUT_LINES:` values at least 1; `ERRORS: 0`; `WRITESET_DIAGNOSTIC_LINES: 0` (otherwise `WRITE SET DIAGNOSTIC BASELINE NOT CLEAN`: stop and report the matching log lines); both `-DLL-EXISTS:` values `True` (the precondition for P0-T9 and P0-T10). A non-zero exit is `NULLABLE BASELINE NOT CLEAN`: stop and report. + +- [x] [P0-T9] Run the stall probe with `CMD-VSTEST` using `ASSEMBLY-UCS`, `FILTER-STALL`, `NAMES-NONE` and `TASKID` `p0-t9`, and write FEATURE/evidence/baseline/p0-t9-stall-probe..md with `Timestamp:`, `Command:`, `EXIT_CODE:` (the printed `VSTEST_EXIT_CODE:`, or the payload exit code 3 when the TRX is absent), `ExpectedExitCode:` equal to the observed value when it is non-zero (presentational; this task gates nothing on the exit code), and an `Output Summary:` recording `RUNSETTINGS-HASH-NOW:`, `TRX_PRESENT:`, `SEQUENCE_FILES:`, `COLLECTOR_LINES:`, the `COUNTERS` line when present and every `MESSAGE` line. Then record exactly one `STALL-PROBE:` line: `CLEAR` when `EXIT_CODE: 0`, `failed` is 0 and `SEQUENCE_FILES: 0`; otherwise `REPRODUCES`. From it record exactly one `UCS-FILTERARG:` line (`NONE` under `CLEAR`; the quoted `FILTER-UCS-EXCLUDE` argument verbatim under `REPRODUCES`) and exactly one `COVERAGE-ROUTE:` line (`RUNNER` under `CLEAR`; `DIRECT` under `REPRODUCES`), with the sentence that the four excluded classes are a pre-existing local stall reproduced on main and are executed by CI (fact 16). Acceptance, all three required: `RUNSETTINGS-HASH-NOW:` equals `RUNSETTINGS-HASH:` from P0-T4; exactly one `STALL-PROBE:`, one `UCS-FILTERARG:` and one `COVERAGE-ROUTE:` line are present with the values the rule above derives; the probe took at most one invocation (it is never re-run). Both `STALL-PROBE:` values complete this task. + +- [x] [P0-T10] Capture the baseline parallel-suite runs of the two affected assemblies, before any edit, and write FEATURE/evidence/baseline/test-run-baseline.md (fixed name per the spec). Run `CMD-VSTEST` with `ASSEMBLY-QF`, empty `FILTERARG`, `NAMES-QF` and `TASKID` `p0-t10-qf`, then `CMD-VSTEST` with `ASSEMBLY-UCS`, `UCS-FILTERARG` (P0-T9), `NAMES-FIW` and `TASKID` `p0-t10-ucs`. The artifact carries `Timestamp:`, `Command:` (both commands, the runsettings file name, the isolation switch and the UtilitiesCS filter verbatim or `NONE`), `EXIT_CODE:` (scoped to the QuickFiler.Test run; the UtilitiesCS.Test exit code under `UCS-VSTEST-EXIT:`), `ExpectedExitCode:` equal to the QuickFiler.Test exit code when it is non-zero (presentational), and an `Output Summary:` with, per assembly, `RUNSETTINGS-HASH-NOW:`, the `COUNTERS` line, `RESULT_COUNT:`, `SEQUENCE_FILES:`, `COLLECTOR_LINES:`, every `RESULT` line and every `MESSAGE` line, plus `BASELINE-FAILED-QF:` and `BASELINE-FAILED-UCS:` listing every `Failed` test name or `NONE`. Acceptance, all five required: both `RUNSETTINGS-HASH-NOW:` values equal `RUNSETTINGS-HASH:`; both `COUNTERS` lines are present with `executed` at least 1; the seven `NAMES-AFFINITY` names and `Dispatcher_OwnerOnlyWorker_ReportsWithoutRunningAction` each appear in a `RESULT` line of the QuickFiler.Test run, and the eight `NAMES-FIW` names each appear in a `RESULT` line of the UtilitiesCS.Test run (this is the pre-edit population the final runs are compared against); both `SEQUENCE_FILES:` values are 0; both `BASELINE-FAILED-` lines are present. When either failed set is non-empty, this task records it (D-17): a failed set that is a subset of `BASELINE-ADMISSIBLE` (the six names D-17 lists) completes the task without any re-run; a set containing any other name is `BASELINE NOT GREEN`, reported with the `MESSAGE` lines, and the run stops. + +- [x] [P0-T11] Capture the baseline repository-wide test and coverage run by the route P0-T9 fixed and write FEATURE/evidence/baseline/coverage-baseline.md (fixed name per the spec). Under `COVERAGE-ROUTE: RUNNER` run `CMD-COVERAGE-RUNNER` with `STAGE` `baseline`; under `DIRECT` run `CMD-COVERAGE-DIRECT` with `STAGE` `baseline`; then, under either route and unless branch (d0) below applies, run `CMD-COVERAGE-POST` with `STAGE` `baseline`. In both cases `RAW` is the value the Command Reference rule derives: `True` under `DIRECT`; under `RUNNER` `True` when the run's `COLLECT_FAILURE_MESSAGE:` is non-empty and `False` otherwise; the artifact records `RAW:` with the value used. Under `RUNNER` a run still in progress after 120 minutes is `COVERAGE RUN STALLED`: stop and report (the runner passes no blame argument). The artifact carries `Timestamp:`, `Command:` (both payloads named, the route, and the filter the route applied), `EXIT_CODE:` (`RUNNER_EXIT_CODE:` or `COLLECT_EXIT_CODE:`), `ExpectedExitCode:` equal to the observed value when it is non-zero and the task completes under branch (b), and an `Output Summary:` recording `COVERAGE-ROUTE:`, `RAW:`, `DISCOVERED_LINE:` or `ASSEMBLY_COUNT:` with every `ASSEMBLY:` line, `SEQUENCE_FILES:` (DIRECT) and `TRX_PRESENT:` (both routes; the DIRECT payload prints it after its stage copy), `THRESHOLD_MESSAGE:` (RUNNER), and `LINE-FLOOR:` and `BRANCH-FLOOR:` as printed by `CMD-COVERAGE-POST` (every run), the `First-party coverage:` line, the projection verbatim between `PROJECTION-BEGIN` and `PROJECTION-END`, the five summary lines verbatim between `SUMMARY-BEGIN` and `SUMMARY-END`, and `FAILED-SET:`. Branches, decided by the recorded values and checked in the order (d0), (c), (b), (a), (d): (d0), checked first: a run with `SEQUENCE_FILES:` greater than 0 (DIRECT) or with no stage TRX at coverage\baseline-931.trx is `COVERAGE RUN ABORTED`: stop and report `COLLECT_FAILURE_MESSAGE:` or the last lines of the collector log with absolute paths replaced; do not re-run. The stage-TRX test is the run payload's `TRX_PRESENT:` value under both routes, so (d0) is decided before `CMD-COVERAGE-POST` runs, and when (d0) applies that payload is not run (its first statement reads the stage TRX); under DIRECT the report also names the last test the Sequence document lists. (c) runner exit non-zero with a `THRESHOLD_MESSAGE:`, or, whenever `RAW:` is `True` (every `DIRECT` run, and a `RUNNER` run whose `COLLECT_FAILURE_MESSAGE:` is non-empty), a `LINE-FLOOR: NOT MET` or `BRANCH-FLOOR: NOT MET` line printed by `CMD-COVERAGE-POST`: run `CMD-COVERAGE-POST` with `RAW` per the Command Reference rule if it has not already run, so the projection is recorded (under this task's opening instruction it has already run whenever (d0) does not apply, and it is not run a second time), record `FLOOR: NOT MET` with the message, and stop with `COVERAGE FLOOR BASELINE NOT MET`, because AC17 requires the coverage route to pass and this item changes no production line. (b) non-zero exit with no floor failure whose `FAILED-SET:` is non-empty and is a subset of `BASELINE-ADMISSIBLE` (D-17): record the set, and complete with no re-run (D-12); `RAW` is `True` for such a run under either route, because a failed RUNNER run throws before post-processing. A `FAILED-SET:` containing any other name is `BASELINE NOT GREEN`: stop and report with the `Failed tests:` summary line. (a) exit 0 with both floors met: complete. (d) any outcome that (c), (b) and (a) do not decide, in particular a non-zero exit with an empty `FAILED-SET:`, is `COVERAGE RUN ABORTED` with the same handling. Acceptance, all five required: the projection block contains a `package` element named `UtilitiesCS` and one named `QuickFiler`, each with a `LINE` and a `BRANCH` counter; the `First-party coverage:` line is present with four integer counts and two percentages; the summary block's first line begins `Test run outcome:`; `FAILED-SET:` is present; the artifact contains no absolute path (the runner's own path-bearing lines are not transcribed). coverage\baseline-931.jacoco.xml and coverage\baseline-931.cobertura.xml remain on disk, git-ignored, for P4-T8. + +- [x] [P0-T12] Record the pre-edit census by running `CMD-CENSUS` once for each of `QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.cs`, `QuickFiler.Test/Viewers/BreadcrumbPopupBoundaryCoverageTests.cs`, `UtilitiesCS.Test/HelperClasses/FileInfoWrapper_Tests.cs`, `QuickFiler.Test/QuickFiler.Test.csproj`, QuickFiler/Viewers/BreadcrumbUiDispatcher.cs and QuickFiler/Viewers/ItemViewer.Breadcrumb.cs, and write FEATURE/evidence/baseline/p0-t12-pre-edit-census..md transcribing every `TOKEN`, `LINES` and `SHA256` line per file, plus `N1:` (the DISP `return true;` count) and `N2:` (the DISP `_ownerThreadId.HasValue` count). These are the positive controls every later count gate is measured against. Acceptance, all four required: every value listed in the `pre` column of the Token Census Expectations table matches (in particular AFF `LINES = 490`, `Task.Run(` 1, `RunOnDedicatedWorkerThread` 5, `partial class` 0; BND `LINES = 361`, `Task.Run(` 1, `.GetAwaiter()` 4, `ownerThreadId` 0; FIW `LINES = 359`, `GetSolutionFile` 5, `TaskMaster.sln` 1, `FileMode.` 12, `FileAccess.` 10, `FixturePath` 0; CSPROJ INCLUDE-PART2 0 and INCLUDE-HELPER 0; IVB `System.Threading.SynchronizationContext.Current` 0); `N2:` is at least 1; the four `SHA256` values for the existing Write Set files equal the four `PRE-EDIT-HASH-` values of P0-T4; any mismatch is `TREE DIVERGED FROM PLAN` and is reported before any edit. + +- [x] [P0-T13] Commit the Phase 0 evidence and the feature documents in the exemption-eligible form. Run, as two separate invocations, `git add -- docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931` and then `git commit -m "docs(931): phase 0 baseline evidence for the uncontrolled-environment test fix" -- docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931` (the subject `-m` paragraph, then, when the session requires an attribution trailer, a second `-m` paragraph in the bare-address form the Commits convention fixes, one pathspec operand after `--`, one command segment, no `$`, backtick, `<` or `>` character anywhere on the line; every staged path is under docs/features/active/), then `git status --porcelain -- QuickFiler QuickFiler.Test UtilitiesCS UtilitiesCS.Test` and `git rev-parse HEAD`. Write FEATURE/evidence/baseline/p0-t13-phase0-commit..md recording `COMMIT-EXIT:`, `PHASE0-HEAD:` and the scoped porcelain output verbatim (`EXIT_CODE:` is scoped to the porcelain span). Acceptance, all three required: `COMMIT-EXIT: 0`; the scoped porcelain output is empty, proving no source edit preceded Phase 1; `PHASE0-HEAD:` differs from `BASE-SHA:`. A PreToolUse refusal is `PRE-IMPLEMENTATION GATE BLOCKED`: stop and report. The artifact this task writes is committed by P2-T10. + +### Phase 1 — Fail-Before Evidence + +- [x] [P1-T1] Write the fail-before exception dossier FEATURE/evidence/regression-testing/fail-before-exception..md, where `` equals its own `Timestamp:` field, with these sections: `Timestamp:`; `WhyFailingRunImpossible:` (one to three sentences: the distinct-thread defect is decided by whether the runtime inlines a pool work item onto the waiting thread, a branch no committed test can force without mutating process-global thread-pool state; the file-handle defect is decided by whether another process holds the solution file with a share mode excluding readers, which a committed test cannot arrange without starting an external process; both are prohibited by the unit-test policy); `## Alternative Proof` (the spec's Repro steps 1 to 3 with their line citations as re-recorded by P0-T12, fact 17's record of the observed #906 failure in the #900 run, and the sentence that P3-T1, P3-T3, P3-T5 and P3-T7 later demonstrate deterministically that each rewritten test fails against a deliberately broken guard, without stating those results as observations); `SearchScope:` (FEATURE/evidence/regression-testing/), `SearchPatterns:` (`fail-before-exception.*.md`), `SearchResult:` (this file); `## Output Summary`. Acceptance, all four required: the file exists with all named sections; its filename timestamp equals its `Timestamp:` field; `WhyFailingRunImpossible:` is non-empty; the text contains the literal `P3-T1` and the literal `inline` at least once each. + +### Phase 2 — Fix + +- [x] [P2-T1] Create `QuickFiler.Test/TestSupport/DedicatedWorkerThread.cs` with exactly the Target Source A content, then run `CMD-CENSUS` on it and write FEATURE/evidence/regression-testing/p2-t1-helper-census..md transcribing every `TOKEN`, `LINES` and `SHA256` line. Acceptance, all four required: `namespace QuickFiler.Test.TestSupport` 1, `internal static class DedicatedWorkerThread` 1, `internal static Exception Run(Action action)` 1, `new Thread(` 1, `IsBackground = true` 1, `thread.Join();` 1, `action();` 1, `distinct from every live thread by construction` 1; `Join(` equals `Join()`; `.Should()` 0, `Task.Run(` 0, `Thread.Sleep` 0, `Task.Delay` 0, `[Timeout` 0, `DoNotParallelize` 0, `Retry` 0; `LINES` at most 500. A PreToolUse refusal of the file creation is `PRE-IMPLEMENTATION GATE BLOCKED`: stop and report. + +- [x] [P2-T2] Register the two new files in `QuickFiler.Test/QuickFiler.Test.csproj` exactly as Target Source F states, then run `CMD-CENSUS` on the project file, `git diff --numstat HEAD -- QuickFiler.Test/QuickFiler.Test.csproj` and `git status --porcelain -- QuickFiler.Test/QuickFiler.Test.csproj`, and write FEATURE/evidence/regression-testing/p2-t2-csproj-census..md transcribing the three project-file `TOKEN` lines, the numstat line and the porcelain line. Acceptance, all three required: INCLUDE-AFFINITY 1, INCLUDE-PART2 1, INCLUDE-HELPER 1; the numstat line reads 2 added and 0 deleted; the porcelain line begins ` M` for the project file. + +- [x] [P2-T3] Create `QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.Part2.cs` with exactly the Target Source B content (the two verbatim test blocks copied from pre-edit lines 199 to 251 and 253 to 304 of the primary file with the two named substitutions each, the rewritten null-owner test, and `ClearViewerDispatcher` copied from pre-edit lines 357 to 371), then run `CMD-CENSUS` on it and write FEATURE/evidence/regression-testing/p2-t3-part2-census..md transcribing every `TOKEN`, `LINES` and `SHA256` line. Acceptance, all four required: `DedicatedWorkerThread.Run(` 3, `ClearViewerDispatcher(` 2, `[TestMethod]` 3, `dedicated worker thread must not be` 3; `partial class` 1, `using QuickFiler.Test.TestSupport;` 1, `using System.Reflection;` 1, `owner.CheckAccess()` 1, `.BeNull(` 1, `unconditionally` 1; `Task.Run(` 0, `.GetAwaiter()` 0, `RunOnDedicatedWorkerThread` 0, `action();` 0, `Thread.Sleep` 0, `Task.Delay` 0, `[Timeout` 0, `DoNotParallelize` 0, `Retry` 0; `LINES` at most 500. + +- [x] [P2-T4] Apply the five Target Source C edits to `QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.cs`, then run `CMD-CENSUS` on it, `git diff --numstat HEAD -- QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.cs` and `git status --porcelain -- QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.cs`, and write FEATURE/evidence/regression-testing/p2-t4-primary-census..md transcribing every `TOKEN`, `LINES` and `SHA256` line, the numstat line and the porcelain line. Acceptance, all five required: `partial class` 1 and `[TestMethod]` 4; `Task.Run(` 0, `.GetAwaiter()` 0, `RunOnDedicatedWorkerThread` 0, `ClearViewerDispatcher(` 0, `action();` 0, `new Thread(` 0, `IsBackground = true` 0, `thread.Join();` 0, `using System.Reflection;` 0, `dedicated worker thread must not be` 0, `distinct from every live thread by construction` 0, `DedicatedWorkerThread.Run(` 0; `LINES` at most 500 and at least 280; the numstat line reads at most 4 added and at least 197 deleted (the edits add the `partial` declaration line and two remark lines and delete 199 lines); the porcelain line begins ` M`. + +- [x] [P2-T5] Apply the Target Source D edits to `QuickFiler.Test/Viewers/BreadcrumbPopupBoundaryCoverageTests.cs`, then run `CMD-CENSUS` on it and `git status --porcelain -- QuickFiler.Test/Viewers/BreadcrumbPopupBoundaryCoverageTests.cs`, and write FEATURE/evidence/regression-testing/p2-t5-boundary-census..md transcribing every `TOKEN`, `LINES` and `SHA256` line and the porcelain line. Acceptance, all four required: `Task.Run(` 0 and `.GetAwaiter()` 3 (each one less than P0-T12); `DedicatedWorkerThread.Run(` 1, `using QuickFiler.Test.TestSupport;` 1, `.NotBe(` 1, `.BeNull(` 1, `dedicated worker thread must not be` 1, `ownerThreadId` 2; `executions.Should().Be(0)` 1, `cannot marshal` 1, `partial class` 1, `Thread.Sleep` 0, `Task.Delay` 0, `[Timeout` 0, `DoNotParallelize` 0; `LINES` at most 500. + +- [x] [P2-T6] Apply the three Target Source E edits to `UtilitiesCS.Test/HelperClasses/FileInfoWrapper_Tests.cs`, then run `CMD-CENSUS` on it, `git diff -U0 HEAD -- UtilitiesCS.Test/HelperClasses/FileInfoWrapper_Tests.cs` and `git status --porcelain -- UtilitiesCS.Test/HelperClasses/FileInfoWrapper_Tests.cs`, and write FEATURE/evidence/regression-testing/p2-t6-fileinfowrapper-census..md transcribing every `TOKEN`, `LINES` and `SHA256` line, the porcelain line, and these counts over the diff's added lines (lines beginning with a single `+`): `ADDED-LENGTH:` (added lines containing `.Length`, excluding any line whose trimmed text is exactly `stream.Length.Should().BeGreaterThan(0);`, because that line is byte-identical to pre-edit line 66 and whether the diff reports it as added or as unchanged context depends on hunk alignment), `ADDED-OPENREAD:` (lines containing `OpenRead()`), `ADDED-FIXTUREPATH:` (lines containing `FixturePath`), `ADDED-OPEN-CREATE-WRITE:` (lines containing `.Open(`, `.Create(` or `.OpenWrite(`). Acceptance, all six required: `GetSolutionFile` 0, `TaskMaster.sln` 0, `AppDomain` 0, `File.Exists(` 0, `File.Create` 0, `File.WriteAll` 0, `File.Delete` 0, `Path.GetTemp` 0; `FileMode.` equals `FileMode.Open` (13) and `FileAccess.` equals `FileAccess.Read` (11); `FileShare.ReadWrite` 9, `Assembly.Location` 7, `FixturePath` 4, `using var sentinel = new FileStream(` 1, `BeSameAs(sentinel)` 1, `.Returns(decoy)` 0, `wrapper.OpenRead()` 2, `stream.CanRead.Should().BeTrue()` 1, `stream.Length.Should().BeGreaterThan(0)` 1, `[TestMethod]` 8; `ADDED-LENGTH: 0` (the only `.Length` in the rewritten tests is the excluded assertion line; the Arrange comment names no member), `ADDED-OPENREAD: 2` (the mock setup line and the `FileStream stream = wrapper.OpenRead();` line, which differs from pre-edit line 62), `ADDED-FIXTUREPATH: 4`, `ADDED-OPEN-CREATE-WRITE: 0` (the three metadata tests open no stream and call none of `Open`, `Create` or `OpenWrite`, AC10); `LINES` at most 500; the porcelain line begins ` M`. + +- [x] [P2-T7] Format and verify the five Write Set `.cs` files with the pinned CSharpier. Inside one payload: capture the SHA-256 of each of `QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.cs`, `QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.Part2.cs`, `QuickFiler.Test/TestSupport/DedicatedWorkerThread.cs`, `QuickFiler.Test/Viewers/BreadcrumbPopupBoundaryCoverageTests.cs` and `UtilitiesCS.Test/HelperClasses/FileInfoWrapper_Tests.cs`; run `dotnet tool run csharpier format` with those five paths as arguments; capture the five hashes again; run `dotnet tool run csharpier check` with the same five paths; then run `CMD-CENSUS` on each of the five. Write FEATURE/evidence/regression-testing/p2-t7-csharpier-scoped..md with `Timestamp:`, `Command:` (all three), `EXIT_CODE:` (of the `check`), and an `Output Summary:` recording the ten hashes, `REWRITTEN:` as the number of files whose hash changed (the console line `Formatted 5 files in Xms.` is a processed-file count and is not this value), the `check` command's final summary line verbatim, and every `TOKEN` and `LINES` line per file. Acceptance, all three required: the `check` exits 0; every `post` value of the Token Census Expectations table holds for all five files (formatting re-wraps lines but every counted token is single-line by construction); every `LINES` value is at most 500. If any `LINES` exceeds 500, stop and report `FILE SIZE LIMIT EXCEEDED` with the file and value; do not shorten remarks to recover, because the remarks carry pinned census literals. + +- [x] [P2-T8] Build both test projects from the edited source with `CMD-BUILD-QF` (`TASKID` `p2-t8-qf`) and `CMD-BUILD-UCS` (`TASKID` `p2-t8-ucs`) and write FEATURE/evidence/regression-testing/p2-t8-build-after-fix..md with `Timestamp:`, `Command:` (`msbuild QuickFiler.Test\QuickFiler.Test.csproj /t:Build /m /p:Configuration=Debug /p:Platform=AnyCPU` and the UtilitiesCS.Test twin, resolved through vswhere, plus /nodeReuse:false), `EXIT_CODE:` (scoped to the QuickFiler.Test build; the other under `UCS-MSBUILD-EXIT:`), and an `Output Summary:` recording, per build, `CSC_OUT_LINES:`, `PROD_CSC_OUT_LINES:`, `ZERO_ERRORS_LINES:` and `DLL_ADVANCED:`. Acceptance, all three required: both exit codes 0; both `CSC_OUT_LINES:` at least 1 and both `DLL_ADVANCED: True` (the edited files were compiled into the assemblies the next task loads); both `ZERO_ERRORS_LINES:` at least 1. + +- [x] [P2-T9] Run the rewritten tests before any control, as confirming runs (the measured runs are P4-T5 and P4-T6). Run `CMD-VSTEST` four times: `ASSEMBLY-QF` with `FILTER-FOUR`, `NAMES-FOUR`, `TASKID` `p2-t9-four`; `ASSEMBLY-UCS` with `FILTER-OPENREAD`, `NAMES-FIW`, `TASKID` `p2-t9-openread`; `ASSEMBLY-QF` with `FILTER-AFFINITY-CLASS`, `NAMES-AFFINITY`, `TASKID` `p2-t9-affinity`; `ASSEMBLY-UCS` with `FILTER-FIW-CLASS`, `NAMES-FIW`, `TASKID` `p2-t9-fiw`. Write FEATURE/evidence/regression-testing/p2-t9-pass-before-controls..md with `Timestamp:`, `Command:` (all four), `EXIT_CODE:` (scoped to the first run; the other three under named lines) and an `Output Summary:` recording, per run, `RUNSETTINGS-HASH-NOW:`, the `COUNTERS` line, every `RESULT` line and every `MESSAGE` line. Acceptance, all five required: all four exit codes 0; `total` is 4, 1, 7 and 8 respectively with `failed` 0 in each (a different total means a stale assembly or an unregistered file and fails this task); every `RESULT` line reads `Passed`; the four `NAMES-FOUR` names, the seven `NAMES-AFFINITY` names and the eight `NAMES-FIW` names each appear in a `RESULT` line of the run that targets them; every `RUNSETTINGS-HASH-NOW:` equals `RUNSETTINGS-HASH:`. If any test fails, the fix is wrong: repair within the Target Source contract, re-run P2-T7 through P2-T9, and record each iteration; do not proceed to P2-T10 with a failing run. + +- [x] [P2-T10] Commit the fix and the Phase 1 and Phase 2 evidence. Run `git add -- QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.cs QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.Part2.cs QuickFiler.Test/TestSupport/DedicatedWorkerThread.cs QuickFiler.Test/Viewers/BreadcrumbPopupBoundaryCoverageTests.cs QuickFiler.Test/QuickFiler.Test.csproj UtilitiesCS.Test/HelperClasses/FileInfoWrapper_Tests.cs docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931`, then `git commit -m "test(931): remove scheduler and file-handle dependence from breadcrumb affinity and FileInfoWrapper tests"` (with, when the session requires an attribution trailer, a second `-m` paragraph in the bare-address form the Commits convention fixes; no angle bracket, dollar sign or backtick anywhere on the line), then `git ls-files --eol -- QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.cs QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.Part2.cs QuickFiler.Test/TestSupport/DedicatedWorkerThread.cs QuickFiler.Test/Viewers/BreadcrumbPopupBoundaryCoverageTests.cs QuickFiler.Test/QuickFiler.Test.csproj UtilitiesCS.Test/HelperClasses/FileInfoWrapper_Tests.cs` (recorded verbatim as `EOL-BEFORE:`). For each of those six paths whose entry does not read w/crlf (the working-tree column), run one payload whose statement after the `Set-Location` is `Remove-Item -LiteralPath "PATH"`, with `PATH` the path in backslash separators, then, as a separate invocation, `git checkout -- ` (the same path in the forward-slash form the `git ls-files --eol` span uses). This makes the working-tree bytes identical to the bytes every later `git checkout` of that path writes under `core.autocrlf=true`; files created with the Write tool have LF endings (fact 23). Then run the same `git ls-files --eol` span again (`EOL-AFTER:`) and record the restored paths under `EOL-RESTORED:` (or `NONE`). Then run `git diff --exit-code HEAD -- QuickFiler.Test UtilitiesCS.Test`, `git show --name-only --format= HEAD`, `git rev-parse HEAD`, and the SHA-256 of each of the six code files, so every `FIX-HASH-` anchor is taken after the restore. Write FEATURE/evidence/regression-testing/p2-t10-fix-commit..md recording `COMMIT-EXIT:`, `EOL-BEFORE:`, `EOL-AFTER:`, `EOL-RESTORED:`, `FIX-HEAD:`, `FIX-HASH-AFF:`, `FIX-HASH-PART2:`, `FIX-HASH-HELPER:`, `FIX-HASH-BND:`, `FIX-HASH-CSPROJ:`, `FIX-HASH-FIW:` (the anchors every Phase 3 revert compares against), the `git show` path list verbatim, and `EXIT_CODE:` scoped to the `git diff --exit-code` span. Acceptance, all four required: `COMMIT-EXIT: 0`; `EXIT_CODE: 0`, proving the committed state equals the working tree for both test projects; the `git show` list contains exactly the six code paths plus paths under docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/ and nothing else; every `EOL-AFTER:` entry reads i/lf (index column) and w/crlf (working-tree column), and each `.cs` path not listed in `EOL-RESTORED:` has a hash equal to its P2-T7 post-format hash (a restored path differs from P2-T7 in line terminators only, which the `git diff --exit-code HEAD` span exiting 0 after the restore proves). This commit stages paths outside every exempt tree; a PreToolUse refusal is `PRE-IMPLEMENTATION GATE BLOCKED`: stop and report. + +### Phase 3 — Negative Controls (each rewritten guard test observed failing against a broken guard) + +- [x] [P3-T1] [expect-fail] Control M1, owner-only dispatcher guard disabled: apply mutation M1 (Target Source G) to QuickFiler/Viewers/BreadcrumbUiDispatcher.cs, run `CMD-CENSUS` on that file, `CMD-BUILD-QF` with `TASKID` `p3-t1`, then `CMD-VSTEST` with `ASSEMBLY-QF`, `FILTER-DISPATCHER`, `NAMES-FOUR`, `TASKID` `p3-t1`. Write FEATURE/evidence/regression-testing/mutation-owner-only-dispatcher-guard.md (fixed name) with `Timestamp:`, `Command:` (all three), `EXIT_CODE:` (the `VSTEST_EXIT_CODE:` of this mutated run; the artifact states that its exit-code row is scoped to the mutated run), `ExpectedExitCode: 1`, and a section `## Mutated run` recording the mutated file (QuickFiler/Viewers/BreadcrumbUiDispatcher.cs) and hunk (lines 276 to 277 replaced by `return true;`), `TOKEN return true; = N1 plus 1` and `TOKEN _ownerThreadId.HasValue = N2 minus 1` (with the P0-T12 values named), `MSBUILD_EXIT_CODE:`, `PROD_CSC_OUT_LINES:`, the runsettings file name, the isolation switch, the filter verbatim, `RUNSETTINGS-HASH-NOW:`, the `COUNTERS` line, every `RESULT` line and every `MESSAGE` line verbatim. Predicted failure (D-6): the test `Failed` with a `MESSAGE` line containing `to be 0, but found 1`. Acceptance, all five required: the two census transitions hold; `MSBUILD_EXIT_CODE: 0` and `PROD_CSC_OUT_LINES:` at least 1 (the mutated production file was compiled); `total` 1, `failed` 1; the `MESSAGE` line contains `to be 0, but found 1`; `RUNSETTINGS-HASH-NOW:` equals `RUNSETTINGS-HASH:`. A passing run, or a `MESSAGE` naming a different assertion, is `MUTATION PREDICTION MISMATCH`: stop and report the text. + +- [x] [P3-T2] Revert control M1 and confirm: run `git checkout -- QuickFiler\Viewers\BreadcrumbUiDispatcher.cs`, then `git diff --exit-code HEAD -- QuickFiler\Viewers\BreadcrumbUiDispatcher.cs`, `git status --porcelain -- QuickFiler\Viewers\BreadcrumbUiDispatcher.cs` (backslash pathspecs per the Commits convention), `CMD-CENSUS` on that file, `CMD-BUILD-QF` with `TASKID` `p3-t2`, and `CMD-VSTEST` with `ASSEMBLY-QF`, `FILTER-DISPATCHER`, `NAMES-FOUR`, `TASKID` `p3-t2`. Append to FEATURE/evidence/regression-testing/mutation-owner-only-dispatcher-guard.md a section `## Revert and confirming run` recording `REVERT-DIFF-EXIT:` (the `git diff --exit-code` exit), the porcelain output verbatim (`EMPTY` when it printed nothing), `TOKEN return true; = N1`, `TOKEN _ownerThreadId.HasValue = N2`, `SHA256:` (the census line) beside the P0-T12 `SHA256` of the same file, `PROD_CSC_OUT_LINES:`, `CONFIRMING-RUN-EXIT:`, `RUNSETTINGS-HASH-NOW:`, the `COUNTERS` line and every `RESULT` line. Acceptance, all six required: `REVERT-DIFF-EXIT: 0`; the porcelain output is empty; both census values equal the P0-T12 values; the post-revert SHA256 equals the P0-T12 SHA256 of the same file; `PROD_CSC_OUT_LINES:` at least 1 (the reverted production file was recompiled before the confirming run); `CONFIRMING-RUN-EXIT: 0` with `total` 1, `passed` 1 and the `RESULT` line `Passed`. + +- [x] [P3-T3] [expect-fail] Control M2, null-owner escape replaced by the pre-#781 context-reference throw: apply mutation M2 (Target Source G) to QuickFiler/Viewers/ItemViewer.Breadcrumb.cs, run `CMD-CENSUS` on that file, `CMD-BUILD-QF` with `TASKID` `p3-t3`, then `CMD-VSTEST` with `ASSEMBLY-QF`, `FILTER-NULLOWNER`, `NAMES-FOUR`, `TASKID` `p3-t3`. Write FEATURE/evidence/regression-testing/mutation-null-owner-escape.md (fixed name) with the same field set and `## Mutated run` content as P3-T1 (mutated file QuickFiler/Viewers/ItemViewer.Breadcrumb.cs, hunk lines 435 to 438, `TOKEN System.Threading.SynchronizationContext.Current = 1`), `EXIT_CODE:` scoped to the mutated run, `ExpectedExitCode: 1`. Predicted failure (D-6): the test `Failed` at the `captured` null assertion with a `MESSAGE` line containing `InvalidOperationException` and `but found`; this is the discrimination the old `Task.Run` shape had only when the work item was not inlined. Acceptance, all five required: the census value is 1; `MSBUILD_EXIT_CODE: 0` and `PROD_CSC_OUT_LINES:` at least 1; `total` 1, `failed` 1; the `MESSAGE` line contains both `InvalidOperationException` and `but found`; `RUNSETTINGS-HASH-NOW:` equals `RUNSETTINGS-HASH:`. A passing run, or a `MESSAGE` naming `NullReferenceException`, is `MUTATION PREDICTION MISMATCH`: stop and report. + +- [x] [P3-T4] Revert control M2 and confirm, with the P3-T2 mechanics applied to QuickFiler/Viewers/ItemViewer.Breadcrumb.cs (`git checkout`, anchored `git diff --exit-code HEAD`, scoped porcelain, `CMD-CENSUS`, `CMD-BUILD-QF` with `TASKID` `p3-t4`, `CMD-VSTEST` with `ASSEMBLY-QF`, `FILTER-NULLOWNER`, `NAMES-FOUR`, `TASKID` `p3-t4`), appending `## Revert and confirming run` to FEATURE/evidence/regression-testing/mutation-null-owner-escape.md, including the census `SHA256:` line beside the P0-T12 `SHA256` of the same file. Acceptance, all six required: `REVERT-DIFF-EXIT: 0`; the porcelain output is empty; `TOKEN System.Threading.SynchronizationContext.Current = 0`; the post-revert SHA256 equals the P0-T12 SHA256 of the same file; `PROD_CSC_OUT_LINES:` at least 1; `CONFIRMING-RUN-EXIT: 0` with `total` 1, `passed` 1 and the `RESULT` line `Passed`. + +- [x] [P3-T5] [expect-fail] Control M3, precondition run inline: apply mutation M3 (Target Source G) to `QuickFiler.Test/TestSupport/DedicatedWorkerThread.cs`, run `CMD-CENSUS` on it, `CMD-BUILD-QF` with `TASKID` `p3-t5`, then `CMD-VSTEST` with `ASSEMBLY-QF`, `FILTER-FOUR`, `NAMES-FOUR`, `TASKID` `p3-t5`. Write FEATURE/evidence/regression-testing/mutation-inline-precondition.md (fixed name) with the P3-T1 field set (mutated file `QuickFiler.Test/TestSupport/DedicatedWorkerThread.cs`, hunk: `action();` inserted before `Exception captured = null;`, `TOKEN action(); = 2`, `CSC_OUT_LINES:` instead of the production line count), `EXIT_CODE:` scoped to the mutated run, `ExpectedExitCode: 1`. Predicted failure (D-6): all four tests `Failed` at their in-thread distinctness precondition, each `MESSAGE` line containing `dedicated worker thread must not be`. Acceptance, all five required: `TOKEN action(); = 2`; `MSBUILD_EXIT_CODE: 0` and `CSC_OUT_LINES:` at least 1; `total` 4, `failed` 4; each of the four `MESSAGE` lines contains `dedicated worker thread must not be`; `RUNSETTINGS-HASH-NOW:` equals `RUNSETTINGS-HASH:`. Any passing test is `MUTATION PREDICTION MISMATCH`: stop and report. + +- [x] [P3-T6] Revert control M3 and confirm, with the P3-T2 mechanics applied to `QuickFiler.Test/TestSupport/DedicatedWorkerThread.cs` (`git checkout`, anchored `git diff --exit-code HEAD`, scoped porcelain, `CMD-CENSUS`, SHA-256 equality with `FIX-HASH-HELPER:`, `CMD-BUILD-QF` with `TASKID` `p3-t6`, `CMD-VSTEST` with `ASSEMBLY-QF`, `FILTER-FOUR`, `NAMES-FOUR`, `TASKID` `p3-t6`), appending `## Revert and confirming run` to FEATURE/evidence/regression-testing/mutation-inline-precondition.md. Acceptance, all five required: `REVERT-DIFF-EXIT: 0`; the porcelain output is empty; `TOKEN action(); = 1` and the SHA-256 equals `FIX-HASH-HELPER:` (the post-restore anchor of P2-T10, fact 23: the checkout writes the CRLF ending the anchor was taken over); `CSC_OUT_LINES:` at least 1 with `DLL_ADVANCED: True`; `CONFIRMING-RUN-EXIT: 0` with `total` 4, `passed` 4 and all four `RESULT` lines `Passed`. + +- [x] [P3-T7] [expect-fail] Control M4, OpenRead sentinel replaced: apply mutation M4 (Target Source G) to `UtilitiesCS.Test/HelperClasses/FileInfoWrapper_Tests.cs`, run `CMD-CENSUS` on it, `CMD-BUILD-UCS` with `TASKID` `p3-t7`, then `CMD-VSTEST` with `ASSEMBLY-UCS`, `FILTER-OPENREAD`, `NAMES-FIW`, `TASKID` `p3-t7`. Write FEATURE/evidence/regression-testing/mutation-openread-sentinel.md (fixed name) with the P3-T5 field set (mutated file `UtilitiesCS.Test/HelperClasses/FileInfoWrapper_Tests.cs`, hunk: the `decoy` declaration and the `.Returns(decoy)` change, `TOKEN .Returns(decoy) = 1`, `TOKEN Assembly.Location = 8`, `TOKEN BeSameAs(sentinel) = 1`), `EXIT_CODE:` scoped to the mutated run, `ExpectedExitCode: 1`. Predicted failure (D-6): the test `Failed` at the same-instance assertion with a `MESSAGE` line containing `to refer to`. Acceptance, all five required: the three census values hold; `MSBUILD_EXIT_CODE: 0` and `CSC_OUT_LINES:` at least 1; `total` 1, `failed` 1; the `MESSAGE` line contains `to refer to`; `RUNSETTINGS-HASH-NOW:` equals `RUNSETTINGS-HASH:`. A passing run is `MUTATION PREDICTION MISMATCH`: stop and report. + +- [x] [P3-T8] Revert control M4 and confirm, with the P3-T6 mechanics applied to `UtilitiesCS.Test/HelperClasses/FileInfoWrapper_Tests.cs` (`git checkout`, anchored `git diff --exit-code HEAD`, scoped porcelain, `CMD-CENSUS`, SHA-256 equality with `FIX-HASH-FIW:`, `CMD-BUILD-UCS` with `TASKID` `p3-t8`, `CMD-VSTEST` with `ASSEMBLY-UCS`, `FILTER-OPENREAD`, `NAMES-FIW`, `TASKID` `p3-t8`), appending `## Revert and confirming run` to FEATURE/evidence/regression-testing/mutation-openread-sentinel.md. Acceptance, all five required: `REVERT-DIFF-EXIT: 0`; the porcelain output is empty; `TOKEN .Returns(decoy) = 0`, `TOKEN Assembly.Location = 7` and the SHA-256 equals `FIX-HASH-FIW:` (the post-restore anchor of P2-T10, fact 23); `CSC_OUT_LINES:` at least 1 with `DLL_ADVANCED: True`; `CONFIRMING-RUN-EXIT: 0` with `total` 1, `passed` 1 and the `RESULT` line `Passed`. + +- [x] [P3-T9] Prove the tree is clean after the controls: run `git diff --exit-code HEAD -- QuickFiler QuickFiler.Test UtilitiesCS UtilitiesCS.Test`, `git status --porcelain -- QuickFiler QuickFiler.Test UtilitiesCS UtilitiesCS.Test`, and the SHA-256 of each of the six Write Set code files, and write FEATURE/evidence/regression-testing/p3-t9-post-control-clean-tree..md with `Timestamp:`, `Command:`, `EXIT_CODE:` (the `git diff --exit-code` exit) and an `Output Summary:` recording the porcelain output verbatim (`EMPTY` when nothing printed) and the six hashes beside their `FIX-HASH-` anchors. Acceptance, all three required: `EXIT_CODE: 0`; the porcelain output is empty; all six hashes equal their P2-T10 anchors (taken after that task's line-ending restore, fact 23; since then each Write Set file has been written only by the controls' Edit-then-`git checkout` cycles, which end in the anchored CRLF form). This is the mechanical proof, for AC11 through AC15, that no temporary production edit and no control residue survives into Phase 4. + +### Phase 4 — Parallel-Suite Runs, Toolchain Loop, Coverage Comparison, Acceptance Check-Off and Commit + +The loop is P4-T1 through P4-T7 in order. When P4-T1 rewrites a Write Set file, the loop restarts from P4-T1 and P4-T10 records the iteration count; that formatter output is the only change to a Write Set file this phase admits. When any other task in the loop fails its acceptance, the executor reports the stop condition the task names and stops. No task in this phase edits any file outside the Write Set. An edit to a Write Set `.cs` file in Phase 4, other than the P4-T1 formatter output, is `WRITE SET CHANGED AFTER CONTROLS`: stop and request a plan revision, because the four negative controls of Phase 3 observed the committed P2-T10 state and an edited file would carry no fail-before evidence. + +- [x] [P4-T1] Run the repository-wide formatter `dotnet tool run csharpier format .` from the worktree root inside one payload that also captures, immediately before and immediately after the command, the SHA-256 of each of the five Write Set `.cs` files and the SHA-256 of the text printed by `git diff MERGE-BASE -- QuickFiler.Test UtilitiesCS.Test` (the anchored patch, because the formatter can rewrite a file the plan already changed without changing a name list), then runs `git status --porcelain -- QuickFiler QuickFiler.Test UtilitiesCS UtilitiesCS.Test`. Write FEATURE/evidence/qa-gates/p4-t1-csharpier-format..md with `Timestamp:`, `Command:`, `EXIT_CODE:` and an `Output Summary:` recording the tool's `Formatted N files in Xms.` line verbatim (a processed-file count, not a rewrite count), the ten file hashes, each labelled with its file and as taken before or after the command (the five after-command values are the per-file after-format hashes P4-T9 compares against), `REWRITTEN:` (the number of the five whose hash changed), `FORMAT_CHANGED_TREE:` (whether the two patch hashes differ) and the porcelain output verbatim. Acceptance, all three required: `EXIT_CODE: 0`; `FORMAT_CHANGED_TREE:` and `REWRITTEN:` are recorded; the porcelain output lists only paths in the Write Set (any other path was clean at P0-T6 and cannot have been rewritten by a correct pinned formatter: `FORMAT SCOPE BREACH`, stop and report). When `REWRITTEN:` is greater than 0 the loop restarts from this task after `CMD-CENSUS` confirms the `post` token table still holds for the rewritten files; a second consecutive iteration with `REWRITTEN:` greater than 0 is `FORMAT NOT IDEMPOTENT`: stop and report. A line-ending rewrite by the formatter is formatter output under the Phase 4 preamble and is observed through `REWRITTEN:` like any other rewrite; fact 23 predicts none, because P2-T10 leaves every Write Set `.cs` file at the CRLF ending .editorconfig assigns to `.cs` files, and the prediction is recorded as met or not met by this task's value, never assumed. + +- [x] [P4-T2] Run `dotnet tool run csharpier check .` from the worktree root and write FEATURE/evidence/qa-gates/p4-t2-csharpier-check..md with `Timestamp:`, `Command:`, `EXIT_CODE:` and an `Output Summary:` quoting the `Checked N files in Xms.` line verbatim and recording `CHECKED-FILES:` and `CHECKED-DELTA:` as that N minus the P0-T6 `CHECKED-FILES:` value. Acceptance, both required: `EXIT_CODE: 0`; `CHECKED-DELTA:` is exactly 2 (this plan adds two `.cs` files and removes none; the project file is excluded by .csharpierignore). A non-zero exit restarts the loop from P4-T1. + +- [x] [P4-T3] Run the analyzer gate with `CMD-REBUILD` using the analyzer `GATEARGS` and `TASKID` `p4-t3` and write FEATURE/evidence/qa-gates/p4-t3-msbuild-analyzers..md with the P0-T7 field set plus `WARNINGS-DELTA:` (this run's `WARNINGS:` minus `ANALYZE-BASELINE-WARNINGS:`, an observation). Acceptance, all five required (D-8): `EXIT_CODE: 0`; `ERRORS: 0`; `SKIP_CORECOMPILE_LINES: 0`; `QF_TEST_CSC_OUT_LINES:` and `UCS_TEST_CSC_OUT_LINES:` each at least 1; `WRITESET_DIAGNOSTIC_LINES: 0`. A diagnostic naming a Write Set file is not repaired in this phase (Phase 4 preamble, `WRITE SET CHANGED AFTER CONTROLS`): stop and request a plan revision quoting the diagnostic; any other failure is reported and stops. + +- [x] [P4-T4] Run the nullable gate with `CMD-REBUILD` using the nullable `GATEARGS` and `TASKID` `p4-t4` (no Nullable property override, no incremental Build target) and write FEATURE/evidence/qa-gates/p4-t4-msbuild-nullable..md with the P0-T8 field set plus `WARNINGS-DELTA:` against `NULLABLE-BASELINE-WARNINGS:`. Acceptance, all five required: `EXIT_CODE: 0`; `ERRORS: 0`; `SKIP_CORECOMPILE_LINES: 0`; both `_CSC_OUT_LINES:` at least 1; `WRITESET_DIAGNOSTIC_LINES: 0`. Same restart rule as P4-T3. + +- [x] [P4-T5] Run the full QuickFiler.Test assembly in the parallel regime (the measured run for AC1, AC2, AC4, AC6 and the QuickFiler half of AC16) with `CMD-VSTEST` using `ASSEMBLY-QF`, empty `FILTERARG`, `NAMES-QF` and `TASKID` `p4-t5`, and write FEATURE/evidence/regression-testing/parallel-suite-quickfiler-test.md (fixed name) with `Timestamp:`, `Command:` (naming TaskMaster.runsettings, the isolation switch and `no test-case filter`), `EXIT_CODE:` and an `Output Summary:` recording `RUNSETTINGS-HASH-NOW:`, the `COUNTERS` line, `RESULT_COUNT:`, `SEQUENCE_FILES:`, `COLLECTOR_LINES:`, every `RESULT` line (the eight `NAMES-QF` names), every `MESSAGE` line, and `NEWLY-FAILING:` (names in this run's failed set that are not in `BASELINE-FAILED-QF:`, or `NONE`). Acceptance, all six required: `EXIT_CODE: 0`; `failed` is 0 and `executed` is at least the P0-T10 QuickFiler `executed` value; `SEQUENCE_FILES: 0`; the eight `RESULT` lines are present and each reads `Passed` (this is the proof that the Part2 file and the helper compiled and were discovered, AC6); `RUNSETTINGS-HASH-NOW:` equals `RUNSETTINGS-HASH:`; `NEWLY-FAILING: NONE`. A failing test in a Write Set file is not repaired in this phase (Phase 4 preamble, `WRITE SET CHANGED AFTER CONTROLS`): stop and request a plan revision with the `MESSAGE` line; any other failure, including a failure of `TryAddValuesAsync_UpdatesExistingValue` (D-12; spec AC16 requires zero failed tests and admits no re-run), is `AC16: NOT MET` by design, reported with the `MESSAGE` lines, and the run stops. + +- [x] [P4-T6] Run the full UtilitiesCS.Test assembly in the parallel regime (the measured run for AC8, AC9, AC10 and the UtilitiesCS half of AC16) with `CMD-VSTEST` using `ASSEMBLY-UCS`, `UCS-FILTERARG` (P0-T9), `NAMES-FIW` and `TASKID` `p4-t6`, and write FEATURE/evidence/regression-testing/parallel-suite-utilitiescs-test.md (fixed name) with the P4-T5 field set; `Command:` records the filter verbatim (or `no test-case filter`) and, when the filter is present, the four excluded class names and the sentence that the stall is pre-existing on main and covered by CI. Acceptance, all six required: `EXIT_CODE: 0`; `failed` 0 and `executed` at least 1; `SEQUENCE_FILES: 0`; the eight `NAMES-FIW` `RESULT` lines are present and each reads `Passed`; `RUNSETTINGS-HASH-NOW:` equals `RUNSETTINGS-HASH:`; `NEWLY-FAILING: NONE`. Same failure handling as P4-T5: in particular a failure of `DictionaryExtensions_Tests.TryAddValuesAsync_UpdatesExistingValue`, which lives in this assembly, is `AC16: NOT MET` by design (D-12), recorded with its `MESSAGE` line, and the run stops without re-running. + +- [x] [P4-T7] Run the final repository-wide test and coverage pass by the route P0-T9 fixed (`CMD-COVERAGE-RUNNER` or `CMD-COVERAGE-DIRECT` with `STAGE` `final`, then, unless branch (d0) below applies, `CMD-COVERAGE-POST` with `STAGE` `final`, `RAW` being the value the Command Reference rule derives from the run: `True` under `DIRECT`; under `RUNNER` `True` when `COLLECT_FAILURE_MESSAGE:` is non-empty, else `False`; recorded as `RAW:`) and write FEATURE/evidence/qa-gates/coverage-final.md (fixed name) with the P0-T11 field set, plus `NEWLY-FAILING:` (names in `FAILED-SET:` not in the P0-T11 `FAILED-SET:`, or `NONE`). Under `RUNNER` a run still in progress after 120 minutes is `COVERAGE RUN STALLED`: stop and report. Branches, checked in the order (d0), (c), (b), (a), (d): (d0), checked first: a run with `SEQUENCE_FILES:` greater than 0 (DIRECT) or with no stage TRX at coverage\final-931.trx (the run payload's `TRX_PRESENT:` value under both routes) is `COVERAGE RUN ABORTED`: stop and report `COLLECT_FAILURE_MESSAGE:` or the last lines of the collector log with absolute paths replaced, do not run `CMD-COVERAGE-POST` (its first statement reads the stage TRX), and do not re-run; (a) exit 0 with `FAILED-SET:` empty and both floors met completes; (b) a non-empty `FAILED-SET:` naming a test in a Write Set file is not repaired in this phase (Phase 4 preamble, `WRITE SET CHANGED AFTER CONTROLS`): stop and request a plan revision with the `Failed tests:` summary line; a non-empty `FAILED-SET:` naming any other test, including `TryAddValuesAsync_UpdatesExistingValue` (D-12: no re-run), is `AC17: NOT MET`: stop and report with the `Failed tests:` summary line; (c) a floor failure under either route (a runner `THRESHOLD_MESSAGE:`, or, whenever `RAW:` is `True` (every `DIRECT` run, and a `RUNNER` run whose `COLLECT_FAILURE_MESSAGE:` is non-empty), a `LINE-FLOOR: NOT MET` or `BRANCH-FLOOR: NOT MET` line printed by `CMD-COVERAGE-POST`) is `AC17: NOT MET`: run `CMD-COVERAGE-POST` if it has not already run so the projection is recorded, record `FLOOR: NOT MET` with the message, and stop and report; (d) any outcome that (c), (b) and (a) do not decide, in particular a non-zero exit with an empty `FAILED-SET:`, is `COVERAGE RUN ABORTED` with the (d0) handling. Acceptance, all eight required: `EXIT_CODE: 0`; under `COVERAGE-ROUTE: DIRECT`, `SEQUENCE_FILES: 0`; the projection block contains the `UtilitiesCS` and `QuickFiler` package elements with `LINE` and `BRANCH` counters; the `First-party coverage:` line is present; the summary block's first line begins `Test run outcome:` and `FAILED-SET:` is empty on the completing run; `NEWLY-FAILING: NONE`; `LINE-FLOOR: MET` and `BRANCH-FLOOR: MET` as printed by `CMD-COVERAGE-POST` under either route (under `RUNNER` the runner's exit 0 has already asserted both floors and `THRESHOLD_MESSAGE:` is empty); the artifact contains no absolute path. coverage\final-931.jacoco.xml remains on disk for P4-T8. + +- [x] [P4-T8] Compare the final coverage projection against the baseline (AC18) by running `CMD-PACKAGE-COMPARE` and append to FEATURE/evidence/qa-gates/coverage-final.md a section `## Comparison against coverage-baseline.md` carrying the four `PACKAGE` lines verbatim, `BASELINE-FIRST-PARTY:` and `FINAL-FIRST-PARTY:` (the two `First-party coverage:` lines), and exactly one `COMPARABILITY:` line: `A` when the two first-party `lines` denominators differ by at most 1 percent of the baseline denominator, else `B` with the one-sentence note that the repository-wide totals were measured over different merged denominators and are recorded, not gated (D-7); and `CHANGED-PRODUCTION-LINES: 0` with the sentence that the P2-T10 `git show` path list contains no production path, so every changed line of this item is test code outside the coverage denominator and the policy's no-regression-on-changed-lines clause has an empty subject. Acceptance, all four required: no `PACKAGE` line reads `MISSING`; all four `PACKAGE` lines read `NOT-LOWER=True`; exactly one `COMPARABILITY:` line is present; `CHANGED-PRODUCTION-LINES: 0` is present and the P2-T10 path list it cites contains no path under QuickFiler/ or UtilitiesCS/. When a `PACKAGE` line reads `NOT-LOWER=False`, run P4-T7 once more as a second measurement (identical command, recorded as `MEASUREMENT: 2` in coverage-final.md) and re-run this comparison; a second `False` is `AC18: NOT MET`: stop and report with both projections quoted. + +- [x] [P4-T9] Audit file size and the token table after the repository-wide format by running `CMD-CENSUS` on each of the five Write Set `.cs` files and `QuickFiler.Test/QuickFiler.Test.csproj`, and write FEATURE/evidence/qa-gates/p4-t9-post-format-census..md transcribing every `TOKEN`, `LINES` and `SHA256` line per file. Acceptance, all three required: every `LINES` value for the five `.cs` files is at most 500 (AC5; a larger value is `FILE SIZE LIMIT EXCEEDED`: stop and report rather than shortening remarks); every `post` value of the Token Census Expectations table holds, including `action();` 1 in HELPER, `.Returns(decoy)` 0 and `Assembly.Location` 7 in FIW, INCLUDE-PART2 1 and INCLUDE-HELPER 1 (no control residue); each `.cs` `SHA256` equals the after-format hash that the final P4-T1 iteration recorded for that file; when every P4-T1 iteration of this run recorded `REWRITTEN: 0` (the loop started from P4-T1 exactly once), that value also equals the file's `FIX-HASH-` anchor (the post-restore P2-T10 value, fact 23), and otherwise the artifact records it under `POST-FORMAT-HASH-:` naming the iteration in which the rewrite occurred. + +- [x] [P4-T10] Close the toolchain loop and write FEATURE/evidence/qa-gates/toolchain-pass.md (fixed name) with `Timestamp:`, `Command:` (`reconciliation of P4-T1 through P4-T9`), `EXIT_CODE: 0` (scoped to the reconciliation), and an `Output Summary:` carrying one line per toolchain command of the final clean iteration in CLAUDE.md order, each naming the command, its exit code and its artifact: `dotnet tool run csharpier check .` (P4-T2), the analyzer rebuild (P4-T3) with its `SKIP_CORECOMPILE_LINES:` value, the nullable rebuild (P4-T4) with its `SKIP_CORECOMPILE_LINES:` value, and the MSTest-with-coverage route (P4-T7) with `COVERAGE-ROUTE:`; then the two parallel-suite runs (P4-T5, P4-T6); `ITERATIONS:` (the number of times the loop started from P4-T1); one `EXPECTATION-MET:` line per task P4-T1 through P4-T9; and `LOOP: CLEAN PASS`. Acceptance, all four required: the four toolchain lines each record exit 0; both `SKIP_CORECOMPILE_LINES:` values are 0; nine `EXPECTATION-MET:` lines each read `YES`; `LOOP: CLEAN PASS` is present. If any expectation is not met because P4-T1 rewrote a file, the loop restarts from P4-T1 and this task is re-run after it (the only admissible restart, Phase 4 preamble); any other unmet expectation is the stop condition its task names; nothing is written until every expectation holds. + +- [x] [P4-T11] Verify the footprint and scope boundary (AC3, AC15, AC16's diff clause) and write FEATURE/evidence/qa-gates/p4-t11-scope-boundary..md. Run: `git diff --name-only MERGE-BASE` (working tree); `git status --porcelain --untracked-files=all`; `git diff --name-only MERGE-BASE -- QuickFiler.Test`; `git diff --name-only MERGE-BASE -- .claude`; `git diff --name-only MERGE-BASE..HEAD -- .claude config` (the committed diff of the two trees the executor never commits to); `git log --oneline --diff-filter=A MERGE-BASE..HEAD -- docs\features\potential` (backslash pathspec per the Commits convention); `git diff --exit-code MERGE-BASE -- QuickFiler UtilitiesCS TaskMaster.runsettings scripts\vscode\TaskMaster.cli.runsettings config`; `git diff -U0 MERGE-BASE -- QuickFiler.Test UtilitiesCS.Test` (issued inside `CMD-ADDED-LINES`); and the SHA-256 of TaskMaster.runsettings. Record the two full path lists verbatim; `INHERITED-AND-EXCLUDED:` (the paths removed by Clause A and Clause B, each listed with its clause); `THIS-ITEM-FOOTPRINT:` (the union of the first two lists minus those); `QUICKFILER-TEST-CHANGED:` (the third list); `CLAUDE-CHANGED:` (the fourth list); `COMMITTED-CLAUDE-OR-CONFIG:` (the fifth list, or `NONE`); `INHERITED-PROMOTION-RECORD:` (the `git log` output verbatim: the promoted record docs/features/potential/promoted/2026-09-28-tests-depend-on-uncontrolled-environment.md was committed on this branch deliberately before execution, lies in `INHERITED-CLAUSE-A:` because it is in the committed MERGE-BASE...HEAD diff, and is subtracted from `THIS-ITEM-FOOTPRINT:` under Clause A); `PRODUCTION-AND-CONFIG-DIFF-EXIT:`; over the added lines (single leading `+`) of the `-U0` diff, computed by `CMD-ADDED-LINES` and transcribed with its `ADDED-LINE-COUNT:`, `ADDED-DONOTPARALLELIZE:`, `ADDED-THREAD-SLEEP:`, `ADDED-TASK-DELAY:`, `ADDED-TIMEOUT:` (lines containing `[Timeout` or `Timeout=`), `ADDED-RETRY:` (lines containing `Retry`), `ADDED-WORKERS:` (lines containing the XML element form, the text Workers enclosed in angle brackets as an element tag, or the text `Workers =`; defined this way because the two remark lines moved verbatim into the Part2 file, pre-edit lines 210 and 266, contain the text Workers=0 inside a code element, which matches neither form, and a runsettings edit would carry the element form); `ADDED-SCOPE:` (lines containing the text Scope enclosed in angle brackets as an element tag, or the text `ExecutionScope`; the `ViewerScope` identifier in the moved tests matches neither); every `ADDED-` count is case-sensitive and counts lines; and `RUNSETTINGS-HASH-NOW:`. Acceptance, all nine required: `THIS-ITEM-FOOTPRINT:` is exactly the six Write Set code paths plus paths under docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/ and nothing else; `QUICKFILER-TEST-CHANGED:` is exactly the four QuickFiler.Test Write Set paths, so none of the eight files AC3 names and neither `[DoNotParallelize]` carrier changed; every path in `CLAUDE-CHANGED:` begins .claude/agent-memory/ (Clause B; agent memory writes are not this item's footprint); `COMMITTED-CLAUDE-OR-CONFIG: NONE`; `INHERITED-PROMOTION-RECORD:` is recorded; `PRODUCTION-AND-CONFIG-DIFF-EXIT: 0`; the seven `ADDED-` counts are all 0 over an `ADDED-LINE-COUNT:` of at least 1; `RUNSETTINGS-HASH-NOW:` equals `RUNSETTINGS-HASH:`; the porcelain span is present as the name-listing diff's companion. + +- [x] [P4-T12] Sweep the feature folder for host identifiers and raw documents (AC19) by running `CMD-SWEEP` and write FEATURE/evidence/qa-gates/p4-t12-hygiene-sweep..md with `Timestamp:`, `Command:`, `EXIT_CODE:` and an `Output Summary:` recording `FILES:`, `ACCOUNT-TOKEN-MATCHES:`, `PROFILE-LEAF-MATCHES:`, `MACHINE-TOKEN-MATCHES:`, `WORKTREE-ROOT-MATCHES:`, `USERS-PATH-MATCHES:` and `RAW-DOCUMENT-FILES:`; the tokens themselves are never written. Also run `git diff --name-only MERGE-BASE` with `git status --porcelain --untracked-files=all` and record `RAW-EXTENSION-PATHS:` as every listed path ending `.trx`, `.xml` or `.coverage` outside `INHERITED-CLAUSE-A:`, or `NONE`. Acceptance, all three required: the six match counts are 0; `RAW-DOCUMENT-FILES: 0`; `RAW-EXTENSION-PATHS: NONE`. A non-zero count is repaired by replacing the offending text with ``, ``, `` or `` in the artifact that carries it (never by deleting the artifact), or by removing a raw document from the feature folder, and the sweep is re-run until every count is 0. + +- [x] [P4-T13] Write the follow-up handoff record FEATURE/evidence/qa-gates/p4-t13-follow-up-handoff..md for the orchestrator, who files the potential entries through the promotion lifecycle's MCP tool (D-9; the executor creates nothing under docs/features/potential/). The record carries `Timestamp:` and, for each of the four follow-ups in the spec's Rollout list, a `short_name`, a one-paragraph body with the spec's file and line citations, and the sentence `not fixed under #931`: (1) `physicalfilesystemadapters-tests-open-repository-solution-file` (the same `GetSolutionFile` pattern, the swallowed `IOException` catches and the real solution-file opens in UtilitiesCS.Test/HelperClasses/PhysicalFileSystemAdapters_Tests.cs); (2) `directoryinfowrapper-tests-enumerate-repository-solution-file` (UtilitiesCS.Test/HelperClasses/DirectoryInfoWrapper_Tests.cs lines 60, 79 and 381); (3) `breadcrumb-dispatchvalue-message-wording-broader-than-mechanism` (QuickFiler.Test/Viewers/BreadcrumbUiThreadDispatchTests.cs line 305, wording only); (4) `issue-900-handoff-misattributes-dispatchvalue-site-to-owner-thread-id-check` (documentation correction to the #900 follow-up handoff record). Acceptance, all three required: the record exists; it names the four `short_name` values exactly once each; it contains the literal `not fixed under #931` at least four times. + +Check-off protocol for P4-T14 through P4-T32: each task changes exactly one line of `docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/spec.md`, the line beginning `- [ ] ACn. `, to begin `- [x] ACn. `, preserving the criterion text; its acceptance is that exactly one line begins `- [x] ACn. `, no line begins `- [ ] ACn. `, and no other AC line changed in that task. When the named evidence does not hold, the box is left unchecked and `ACn: NOT MET` is recorded with the failing value in FEATURE/evidence/qa-gates/p4-t-ac-checkoff..md; the task completes in either case. + +- [x] [P4-T14] Check off AC1 in FEATURE/spec.md. Evidence: P4-T9 census of BND (`Task.Run(` 0, `.GetAwaiter()` 3 down from 4, `DedicatedWorkerThread.Run(` 1, `ownerThreadId` 2, `.NotBe(` 1, `.BeNull(` 1, `executions.Should().Be(0)` 1, `cannot marshal` 1), P3-T1 (fails at the executions assertion when the guard is disabled) and P4-T5 (`Dispatcher_OwnerOnlyWorker_ReportsWithoutRunningAction` Passed). + +- [x] [P4-T15] Check off AC2 in FEATURE/spec.md. Evidence: P4-T9 census of PART2 (`Task.Run(` 0, `.GetAwaiter()` 0, `DedicatedWorkerThread.Run(` 3, `owner.CheckAccess()` 1, `.BeNull(` 1, `unconditionally` 1), P3-T3 (fails at the captured null assertion under the restored context-reference guard) and P4-T5 (`InitializeBreadcrumbPipeline_NullOwningDispatcher_DoesNotThrow` Passed). + +- [x] [P4-T16] Check off AC3 in FEATURE/spec.md. Evidence: P4-T11 (`QUICKFILER-TEST-CHANGED:` is exactly the four QuickFiler.Test Write Set paths; `ADDED-DONOTPARALLELIZE: 0`). + +- [x] [P4-T17] Check off AC4 in FEATURE/spec.md. Evidence: P4-T9 census of AFF (`partial class` 1, `[TestMethod]` 4, `RunOnDedicatedWorkerThread` 0, `ClearViewerDispatcher(` 0) and PART2 (`partial class` 1, `[TestMethod]` 3, `ClearViewerDispatcher(` 2, `RunOnDedicatedWorkerThread` 0), and P4-T5 (the seven `NAMES-AFFINITY` names present and Passed, the same seven P0-T10 recorded, so no test was renamed or removed). + +- [x] [P4-T18] Check off AC5 in FEATURE/spec.md. Evidence: P4-T9 (`LINES` at most 500 for each of the five `.cs` files, measured after the repository-wide format). + +- [x] [P4-T19] Check off AC6 in FEATURE/spec.md. Evidence: P4-T9 census of CSPROJ (INCLUDE-PART2 1, INCLUDE-HELPER 1) and P4-T5 (the four named tests present and Passed in the committed QuickFiler.Test projection). + +- [x] [P4-T20] Check off AC7 in FEATURE/spec.md. Evidence: P4-T9 census of HELPER (`namespace QuickFiler.Test.TestSupport` 1, `internal static class DedicatedWorkerThread` 1, `internal static Exception Run(Action action)` 1, `new Thread(` 1, `IsBackground = true` 1, `thread.Join();` 1, `Join(` equals `Join()`, `distinct from every live thread by construction` 1, `.Should()` 0, `Thread.Sleep` 0, `Task.Delay` 0, `[Timeout` 0, `Retry` 0), the `DedicatedWorkerThread.Run(` counts 3 (PART2) plus 1 (BND), and P3-T5 (all four tests fail together when the helper runs the delegate inline). + +- [x] [P4-T21] Check off AC8 in FEATURE/spec.md. Evidence: P4-T9 census of FIW (`GetSolutionFile` 0, `TaskMaster.sln` 0, `AppDomain` 0, `File.Create` 0, `File.WriteAll` 0, `File.Delete` 0, `Path.GetTemp` 0, `FileMode.` equal to `FileMode.Open`, `FileAccess.` equal to `FileAccess.Read`) and P4-T6 (the four named tests present and Passed). + +- [x] [P4-T22] Check off AC9 in FEATURE/spec.md. Evidence: P4-T9 census of FIW (`using var sentinel = new FileStream(` 1, `Assembly.Location` 7, `FileShare.ReadWrite` 9, `BeSameAs(sentinel)` 1, `stream.CanRead.Should().BeTrue()` 1, `stream.Length.Should().BeGreaterThan(0)` 1), P3-T7 (fails at the same-instance assertion under a second stream) and P4-T6 (`OpenRead_ShouldReturnReadableStreamForWrappedFile` Passed). + +- [x] [P4-T23] Check off AC10 in FEATURE/spec.md. Evidence: P2-T6 (`ADDED-FIXTUREPATH: 4`, `ADDED-LENGTH: 0` under its alignment-independent definition, `ADDED-OPENREAD: 2`, `ADDED-OPEN-CREATE-WRITE: 0`, so the three metadata tests use the rooted literal, open no stream and assert none of the excluded members) and P4-T6 (the three tests Passed). + +- [x] [P4-T24] Check off AC11 in FEATURE/spec.md. Evidence: FEATURE/evidence/regression-testing/mutation-owner-only-dispatcher-guard.md (P3-T1 mutated run failing on `to be 0, but found 1` with the mutated file and hunk, the filter, the runsettings name and the isolation switch recorded; P3-T2 revert with `REVERT-DIFF-EXIT: 0`, empty porcelain and the confirming pass). + +- [x] [P4-T25] Check off AC12 in FEATURE/spec.md. Evidence: FEATURE/evidence/regression-testing/mutation-null-owner-escape.md (P3-T3 and P3-T4, with the same recorded fields). + +- [x] [P4-T26] Check off AC13 in FEATURE/spec.md. Evidence: FEATURE/evidence/regression-testing/mutation-inline-precondition.md (P3-T5: all four `MESSAGE` lines carry the precondition reason; P3-T6 revert and confirming pass). + +- [x] [P4-T27] Check off AC14 in FEATURE/spec.md. Evidence: FEATURE/evidence/regression-testing/mutation-openread-sentinel.md (P3-T7 failing on `to refer to`; P3-T8 revert and confirming pass). + +- [x] [P4-T28] Check off AC15 in FEATURE/spec.md. Evidence: P4-T11 (`THIS-ITEM-FOOTPRINT:` equals the six Write Set code paths plus feature-folder documents; `PRODUCTION-AND-CONFIG-DIFF-EXIT: 0`; every `CLAUDE-CHANGED:` path is agent memory outside this item's footprint; `COMMITTED-CLAUDE-OR-CONFIG: NONE`, so no commit on this branch touched .claude or config; `INHERITED-PROMOTION-RECORD:` names the deliberately pre-committed promoted record that Clause A subtracts) and P3-T9 (no control residue). + +- [x] [P4-T29] Check off AC16 in FEATURE/spec.md. Evidence: FEATURE/evidence/regression-testing/parallel-suite-quickfiler-test.md and FEATURE/evidence/regression-testing/parallel-suite-utilitiescs-test.md (P4-T5 and P4-T6: exit 0, `failed` 0, the runsettings file, the isolation switch and the filter recorded verbatim, `RUNSETTINGS-HASH-NOW:` unchanged) and P4-T11 (the seven `ADDED-` counts 0). + +- [x] [P4-T30] Check off AC17 in FEATURE/spec.md. Evidence: FEATURE/evidence/qa-gates/toolchain-pass.md (P4-T10: `LOOP: CLEAN PASS`, four toolchain commands at exit 0, both `SKIP_CORECOMPILE_LINES:` 0). + +- [x] [P4-T31] Check off AC18 in FEATURE/spec.md. Evidence: FEATURE/evidence/baseline/coverage-baseline.md and FEATURE/evidence/qa-gates/coverage-final.md (P0-T11, P4-T7 and P4-T8: both projections with the `First-party coverage:` line, four `PACKAGE` lines `NOT-LOWER=True`). + +- [x] [P4-T32] Check off AC19 in FEATURE/spec.md. Evidence: P4-T12 (six match counts 0, `RAW-DOCUMENT-FILES: 0`, `RAW-EXTENSION-PATHS: NONE`). + +- [x] [P4-T33] Commit the final state, write the closure record, and leave the source and feature trees clean. Steps, in order: (1) run `CMD-SWEEP` once more and stop with `HYGIENE SWEEP FAILED` if any count is non-zero; (2) run `git add -- QuickFiler.Test UtilitiesCS.Test docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931` then `git commit -m "docs(931): evidence, acceptance check-off and plan state for the uncontrolled-environment test fix"` (with, when the session requires an attribution trailer, a second `-m` paragraph in the bare-address form the Commits convention fixes; no angle bracket, dollar sign or backtick anywhere on the line), recording `COMMIT-1-EXIT:`; (3) write FEATURE/evidence/qa-gates/p4-t33-closure..md with `Timestamp:`, `Command:` (the git spans of this task), `COMMIT-1-EXIT:`, `HEAD-AFTER-COMMIT:` (`git rev-parse HEAD`), the confirming footprint lists `git diff --name-only MERGE-BASE..HEAD -- QuickFiler QuickFiler.Test UtilitiesCS UtilitiesCS.Test` and `git diff --name-only origin/main...HEAD -- QuickFiler QuickFiler.Test UtilitiesCS UtilitiesCS.Test` (each must list exactly the six Write Set code paths; the step-2 `git add` span is their companion), the pointer to P4-T13, and the acceptance-criteria status summary in the exact form the acceptance-criteria-tracking skill requires (`Source:` FEATURE/spec.md, `Total AC items: 19`, `Checked off (delivered): ` counted from the `- [x] AC` lines on disk, `Remaining (unchecked): <19 minus n>`, `Items remaining:` listing any unchecked criterion text or `none`); (4) tick every remaining unchecked checkbox in `docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/plan.2026-09-28T20-01.md`, including this task's own; (5) run `git add -- docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931` then `git commit --amend --no-edit`, recording `COMMIT-2-EXIT:` in the executor's final message (the artifact cannot record it); (6) run `git status --porcelain -- QuickFiler QuickFiler.Test UtilitiesCS UtilitiesCS.Test docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931` and `git rev-parse HEAD`; (7) append `EXIT_CODE:` (scoped to the step-6 porcelain span), `POST-AMEND-PORCELAIN:` (`EMPTY` or the lines verbatim) and `HEAD-BEFORE-FINAL-AMEND:` (the step-6 `git rev-parse HEAD` value, which the step-8 amend supersedes) to the closure record; (8) run `git add -- docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931` then `git commit --amend --no-edit` once more, recording `COMMIT-3-EXIT:` in the executor's final message, and write no file afterwards; (9) run `git rev-parse HEAD` and report its value as `FINAL-HEAD:` in the executor's final message only (no artifact can record the commit that contains it). Paths under .claude/agent-memory/ are outside every span above by pathspec and are not this task's residual. Acceptance, all five required: `COMMIT-1-EXIT: 0`; both confirming footprint lists are exactly the six Write Set code paths; the status summary reports `Total AC items: 19` and its `Checked off` count equals the number of `- [x] AC` lines in FEATURE/spec.md; `POST-AMEND-PORCELAIN: EMPTY`; the executor's final message reports the plan path, `FINAL-HEAD:` (from step 9), `COMMIT-2-EXIT:`, `COMMIT-3-EXIT:`, the status summary and the P4-T13 follow-up pointer. A PreToolUse refusal at any git span is `PRE-IMPLEMENTATION GATE BLOCKED`: stop and report the refusal text verbatim; do not alter hooks, checkpoints, permission configuration or another item's files. + diff --git a/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/policy-audit.2026-09-29T20-15.md b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/policy-audit.2026-09-29T20-15.md new file mode 100644 index 000000000..63ef8b418 --- /dev/null +++ b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/policy-audit.2026-09-29T20-15.md @@ -0,0 +1,278 @@ +# Policy Compliance Audit: tests-depend-on-uncontrolled-environment (Issue #931) + +- Component: QuickFiler.Test and UtilitiesCS.Test (test-only change; no production file changed) +- Branch: bug/tests-depend-on-uncontrolled-environment-931 +- Head under review: ce744bbba3db4701e070785fd782c8b45289f2da (origin/main c4ff0e2be0bc9c51acc43dacd2cc5954a448676c merged at 55a50e922 with no conflicts) +- Resolved base: origin/main (merge base for the pre-merge evidence: 177b6d78e1b2408e5aedbd794cef3aad6b7fb372; post-merge evidence measured at 55a50e922) +- Work mode: full-bug (spec.md is the sole acceptance-criteria source) +- Audit timestamp: 2026-09-29T20-15 +- Reviewer: feature-review agent (no-Bash run; evidence read from committed projections, the current worktree files, the git-ignored raw coverage documents left in the worktree, and the orchestrator-supplied git facts) +- Total blocking findings in this artifact: 0 +- Total non-blocking findings in this artifact: 8 + +## Executive Summary + +Verdict: PASS. Zero blocking findings. + +The branch rewrites two thread-identity tests and four file-handle tests so that their outcomes no longer depend on thread-pool scheduling or on which other process holds the repository solution file. It introduces one shared internal test helper (`DedicatedWorkerThread.Run`), splits a 490-line test class into two partial files (294 and 202 lines), and registers both new files in `QuickFiler.Test.csproj`. Six code files changed; every one is a test-project file. No file under `QuickFiler/`, `UtilitiesCS/`, `.claude/` or `config/` changed (orchestrator-supplied `git diff --name-only origin/main...HEAD`, corroborated by `evidence/qa-gates/p4-t11-scope-boundary.2026-09-29T09-44.md` and `p4-t33-closure.2026-09-29T09-47.md`). + +Gate results (pre-merge Phase 4 and post-merge re-run, both exit 0): csharpier check clean (1625 files); analyzer rebuild 0 errors / 0 warnings / 0 skipped CoreCompile; TreatWarningsAsErrors rebuild 0 errors / 0 warnings / 0 skipped CoreCompile; QuickFiler.Test 1468 then 1469 executed, 0 failed; UtilitiesCS.Test 4922 then 4924 executed, 0 failed; repository-wide coverage run 7320 then 7323 executed, 0 failed. C# first-party coverage 85.32% lines / 79.73% branches pre-merge and 85.31% lines / 79.73% branches post-merge, both above the 85% line and 75% branch floors: C# coverage verdict PASS. + +Four negative controls each show the rewritten test failing against a deliberately broken guard or sentinel and passing after the revert, with porcelain output proving no residual production change. Committed evidence is projections only; the feature folder contains no `.trx`, `.xml` or `.coverage` file and no absolute host path, account name or host name. + +Non-blocking findings: PA-1 spec AC15 wording omits the promotion-lifecycle record that the branch necessarily carries (disclosed by the plan and the footprint gate); PA-2 `quality-tiers.yml` absent at repo root (pre-existing); PA-3 coverage-floor documentation conflict (CLAUDE.md 80%/90% versus `.claude/rules` 85%/75%), both satisfied here; PA-4 post-merge UtilitiesCS package line rate 0.893789 is below the pre-merge baseline 0.893884, attributable to production edits origin/main contributed plus run-to-run collector variance, with zero production lines changed by this item; PA-5 canonical `artifacts/csharp/coverage.xml` not emitted in the worktree, evidence held as committed projections plus git-ignored raw documents; PA-6 PR-context artifacts absent in the worktree, scope taken from orchestrator-supplied git facts; PA-7 disclosed command substitutions (`/nodeReuse:false`, DIRECT coverage route); PA-8 repository test layout `.Test/` rather than `tests/` (pre-existing convention). + +## Rejected Scope Narrowing + +None detected. The caller's prompt supplied a code diff restricted to QuickFiler.Test and UtilitiesCS.Test together with the complete `git diff --name-only origin/main...HEAD` list (seven entries including the feature folder and the promoted potential record), and this audit covers the complete list. The caller's binding no-Bash directive constrains the verification method, not the audit scope; where it prevented a direct observation the check is recorded as UNVERIFIED with the reason (section 8). The caller's instruction not to raise the four unfiled follow-ups as a finding is a disposition instruction about work the spec explicitly places out of scope (spec `## Rollout & Follow-up`), not a narrowing of language or file coverage; it is honoured and recorded in section 8. + +## Evidence Location Compliance + +- Files in the branch diff under `artifacts/baselines/`, `artifacts/qa/`, `artifacts/evidence/` or `artifacts/coverage/`: none. The orchestrator-supplied name list contains no `artifacts/` path; a Glob of `artifacts/**` in the worktree returns nothing (the worktree has no `artifacts/` directory). +- All evidence written by execution lives under `docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/{baseline,regression-testing,qa-gates}/`, the canonical `/evidence//` layout. 43 evidence files pre-merge plus 8 post-merge files. +- `validate_evidence_locations.py --root .`: not run (no Bash in this run). Disposition by inspection: PASS. Recorded as UNVERIFIED-by-script in section 8. +- Canonical coverage artifact reconciliation: `artifacts/csharp/coverage.xml` is absent in the worktree. The committed coverage evidence is the CLAUDE.md-mandated projection form (`coverage-baseline.md`, `coverage-final.md`, `post-merge-coverage-final.md`), and the raw documents remain git-ignored under `coverage/` in the worktree (`baseline-931`, `final-931`, `postmerge-931`, `postmerge2-931` in both `.cobertura.xml` and `.jacoco.xml` forms). The reviewer read the four `.jacoco.xml` documents directly and confirmed each equals its committed projection counter for counter. Treated as artifact present (see finding PA-5). +- EVIDENCE_LOCATION_OVERRIDE_REJECTED: none (no caller instruction named a non-canonical evidence path). + +## 1. General Unit Test Policy Compliance + +### 1.1 Core principles + +| Principle | Verdict | Evidence | +|---|---|---| +| Independence | PASS | Each rewritten test constructs and disposes its own viewer, dispatcher or stream; no shared static state introduced (diff read in full). Full assemblies pass under Workers=0 / ClassLevel parallelism (`parallel-suite-quickfiler-test.md`, `parallel-suite-utilitiescs-test.md`, post-merge twins). | +| Isolation | PASS | Each test targets one guard or one wrapper member; the in-thread precondition assertions name the guard under test in their reason strings. | +| Fast execution | PASS | Each rewritten test starts and joins exactly one thread; the post-merge QuickFiler.Test run of 1469 tests and UtilitiesCS.Test run of 4924 tests completed in the recorded runs with no timeout. | +| Determinism | PASS | The defect being fixed is non-determinism. The distinct-thread precondition now holds by construction (a `Thread` the test creates is never the calling thread); the file test opens a read-shared handle on the test host's own loaded image, which no other process can deny. Negative control M3 (`mutation-inline-precondition.md`) proves the preconditions are live. | +| Readability | PASS | Every rewritten test carries a `` and `` explaining the mechanism and the issue reference; reason strings are explicit. | + +### 1.2 Coverage requirements + +Languages with changed files on the branch: C# only (`.cs` x 5 and `.csproj` x 1). TypeScript, Python and PowerShell have zero changed files. + +### Coverage Evidence Checklist + +- C# baseline coverage artifact: `evidence/baseline/coverage-baseline.md` (committed JaCoCo package projection plus first-party summary; raw `coverage/baseline-931.jacoco.xml` and `.cobertura.xml` git-ignored in the worktree, read directly by the reviewer) - PASS +- C# post-change coverage artifact: `evidence/qa-gates/coverage-final.md` (pre-merge, MEASUREMENT 2) and `evidence/qa-gates/post-merge-coverage-final.md` (post-merge, MEASUREMENT 2); raw `coverage/final-931.*` and `coverage/postmerge2-931.*` git-ignored in the worktree - PASS +- TypeScript baseline coverage artifact: `N/A - out of scope` +- TypeScript post-change coverage artifact: `N/A - out of scope` +- PowerShell baseline coverage artifact: `N/A - out of scope` +- PowerShell post-change coverage artifact: `N/A - out of scope` +- Python baseline coverage artifact: `N/A - out of scope` +- Python post-change coverage artifact: `N/A - out of scope` +- Per-language comparison summary: section 1.2.1 of this document + +### 1.2.1 Per-Language Coverage Comparison + +- C#: Baseline: 85.32% lines (56085/65737) / 79.73% branches (13595/17052) -> Post-change: 85.32% lines (56085/65737) / 79.73% branches (13595/17052). Change: 0.00% lines / 0.00% branches (identical first-party totals on the pre-merge final MEASUREMENT 2; per-package UtilitiesCS and QuickFiler LINE and BRANCH counters equal to baseline; the post-merge re-run reads 85.31% lines (56080/65736) / 79.73% branches (13597/17054), see section 5.3). New/changed-code coverage: N/A - no new executable production code (test-only change; zero production lines changed). Disposition: PASS. Evidence: evidence/baseline/coverage-baseline.md; evidence/qa-gates/coverage-final.md; evidence/qa-gates/post-merge-coverage-final.md; worktree coverage/*.jacoco.xml read directly. +- TypeScript: Baseline: N/A. Post-change: N/A. Change: N/A. Disposition: N/A. Evidence: N/A - zero TypeScript files changed on this branch. +- PowerShell: Baseline: N/A. Post-change: N/A. Change: N/A. Disposition: N/A. Evidence: N/A - zero PowerShell files changed on this branch. +- Python: Baseline: N/A. Post-change: N/A. Change: N/A. Disposition: N/A. Evidence: N/A - zero Python files changed on this branch. + +### 1.2.2 Coverage Artifact State + +**Coverage Metrics by Language:** + +| Language | Files Changed | Tests | Test Result | Baseline Coverage | Post-Change Coverage | New Code Coverage | +|---|---|---|---|---|---|---| +| C# | 6 (5 .cs, 1 .csproj; all test-project files) | 8 rewritten or relocated; 1469 + 4924 executed post-merge | PASS (0 failed) | 85.32% lines / 79.73% branches | 85.32% lines / 79.73% branches (pre-merge final); 85.31% lines / 79.73% branches (post-merge) | N/A - no new production code | +| TypeScript | 0 | 0 | N/A | N/A | N/A | N/A | +| PowerShell | 0 | 0 | N/A | N/A | N/A | N/A | +| Python | 0 | 0 | N/A | N/A | N/A | N/A | + +C# threshold evaluation (single line for the record): C# repository-wide first-party coverage 85.31% lines and 79.73% branches post-merge (85.32% / 79.73% pre-merge) meets the 85% line floor and the 75% branch floor of `.claude/rules/quality-tiers.md`, and the 80% / 75% floors of CLAUDE.md UT2: C# coverage verdict PASS. + +Per-file production coverage on the classes the rewritten tests exercise, read from the raw Cobertura documents (class rows, identical in baseline, final and post-merge documents): `QuickFiler\Viewers\BreadcrumbUiDispatcher.cs` line-rate 1.000, branch-rate 0.972; `UtilitiesCS\HelperClasses\FileSystem\FileInfoWrapper.cs` line-rate 1.000, branch-rate 0.750. No changed-line regression is possible: zero production lines changed (`CHANGED-PRODUCTION-LINES: 0` in `coverage-final.md`, confirmed by the name-only diff). + +### 1.3 Scenario completeness + +| Scenario class | Verdict | Evidence | +|---|---|---| +| Positive flows | PASS | Owner-thread admission tests retained in the primary partial (4 tests); `InitializeBreadcrumbPipeline_NullOwningDispatcher_DoesNotThrow` positive escape. | +| Negative flows | PASS | Two cross-thread `ThrowsBoundaryDiagnostic` tests; `Dispatcher_OwnerOnlyWorker_ReportsWithoutRunningAction` rejection path. | +| Edge cases | PASS | Null-owner escape; owner-only (null context) dispatcher. | +| Error handling | PASS | Exception type, message content and non-`ObjectDisposedException` asserted. | +| Concurrency | PASS | Every cross-thread case runs on a dedicated thread with an in-thread distinctness precondition; full suites pass under class-level parallelism. | +| State transitions | PASS | `BreadcrumbCoordinator` identity asserted before and after the repeated initialization. | + +### 1.4 Structure, diagnostics, external dependencies + +- Arrange-Act-Assert: PASS. The three relocated tests and the rewritten null-owner test carry explicit `// Arrange`, `// Act`, `// Assert` markers; `Dispatcher_OwnerOnlyWorker_ReportsWithoutRunningAction` follows the unmarked compact style of its file (see code-review CR-3, informational). +- Clear failure messages: PASS. Every precondition assertion has a reason string; negative controls captured them verbatim (`mutation-inline-precondition.md`). +- No external dependencies: PASS. No network, database, external process or repository-tracked file. The `OpenRead` sentinel is the running host's own loaded assembly image opened read-only with `FileShare.ReadWrite` (six pre-existing opens of the same shape exist in the same file). +- Temporary files: PASS. No `File.Create`, `File.WriteAll`, `File.Delete`, `Path.GetTempFileName`, `Path.GetTempPath` in `FileInfoWrapper_Tests.cs` (Grep of the current file); the only `FileMode` is `Open` and the only `FileAccess` is `Read`. The rooted literal `C:\Repo\fixture.sln` is never opened or created. +- Determinism infrastructure: PASS. No `Thread.Sleep`, `Task.Delay`, `[Timeout]`, retry attribute or loop, `DateTime.Now`, or RNG in the added lines (`p4-t11-scope-boundary` seven `ADDED-` counts 0 over 306 added lines; reviewer Grep of the four QuickFiler.Test files confirms `Task.Run` 0 and `Thread.Sleep` 0; the only `GetAwaiter().GetResult()` occurrences are the pre-existing `PumpSynchronizationContext` drain at lines 322-333 of `BreadcrumbPopupBoundaryCoverageTests.cs`, unchanged). +- Parallel-regime invariants: PASS. `[DoNotParallelize]` occurrences in QuickFiler.Test remain exactly the two pre-existing ones (`Helper Classes/EmailMoveMonitorTests.cs:24`, `Helper Classes/ViewerQueueStaticWrapperTests.cs:11`); `TaskMaster.runsettings` SHA-256 unchanged across every run (`RUNSETTINGS-HASH-NOW` equals the P0-T4 value in every projection). +- Test file location: PASS with note. Tests live in `QuickFiler.Test/` and `UtilitiesCS.Test/`, the repository's established `.Test/` convention; no test file is colocated with production source. The `tests/` mirror-tree wording of `.claude/rules/general-unit-test.md` is a cross-repository rule not adopted by this repository's C# layout (PA-8, pre-existing). + +## 2. General Code Change Policy Compliance + +| Requirement | Verdict | Evidence | +|---|---|---| +| Bugfix workflow step 1 (failing regression test first) | PASS with disclosed substitution | `evidence/regression-testing/fail-before-exception.2026-09-29T09-07.md` explains that a committed failing run is structurally impossible without mutating process-global thread-pool state or spawning a handle-holding process (both prohibited); plan D-13 substitutes four negative controls (M1-M4) as the deterministic observed-failing evidence. All four controls show the rewritten test failing against the mutation and passing after the revert. | +| Bugfix workflow step 2 (minimal, targeted fix) | PASS | Two affected sites rewritten; the twenty UNAFFECTED `Task.Run` sites untouched (name-only diff excludes all eight files AC3 names); no production file changed. | +| Bugfix workflow step 3 (full toolchain, restart on change) | PASS | `toolchain-pass.md` records one clean iteration in CLAUDE.md order; `post-merge-toolchain-pass.md` repeats all gates at 55a50e922. | +| Simplicity / reusability | PASS | The private helper is promoted to one shared `internal static` type with an identical body; duplication removed rather than added. | +| Separation of concerns | PASS | No I/O added to production; test helper contains only threading glue. | +| Fail fast / no broad catch | PASS with note | `DedicatedWorkerThread.Run` catches `Exception` on the worker and returns it to the caller for assertion; this is the capture mechanism at a test-helper boundary, documented in its remarks (code-review CR-2). | +| 500-line file limit | PASS | Current line counts (reviewer Read of each file; matches `p4-t9-post-format-census`): `DedicatedWorkerThread.cs` 48; `ItemViewerBreadcrumbThreadAffinityTests.Part2.cs` 202; `ItemViewerBreadcrumbThreadAffinityTests.cs` 294 (was 490); `BreadcrumbPopupBoundaryCoverageTests.cs` 386 (was 361); `FileInfoWrapper_Tests.cs` 357 (was 359). `QuickFiler.Test.csproj` 570 lines is a project file, exempt. | +| Naming, docs, comments | PASS | `PascalCase` type and member, `camelCase` locals; XML ``/`` on the helper and every rewritten test; comments explain why. | +| Dependencies | PASS | No package added; `packages.config` untouched. The `.csproj` analyzer version strings changed only through the merge from origin/main (MSTest.Analyzers 4.4.1, Meziantou.Analyzer 3.0.290), taken from main. | +| Public API compatibility | PASS | `DedicatedWorkerThread` is `internal` to the test assembly; no production API touched. | +| Supporting documents updated | PASS | spec.md acceptance criteria checked off; plan tasks checked off; evidence tree complete. | +| Committed Test Evidence Format (CLAUDE.md) | PASS | Projections only: JaCoCo package projections, one-line first-party summaries, TRX-derived summaries stating derived figures ("Skipped 0, derived as total minus executed"). No `.trx`, `.xml`, `.coverage` under the feature folder (Glob) or in the diff name list. | +| Tonality | PASS | Evidence artifacts and code comments are neutral and factual. | + +## 3. Language-Specific Code Change Policy Compliance + +C# (`.claude/rules/csharp.md`, CLAUDE.md C#1-C#7): + +| Requirement | Verdict | Evidence | +|---|---|---| +| Formatting via `dotnet tool run csharpier` | PASS | `p4-t1-csharpier-format` REWRITTEN 0; `p4-t2-csharpier-check` exit 0, 1625 files (delta +2 for the two new `.cs` files); `post-merge-csharpier-check` exit 0, no file reported. | +| Analyzer rebuild (`/t:Rebuild`, EnableNETAnalyzers, EnforceCodeStyleInBuild) | PASS | `p4-t3-msbuild-analyzers` exit 0, 0 errors, 0 warnings, SKIP_CORECOMPILE_LINES 0, both test assemblies compiled; `post-merge-msbuild-analyzers` exit 0, 0/0, SKIP 0, merged analyzer versions loaded (MSTest.Analyzers.4.4.1 x36, Meziantou.Analyzer.3.0.290 x32). | +| Nullable / TreatWarningsAsErrors rebuild (`/t:Rebuild`, no `/p:Nullable=enable`) | PASS | `p4-t4-msbuild-nullable` exit 0, 0/0, SKIP 0, "no Nullable property override"; `post-merge-msbuild-nullable` exit 0, 0/0, SKIP 0. | +| Approved command forms | PASS with disclosure | Each msbuild invocation adds `/nodeReuse:false` (plan D-16; additive, changes no diagnostic) to avoid resident node-reuse workers, the very mechanism behind the #906 failure. Recorded verbatim in each artifact (PA-7). | +| Explicit `using` directives, `internal` for non-public | PASS | `System.Reflection` removed from the primary partial and added to Part2 where `FieldInfo` moved; helper is `internal static`. | +| No `dotnet format` | PASS | Not used anywhere in the evidence. | +| Analyzer stack / severity ordering | PASS (not touched) | No `.editorconfig` or `` change originates on this branch. | +| Banned symbols (`Thread.Sleep`, `Task.Delay`, `DateTime.Now`, `Random.Shared`) | PASS | None added (section 1.4). | +| Prohibited behaviours (weakening assertions, sleeps/retries/timing hacks, unrun toolchain) | PASS | Assertions were added (in-thread precondition, `captured` null, `BeSameAs(sentinel)`), none removed; no timing hack; toolchain evidence complete. | + +## 4. Language-Specific Unit Test Policy Compliance + +C# unit test policy (CLAUDE.md CUT1-CUT3, `.claude/rules/csharp.md` Testing Standards): + +| Requirement | Verdict | Evidence | +|---|---|---| +| MSTest framework (`[TestClass]`, `[TestMethod]`) | PASS | Both partials share one `[TestClass]` on the primary declaration (plan D-2); every rewritten test carries `[TestMethod]`; no xUnit/NUnit. | +| Moq for mocks | PASS | `Mock(MockBehavior.Strict)` for the `OpenRead` seam; `Mock(MockBehavior.Strict)` in the affinity tests. | +| FluentAssertions preferred | PASS | All assertions use `.Should()`; no MSTest `Assert` introduced. | +| Test toolchain step 4 (MSTest with coverage route) | PASS with disclosure | COVERAGE-ROUTE DIRECT (plan D-4): the runner's own inner `dotnet-coverage` collect around `vstest.console.exe` with the runner's functions, because the runner hard-codes its filter and cannot apply the recorded shell-icon exclusion; the same three committed forms are produced. Exit 0, 7320 then 7323 executed, 0 failed, LINE-FLOOR MET, BRANCH-FLOOR MET: PASS. | +| Deterministic test rules (no PATH/profile/cwd assumptions) | PASS | The old `AppDomain.CurrentDomain.BaseDirectory` walk-up to `TaskMaster.sln` is removed; the fixture is a rooted literal and the stream is the host's own image. | +| Seam-based mocking for filesystem | PASS | The existing internal `FileInfoWrapper(IFileInfo)` seam is used; no seam added to production. | +| New module/class coverage >= 90% | PASS (vacuous) | No new production module, class or method; `DedicatedWorkerThread` is test code outside the denominator. | +| Coverage regression on changed lines | PASS | Zero changed production lines. | + +## 5. Test Coverage Detail + +### 5.1 Baseline (pre-edit, 2026-09-29T09-05, merge base 177b6d78e) + +First-party: lines 56085/65737 (85.32%), branches 13595/17052 (79.73%). Package counters (JaCoCo projection, verified against `coverage/baseline-931.jacoco.xml`): QuickFiler LINE 10461 covered / 2293 missed, BRANCH 2518 / 699; UtilitiesCS LINE 38816 / 4608, BRANCH 9411 / 1858. Sum of the nine package LINE counters: 56085 covered, 9652 missed, 65737 total (reviewer arithmetic agrees with the first-party line). + +### 5.2 Final (pre-merge, 2026-09-29T09-42, MEASUREMENT 2) + +First-party: lines 56085/65737 (85.32%), branches 13595/17052 (79.73%). QuickFiler LINE 10461 / 2293, BRANCH 2518 / 699; UtilitiesCS LINE 38816 / 4608, BRANCH 9411 / 1858. All four PACKAGE comparisons NOT-LOWER=True. MEASUREMENT 1 read QuickFiler LINE one covered line lower (10460) on an identical denominator with zero production change and was re-measured once per plan D-7; D-7 repeats the measurement, not a test, and no test failed in either measurement. + +### 5.3 Post-merge (55a50e922, 2026-09-29T19-59, MEASUREMENT 2) + +First-party: lines 56080/65736 (85.31%), branches 13597/17054 (79.73%). QuickFiler LINE 10461 / 2293, BRANCH 2518 / 699 (equal to baseline); UtilitiesCS LINE 38811 / 4612 (rate 0.893789 versus baseline 0.893884, NOT-LOWER=False), BRANCH 9413 / 1858 (rate 0.835152, NOT-LOWER=True). + +Reviewer assessment of the UtilitiesCS LINE finding (PA-4, non-blocking): the executor's per-file attribution is corroborated by the raw Cobertura class rows. `UtilitiesCS\NewtonsoftHelpers\SDIL Reader\ILGlobals.cs` reads line-rate 0.95 (38/40) in baseline and final and 0.947368 (36/38) post-merge; `UtilitiesCS\Threading\UiThread.cs` reads 0.977444 (130/133) in baseline and final and 0.977612 (131/134) post-merge. Those two files were changed by origin/main (issue #930), not by this branch. Removing 2 covered lines and adding 1 covered line yields 38815/43423 = 0.893881, already below 0.893884 by 0.000003 on arithmetic alone; the remaining 4 covered lines move between unchanged files across the two identical measurements, which is collector variance. The comparison of a post-merge run against a pre-merge baseline is not like-for-like (denominator 43424 -> 43423) and no baseline exists at the merged main. This item changed zero production lines, so the no-regression-on-changed-lines clause has an empty subject. AC18 is defined on `coverage-final.md` (pre-merge, like-for-like), where every package comparison holds. + +### 5.4 Floors + +| Floor source | Line floor | Branch floor | Pre-merge | Post-merge | Verdict | +|---|---|---|---|---|---| +| `.claude/rules/quality-tiers.md`, `general-unit-test.md` | 85% | 75% | 85.32% / 79.73% | 85.31% / 79.73% | PASS (line margin 0.31 points post-merge) | +| CLAUDE.md UT2 (maintainer decision 2026-09-11, #563) | 80% | 75% | 85.32% / 79.73% | 85.31% / 79.73% | PASS | + +The two floor documents disagree (PA-3, pre-existing); both are satisfied. + +## 6. Test Execution Metrics + +| Run | Assembly / route | Runsettings | Filter | Total | Passed | Failed | Evidence | +|---|---|---|---|---|---|---|---| +| Baseline | QuickFiler.Test | TaskMaster.runsettings, /InIsolation | none | 1468 | 1468 | 0 | `evidence/baseline/test-run-baseline.md` | +| Baseline | UtilitiesCS.Test | TaskMaster.runsettings, /InIsolation | shell-icon exclusion (4 classes named) | 4922 | 4922 | 0 | same | +| Baseline | 9 assemblies, coverage route DIRECT | TaskMaster.cli.runsettings, /InIsolation | LiveOutlook + shell-icon exclusion | 7320 | 7320 | 0 | `evidence/baseline/coverage-baseline.md` | +| Post-fix | QuickFiler.Test | TaskMaster.runsettings, /InIsolation | none | 1468 | 1468 | 0 | `evidence/regression-testing/parallel-suite-quickfiler-test.md` | +| Post-fix | UtilitiesCS.Test | TaskMaster.runsettings, /InIsolation | shell-icon exclusion | 4922 | 4922 | 0 | `evidence/regression-testing/parallel-suite-utilitiescs-test.md` | +| Post-fix | 9 assemblies, coverage route DIRECT (x2) | TaskMaster.cli.runsettings, /InIsolation | LiveOutlook + shell-icon exclusion | 7320 | 7320 | 0 | `evidence/qa-gates/coverage-final.md` | +| Post-merge | QuickFiler.Test | TaskMaster.runsettings, /InIsolation | none | 1469 | 1469 | 0 | `evidence/qa-gates/post-merge-parallel-suite-quickfiler-test.md` | +| Post-merge | UtilitiesCS.Test | TaskMaster.runsettings, /InIsolation | shell-icon exclusion | 4924 | 4924 | 0 | `evidence/qa-gates/post-merge-parallel-suite-utilitiescs-test.md` | +| Post-merge | 9 assemblies, coverage route DIRECT (x2) | TaskMaster.cli.runsettings, /InIsolation | LiveOutlook + shell-icon exclusion | 7323 | 7323 | 0 | `evidence/qa-gates/post-merge-coverage-final.md` | + +Test-count arithmetic: QuickFiler.Test 1468 before and after the split proves the three relocated tests were discovered from the Part2 file and none was lost; the post-merge +1 is a test main contributed (`QfcItemController.UiThreadDispatcherFixtureTests.cs`). UtilitiesCS.Test 4922 before and after; post-merge +2 from main's changes to `ILGlobals_Tests.cs` and `UiThreadApartmentMeasurement_Tests.cs`. The eight `ItemViewerBreadcrumbThreadAffinityTests` and `Dispatcher_OwnerOnlyWorker_ReportsWithoutRunningAction` names and the eight `FileInfoWrapper_Tests` names are listed as Passed in every relevant projection. + +Negative controls (each: mutated run exit 1 with the predicted failure text; revert proven by `git diff --exit-code HEAD` 0, porcelain EMPTY, SHA-256 equal to the anchor; confirming run exit 0): + +| Control | Mutation | Failed test(s) and message excerpt | Evidence | +|---|---|---|---| +| M1 | `BreadcrumbUiDispatcher.IsCurrentBoundary` thread-id branch replaced by `return true;` | `Dispatcher_OwnerOnlyWorker_ReportsWithoutRunningAction`: "Expected executions to be 0, but found 1" | `mutation-owner-only-dispatcher-guard.md` | +| M2 | `ItemViewer.ThrowIfOffUiBoundary` null-owner `return;` replaced by the pre-#781 context-reference throw | `InitializeBreadcrumbPipeline_NullOwningDispatcher_DoesNotThrow`: "Expected captured to be ... but found System.InvalidOperationException" | `mutation-null-owner-escape.md` | +| M3 | `action();` inserted as first statement of `DedicatedWorkerThread.Run` | all four dedicated-thread tests fail at their precondition ("dedicated worker thread must not be ...") | `mutation-inline-precondition.md` | +| M4 | mock `OpenRead()` pointed at a second `FileStream` | `OpenRead_ShouldReturnReadableStreamForWrappedFile`: "Expected stream to refer to ..." | `mutation-openread-sentinel.md` | + +## 7. Code Quality Checks + +| Check | Command / method | Result | +|---|---|---| +| Confidentiality masking scan | Grep of the feature folder for the developer account name, the `:/Users` and `:\Users` prefixes, the 8.3 short-name alias of the profile directory, `Program Files`, `DESKTOP-`, `LAPTOP-`, `runUser`, `MachineName`, `COMPUTERNAME`, UNC prefixes; Grep `-o` for every drive-letter path | PASS. Zero account, profile, host or `Program Files` matches. The only drive-letter paths are the fixture literal `C:\Repo` (spec.md:89, plan lines 95/130/695, research line 259). The one `COMPUTERNAME` hit is the plan's sweep command reading `$env:COMPUTERNAME` as a token source, not a value. Placeholders `` used in the mutation projections. | +| Suppression scan (added lines) | Read of the full diff | PASS. No `#pragma warning disable`, `[SuppressMessage]`, `[ExcludeFromCodeCoverage]`, or `.editorconfig` change. | +| Workflow change scan | Name-only diff | PASS. No `.github/workflows` file changed on this branch. | +| Raw evidence document scan | Glob `**/*.{trx,xml,coverage}` under the feature folder; diff name list | PASS. Zero matches. | +| Coverage exclusion policy | Read of `coverage.config` | PASS (not changed). The `` excludes name only third-party modules (Deedle, FSharp, Castle.Core, FluentAssertions, Moq, Microsoft.Testing, MSTest); no production path excluded; file not in the diff. | +| Architecture boundaries | Read of the diff | PASS (the diff contains no production code; the test code adds no VSTO/Interop reference beyond the pre-existing `System.Windows.Threading.Dispatcher` and WinForms `Panel` uses already in these files). | +| Runsettings integrity | `RUNSETTINGS-HASH-NOW` in every projection | PASS. 199408CA...7FFA in every run, equal to P0-T4. | + +## 8. Gaps and Exceptions + +| ID | Severity | Blocking | Finding | Evidence | Disposition | +|---|---|---|---|---|---| +| PA-1 | Low | Non-blocking | Spec AC15 states the changed-file set "equals the Write Set entries plus documents inside the feature folder", but the branch diff also carries `docs/features/potential/promoted/2026-09-28-tests-depend-on-uncontrolled-environment.md`, written by the promotion-lifecycle commit (e13757267) before execution. The plan (D-11, Execution Conventions Clause A) defines the footprint gate as subtracting inherited paths, and `p4-t11-scope-boundary` records the subtraction explicitly as `INHERITED-PROMOTION-RECORD`. The AC's protective purpose (no production, `.claude`, or `config` change; no stray file) is met. | `p4-t11-scope-boundary.2026-09-29T09-44.md` lines 91-94, 123-124; plan D-11 | Accepted as a spec-wording residual; AC15 graded PASS with disclosure in the feature audit. Recommend the spec template name the promoted record in future footprint criteria. | +| PA-2 | Low | Non-blocking | `quality-tiers.yml` is absent at the repository root, so tier-dependent gates (property tests, mutation score) cannot be evaluated for any project. Pre-existing on main; outside the diff. | Glob `quality-tiers.yml` returns nothing | Pre-existing; record only. | +| PA-3 | Low | Non-blocking | Coverage-floor documentation conflict: CLAUDE.md UT2 states 80% line / 90% new-code; `.claude/rules/general-unit-test.md` and `quality-tiers.md` state 85% line / 75% branch. Both are satisfied here (85.31% / 79.73% post-merge). | Section 5.4 | Pre-existing; report against both; the policy-compliance-order skill lists CLAUDE.md first. | +| PA-4 | Low | Non-blocking | Post-merge UtilitiesCS package LINE rate 0.893789 is below the pre-merge baseline 0.893884 in both post-merge measurements. Attributable to production edits from origin/main (`ILGlobals.cs` -2 lines / -2 covered; `UiThread.cs` +1 / +1; both verified in the raw Cobertura class rows) plus 4 covered lines of run-to-run variance in unchanged files. Zero production lines changed by this item. | `post-merge-coverage-final.md`; section 5.3 | Not a regression of this branch; AC18 is defined on the pre-merge like-for-like comparison, which holds. No remediation. | +| PA-5 | Info | Non-blocking | Canonical `artifacts/csharp/coverage.xml` not emitted in the worktree. Coverage evidence is the CLAUDE.md-mandated committed projections plus the git-ignored raw documents under `coverage/`, which the reviewer read and reconciled. The session checkout holds a stale Cobertura document at that canonical path from another branch (line-rate 0.848316), which is not this branch's evidence. | Section "Evidence Location Compliance" | Artifact treated as present; C# coverage verdict PASS from the reconciled figures. | +| PA-6 | Info | Non-blocking | `artifacts/pr_context.summary.txt` / `.appendix.txt` absent in the worktree and the PR-context MCP unavailable in this no-Bash run; scope taken from the orchestrator-supplied `git diff --name-only origin/main...HEAD` list and the supplied three-dot code patch. UNVERIFIED by the reviewer: byte-identity of the eight AC3 files (established instead by their absence from the name-only diff), `validate_evidence_locations.py` execution, and a live `git merge-base`. | Caller prompt; `p4-t33-closure` diff listings | Best-effort assumptions documented; no finding depends on the unverified items. | +| PA-7 | Info | Non-blocking | Two disclosed command substitutions: `/nodeReuse:false` appended to every msbuild invocation (plan D-16) and the DIRECT coverage route replacing the runner entry point because the runner hard-codes its filter (plan D-4). Neither changes a diagnostic or a committed form. | `toolchain-pass.md`; `coverage-final.md`; plan D-4, D-16 | Accepted. | +| PA-8 | Info | Non-blocking | Repository C# test layout is `.Test/`, not the `tests/` mirror tree named in `general-unit-test.md`. Pre-existing convention; the new files follow it and no test is colocated with production source. | `QuickFiler.Test/TestSupport/DedicatedWorkerThread.cs` | Pre-existing; record only. | + +Follow-ups deliberately not filed on this branch (four potential entries in `evidence/qa-gates/p4-t13-follow-up-handoff.2026-09-29T09-46.md`): the promotion tools write under `docs/features/potential/`, which would contradict AC15, so the orchestrator files them from a separate branch and names them in the PR body. Not a finding against this branch (caller instruction, consistent with spec `## Rollout & Follow-up`). + +## 9. Summary of Changes + +- `QuickFiler.Test/TestSupport/DedicatedWorkerThread.cs` (new, 48 lines): `internal static Exception Run(Action)` runs the delegate on a dedicated background thread, joins it without a timeout, returns the captured exception or null; no assertion, sleep, delay, timeout or retry. +- `QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.cs` (490 -> 294 lines): class becomes `partial`; retains the four owner-thread admission tests, `InertOperations`, and the three nested helper types; loses the three cross-thread tests, `ClearViewerDispatcher` and `RunOnDedicatedWorkerThread`; `using System.Reflection` removed. +- `QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.Part2.cs` (new, 202 lines): the three cross-thread tests now calling `DedicatedWorkerThread.Run`; `InitializeBreadcrumbPipeline_NullOwningDispatcher_DoesNotThrow` rewritten to capture the owning `Dispatcher` before it is cleared and assert `owner.CheckAccess()` is false inside the delegate; `ClearViewerDispatcher` relocated. +- `QuickFiler.Test/Viewers/BreadcrumbPopupBoundaryCoverageTests.cs` (361 -> 386 lines): `Dispatcher_OwnerOnlyWorker_ReportsWithoutRunningAction` rewritten from `Task.Run` + blocking wait to `DedicatedWorkerThread.Run` with an in-thread `CurrentManagedThreadId` distinctness assertion; remark added. +- `QuickFiler.Test/QuickFiler.Test.csproj`: two `Compile Include` entries added in the neighbouring backslash form (lines 99, 229); analyzer version strings updated by the merge from main only. +- `UtilitiesCS.Test/HelperClasses/FileInfoWrapper_Tests.cs` (359 -> 357 lines): `GetSolutionFile()` and the `AppDomain` walk-up removed; three metadata tests use the rooted literal `FixturePath`; `OpenRead` test routed through the internal `FileInfoWrapper(IFileInfo)` seam with a strict mock returning a test-owned `FileStream` over the test assembly image, asserting `BeSameAs(sentinel)`, `CanRead` and `Length > 0`. +- Feature folder: research record, spec, plan, 43 pre-merge and 8 post-merge evidence projections. + +## 10. Compliance Verdict + +PASS. Blocking findings: 0. Non-blocking findings: 8 (PA-1 to PA-8), none requiring remediation. No `remediation-inputs` artifact is produced. + +## Appendix A: Test Inventory + +Rewritten or relocated tests (all `[TestMethod]`, MSTest, FluentAssertions, Moq where a mock is used): + +| Test | File | Change | Status (post-merge run) | +|---|---|---|---| +| `Dispatcher_OwnerOnlyWorker_ReportsWithoutRunningAction` | `QuickFiler.Test/Viewers/BreadcrumbPopupBoundaryCoverageTests.cs` | rewritten (dedicated thread, in-thread id precondition) | Passed | +| `InitializeBreadcrumbPipeline_NullOwningDispatcher_DoesNotThrow` | `QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.Part2.cs` | relocated and rewritten (owner captured before clear, in-thread `CheckAccess()` precondition) | Passed | +| `InitializeBreadcrumbPipeline_WorkerThread_ThrowsBoundaryDiagnostic` | same | relocated; helper call retargeted | Passed | +| `ConfigureBreadcrumbDropDown_WorkerThread_ThrowsBoundaryDiagnostic` | same | relocated; helper call retargeted | Passed | +| `Properties_ShouldMirrorWrappedFileInfo` | `UtilitiesCS.Test/HelperClasses/FileInfoWrapper_Tests.cs` | rewritten (rooted literal fixture) | Passed | +| `ExplicitDirectoryCast_ShouldReturnWrappedContainingDirectory` | same | rewritten (rooted literal fixture) | Passed | +| `OpenRead_ShouldReturnReadableStreamForWrappedFile` | same | rewritten (seam + test-owned sentinel) | Passed | +| `ToString_ShouldDelegateToWrappedFileInfo` | same | rewritten (rooted literal fixture) | Passed | + +Unchanged tests in the touched classes (all Passed): `InitializeBreadcrumbPipeline_ConstructedInsideDispatcherOperation_SucceedsUnderDifferentAmbientContext`, `InitializeBreadcrumbPipeline_OwningThreadNullAmbientContext_DoesNotThrow`, `InitializeBreadcrumbPipeline_OwningThreadDifferentPlainContext_DoesNotThrow`, `ConfigureBreadcrumbDropDown_OwningThreadInsideDispatcherOperation_DoesNotThrow`; `Constructor_WhenFileInfoIsNull_ThrowsArgumentNullException`, `PropertyDelegates_ShouldMirrorMockedIFileInfo`, `StreamAndCopyMethods_ShouldDelegateToWrappedIFileInfo`, `AccessControlAndLifecycleMethods_ShouldDelegateToWrappedIFileInfo`; the four other tests of `BreadcrumbPopupBoundaryCoverageTests`. + +Test helper (not a test): `QuickFiler.Test.TestSupport.DedicatedWorkerThread.Run(Action)`, four call sites. + +## Appendix B: Toolchain Commands Reference + +Commands as recorded in `evidence/qa-gates/toolchain-pass.md` and `post-merge-toolchain-pass.md` (CLAUDE.md order; each exit 0 in the final pass): + +1. `dotnet tool run csharpier format .` (REWRITTEN 0) then `dotnet tool run csharpier check .` (Checked 1625 files). +2. `msbuild TaskMaster.sln /t:Rebuild /m /p:Configuration=Debug "/p:Platform=Any CPU" /p:EnableNETAnalyzers=true /p:EnforceCodeStyleInBuild=true` plus `/nodeReuse:false` (0 errors, 0 warnings, SKIP_CORECOMPILE_LINES 0). +3. `msbuild TaskMaster.sln /t:Rebuild /m /p:Configuration=Debug "/p:Platform=Any CPU" /p:TreatWarningsAsErrors=true` plus `/nodeReuse:false` (0 errors, 0 warnings, SKIP_CORECOMPILE_LINES 0). +4. MSTest-with-coverage route, DIRECT: `dotnet-coverage collect --output-format cobertura --settings coverage\effective-coverage-931.config -- vstest.console.exe <9 test assemblies> "/Settings:scripts\vscode\TaskMaster.cli.runsettings" /InIsolation "/TestCaseFilter:TestCategory!=LiveOutlook&" "/ResultsDirectory:coverage\test-results\931\" "/Logger:trx;LogFileName=-931.trx" "/Blame:CollectHangDump;TestTimeout=4min;HangDumpType=None"`, post-processed with the runner's own helpers into the JaCoCo package projection and first-party summary: exit 0, floors met, PASS. + +Parallel-suite runs: `vstest.console.exe "/Settings:TaskMaster.runsettings" /InIsolation [shell-icon exclusion filter for UtilitiesCS.Test] "/ResultsDirectory:coverage\test-results\931\" "/Logger:trx;LogFileName=.trx" "/Blame:CollectHangDump;TestTimeout=4min;HangDumpType=None"`. + +Reviewer verification method (this run): Read, Grep and Glob over the worktree only; no command executed. Git facts (head, base, merge commit, name-only diff) were supplied by the orchestrator and cross-checked against `p4-t11-scope-boundary` and `p4-t33-closure`. diff --git a/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/research/2026-09-28T20-15-tests-depend-on-uncontrolled-environment-research.md b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/research/2026-09-28T20-15-tests-depend-on-uncontrolled-environment-research.md new file mode 100644 index 000000000..bcb4c9a02 --- /dev/null +++ b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/research/2026-09-28T20-15-tests-depend-on-uncontrolled-environment-research.md @@ -0,0 +1,320 @@ +# Research: tests depend on uncontrolled environment (issue #931) + +- Issue: #931 (consolidates #905 and #906) +- Work mode: full-bug; run `bugs-2026-09-28` +- Branch: `bug/tests-depend-on-uncontrolled-environment-931` +- Inputs read: `docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/issue.md`, `spec.md`, `plan.2026-09-28T20-01.md`; `docs/features/potential/promoted/2026-09-28-tests-depend-on-uncontrolled-environment.md`; the #900 feature folder evidence under `docs/features/active/breadcrumb-thread-affinity-tests-assume-taskrun-distinct-thread-900/`. +- Evidence basis: every file and line cited below was read from the current tree of this worktree with `Read`/`Grep`. No command was executed; no test was run. Line numbers are those of the current tree, which differ from the issue text in places (noted where relevant). + +## 0. Summary of findings + +1. The #900 pattern is present at `QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.cs:228-238` (in-thread precondition) and `:385-403` (`RunOnDedicatedWorkerThread`). +2. `QuickFiler.Test` contains exactly 22 `Task.Run` call sites (derived twice, see the numeric section). Exactly **2 are AFFECTED**: `BreadcrumbPopupBoundaryCoverageTests.cs:58` and `ItemViewerBreadcrumbThreadAffinityTests.cs:332`. The other 6 cited candidates are UNAFFECTED because the guard they exercise decides by ambient `SynchronizationContext` reference identity or by an executing-callback marker, never by thread identity; 14 further sites are TCS completions or thread-agnostic. +3. `FileInfoWrapper` already has a seam: `internal FileInfoWrapper(IFileInfo)` at `UtilitiesCS/HelperClasses/FileSystem/FileInfoWrapper.cs:21-24`, reachable through `InternalsVisibleTo("UtilitiesCS.Test")` at `UtilitiesCS/Properties/AssemblyInfo.cs:19`. No production change is required. +4. A `MemoryStream` cannot replace the `OpenRead()` fixture because `IFileInfo.OpenRead()` returns the concrete `FileStream` type (`UtilitiesCS/Interfaces/IHelperClasses/IFileInfo.cs:26`). The test-owned stream must be a `FileStream`; the repository's established handle-free-of-contention shape is a read-only, `FileShare.ReadWrite` open of the test's own loaded assembly (already used in the same file at lines 189-224). The three property/cast/`ToString` tests need no handle at all. +5. Parallel regime: `TaskMaster.runsettings:4-7` and `scripts/vscode/TaskMaster.cli.runsettings:4-7` both set `Workers=0`, `Scope=ClassLevel`. None of the seven candidate classes nor `FileInfoWrapper_Tests` carries `[DoNotParallelize]`. + +--- + +## A. Distinct-thread defect (issue #905 portion) + +### A1. The canonical #900 / PR #904 shape (verified in the current tree) + +Helper, `QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.cs:385-403`: + +```csharp +private static Exception RunOnDedicatedWorkerThread(Action action) +{ + Exception captured = null; + var thread = new Thread(() => + { + try + { + action(); + } + catch (Exception error) + { + captured = error; + } + }); + thread.IsBackground = true; + thread.Start(); + thread.Join(); + return captured; +} +``` + +In-thread precondition placed before the guarded call, `:228-238` (the second instance is identical at `:277-291`): + +```csharp +Exception captured = RunOnDedicatedWorkerThread(() => +{ + bool isOwnerThread = scope.Viewer.UiDispatcher.CheckAccess(); + isOwnerThread + .Should() + .BeFalse( + "the dedicated worker thread must not be the thread that constructed " + + "the viewer, or the boundary assertion would pass vacuously" + ); + scope.Viewer.InitializeBreadcrumbPipeline(provider.Object, operations); +}); +``` + +Shape: `new Thread(...)`, `IsBackground = true`, `Start()`, untimed `Join()`, exception marshalled back by field, precondition asserted inside the delegate, no apartment state set. The XML remarks at `:203-217` and `:377-384` record the rationale (a `Task.Run` work item queued from a pool thread lands on that thread's local queue and a blocking wait can run it inline; a constructed `Thread` is distinct from every live thread by construction; the `Join()` parks no pool slot waiting on another pool slot). + +Other dedicated-thread helpers exist in the project but serve different purposes and are not the pattern: `Controllers/QfcItemController.TestSupport.cs:251-271` (`StartRunningDispatcher`, STA + `Dispatcher.Run()`), `Controllers/QfcItemController.UiThreadDispatcherFixture.cs:175-203` (parked STA dispatcher), `Controllers/BayesianPerformanceController.TestSupport.cs:16-53` (STA viewer host), `TestSupport/WinFormsPumpHost.cs:53` (WinForms pump), `Helper Classes/EmailMoveMonitorTests.cs:281-287` (marshal-target thread inside a test delegate). + +### A2. Complete enumeration of `Task.Run` call sites in `QuickFiler.Test` (22 sites) + +Guard semantics that decide the classification (all verified in `QuickFiler/Viewers/BreadcrumbUiDispatcher.cs`): + +- `Dispatch(Action)` (`:71-151`) calls `IsCurrentBoundary()` (`:255-278`). With a captured context (`_context != null`) the boundary is `ReferenceEquals(SynchronizationContext.Current, _context)` (`:269-272`); **thread identity is never consulted**. Only when `_context == null` (owner-only test dispatcher from `CreateForCurrentThreadTests()` `:62-65`, or the private 3-argument constructor with a null context) does the guard compare `Environment.CurrentManagedThreadId` with `_ownerThreadId` (`:276-277`). +- `DispatchValue` (`:157-235`) never calls `IsCurrentBoundary()`. It runs inline only when `ReferenceEquals(_executingDispatcher, this)` (`:166`); with `_context == null` it faults with "cannot marshal cross-thread UI work" for **every** other caller, on any thread (`:180-188`); otherwise it posts. +- `BreadcrumbBridgeCoordinator`'s public 2-argument constructor captures a context-backed dispatcher (`QuickFiler/Viewers/BreadcrumbBridgeCoordinator.cs:39-43`); inbound messages go `OnMessageReceived` -> `ObserveInboundAsync` -> `_dispatcher.Dispatch(...)` (`:273-315`). +- `ItemViewer.ThrowIfOffUiBoundary` (`QuickFiler/Viewers/ItemViewer.Breadcrumb.cs:432-447`): returns when `UiDispatcher == null` (`:435-438`), else throws when `!owning.CheckAccess()` (`:440-446`). `_uiDispatcher = Dispatcher.CurrentDispatcher` is captured at `QuickFiler/Viewers/ItemViewer.cs:27`. WPF `CheckAccess()` is `Thread` object identity. +- `BreadcrumbDropDownHost.Close` (`QuickFiler/Viewers/BreadcrumbDropDownHost.cs:247-263`), `Reset` (`:275-280`) and `Dispose` (`:283-290`) contain no thread guard; they schedule through the open lifetime, which dispatches through the context-backed operations. +- `EmailMoveMonitor.UnhookItem` marshals unconditionally through `_marshalToSta` (`QuickFiler/Helper Classes/EmailMoveMonitor.cs:67-76`). + +| # | Site (repo-relative, `QuickFiler.Test/`) | Containing test method | Verdict | Reason | +|---|---|---|---|---| +| 1 | `Helper Classes/EmailMoveMonitorTests.cs:298` | `UnhookItem_InvokedFromThreadPoolThread_RunsComAccessOnMarshalTargetThread` | UNAFFECTED | The asserted property (`recordedBodyThreadId != callingThreadId`, `:308-314`) is guaranteed by the fresh `new Thread` inside the marshal delegate (`:281-287`), which is distinct from every live thread including an inlined caller; `UnhookItem` marshals unconditionally. Class carries a pre-existing `[DoNotParallelize]` at `:24` (leave as is). | +| 2 | `Viewers/BreadcrumbCoordinatorLifecycleTests.cs:350` | `QueuedCompletion_DisposedBeforeOwnerDrain_DoesNotPublish` | UNAFFECTED | Completes a `TaskCompletionSource` (`gate.SetResult(key)`); continuations run asynchronously (`RunContinuationsAsynchronously`). | +| 3 | `Viewers/BreadcrumbPopupBoundaryCoverageTests.cs:58` | `Dispatcher_OwnerOnlyWorker_ReportsWithoutRunningAction` | **AFFECTED** | Owner-only dispatcher (`CreateOwnerOnlyDispatcher`, `:124-134`, null context, owner id = test thread). `Dispatch` reaches the thread-id branch (`BreadcrumbUiDispatcher.cs:276-277`). The blocking `GetAwaiter().GetResult()` at `:59` can inline the work item on the test thread, in which case the action runs inline, `executions == 1`, `errors` empty, and the test fails spuriously (`:60-61`). Outcome is decided by scheduling. | +| 4 | `Viewers/BreadcrumbPopupControlDispatchTests.cs:29` | `SurfaceFactory_WorkerCompletion_DispatchesEveryStageAndCleanup` | UNAFFECTED | Operations built with a context-backed dispatcher (`:311`); every stage goes through `RunAsync` -> `DispatchValue`, which posts unless inside an executing callback and never reads thread identity. The sibling test at `:53` calls `CreateSurface` directly on the test thread and asserts the same stage sequence. No blocking wait at the start (`Drain` waits on a monitor, `:249-279`). | +| 5 | `Viewers/BreadcrumbPopupControlDispatchTests.cs:111` | `Readiness_DisposeFromAmbientNullWorker_DispatchesHandlerDetachment` | UNAFFECTED | `readiness.Dispose()` -> `Cancel` -> `dispatcher.Dispatch(detachHandlers)` (`BreadcrumbPopupUiOperations.cs:418`); with a captured context the guard compares ambient context by reference; the body sets ambient null itself (`:113`), so the post occurs on any thread. `fixture.Drain(disposing)` is not a task wait, so the body is not inlined and the unrestored null context lands on a pool thread only. | +| 6 | `Viewers/BreadcrumbPopupControlDispatchTests.cs:300` | helper `CompleteOnWorker` (used by test 1 at `:31,33`) | UNAFFECTED | Completes a `TaskCompletionSource` only. | +| 7 | `Viewers/BreadcrumbPopupBoundaryCoverageTests.Part2.cs:192` | `CaptureCurrent_NullAndControlledContexts_FailFastAndCapture` | UNAFFECTED | `captured` is a `CaptureCurrent()` dispatcher whose `_context` is the `PumpSynchronizationContext`; `PostAsync` -> `Dispatch` compares ambient context by reference. On the test thread the ambient context is the restored previous one (`WithContext`, `:136-148`), never `context`, so the post happens on any thread; `PostCount == 1` (`:197`) pins the posted path. | +| 8 | `Viewers/BreadcrumbSelectorToggleUiBoundaryTests.cs:61` | `WorkerProviderAndSelectorToggle_MarshalPostsAndCallbackEntryToOwningBoundary` | UNAFFECTED | Completes a `TaskCompletionSource`. | +| 9 | `Viewers/BreadcrumbSelectorToggleUiBoundaryTests.cs:75` | same as 8 | UNAFFECTED | Coordinator built via the public constructor (context-backed). `messenger.Receive` -> inbound `Dispatch(HandleSelectorMessage)` posts whenever ambient context is not the captured one; the body forces ambient null through `InvokeAmbientNull` (`:325-337`). `context.WaitForPost()` (`:76`) precedes the blocking `GetResult()` (`:77`) and is a semaphore wait, so the body has already run on another pool thread before any wait-inlining is possible. | +| 10 | `Viewers/BreadcrumbSelectorToggleUiBoundaryTests.cs:137` | `PopupHost_WorkerCompletions_RunOnlyWhenCreatorThreadDrainsBoundary` | UNAFFECTED | Completes a `TaskCompletionSource` (factory completion). | +| 11 | `Viewers/BreadcrumbSelectorToggleUiBoundaryTests.cs:148` | same as 10 | UNAFFECTED | Completes a `TaskCompletionSource` (readiness). | +| 12 | `Viewers/BreadcrumbSelectorToggleUiBoundaryTests.cs:154` | same as 10 | UNAFFECTED | `host.Close(...)` has no thread guard (`BreadcrumbDropDownHost.cs:247-263`); the test asserts that callbacks and drained work ran on the creator thread (`:164-169`), which the drain enforces; the origin thread of `Close` is not asserted. | +| 13 | `Viewers/BreadcrumbSelectorToggleUiBoundaryTests.cs:161` | same as 10 | UNAFFECTED | `host.Reset` has no thread guard (`:275-280`); same reasoning as 12. | +| 14 | `Viewers/BreadcrumbSelectorOpenRetryTests.cs:37` | `MouseToggle_FirstOpenFaultsAfterAwait_SecondClickRetriesCleanly` | UNAFFECTED | Completes a `TaskCompletionSource` (`SetException`). | +| 15 | `Viewers/BreadcrumbSelectorOpenRetryTests.cs:210` | `Dispose_WhenResetAndOpenWorkAreQueued_HasNoLateActivity` | UNAFFECTED | Completes a `TaskCompletionSource`. | +| 16 | `Viewers/BreadcrumbSelectorOpenRetryTests.cs:218` | same as 15 | UNAFFECTED | `host.Reset` has no thread guard; assertions are about operation counts after drain (`:222-229`). | +| 17 | `Viewers/BreadcrumbSelectorOpenRetryTests.cs:219` | same as 15 | UNAFFECTED | `host.Dispose` has no thread guard (`:283-290`). | +| 18 | `Viewers/BreadcrumbUiThreadDispatchTests.cs:51` | `SetSuggestionsAsync_WorkerProviderCompletion_SchedulesPostOnOwningContext` | UNAFFECTED | Completes a `TaskCompletionSource`; awaited, so no wait-inlining. | +| 19 | `Viewers/BreadcrumbUiThreadDispatchTests.cs:90` | `InboundWorkerMessage_SchedulesEveryPostAndCallbackOnOwningContext` | UNAFFECTED | Awaited (no wait-inlining). Coordinator is context-backed; `Dispatch` posts whenever ambient context is not the captured one; on the test thread the ambient context is the restored `previous` (`:86`), so the outcome is identical on any thread. | +| 20 | `Viewers/BreadcrumbUiThreadDispatchTests.cs:301` | `ProductionCaptureWithoutUiContext_FailsFast` | UNAFFECTED | `DispatchValue` on an owner-only dispatcher (`CreateForCurrentThreadTests()`, `:298-299`) faults for every caller that is not inside an executing dispatcher callback (`BreadcrumbUiDispatcher.cs:166-188`); it never reads `_ownerThreadId`. The expected `InvalidOperationException` is produced on the owner thread as well as on any other thread. This corrects the second citation of Entry 1 in `docs/features/active/breadcrumb-thread-affinity-tests-assume-taskrun-distinct-thread-900/evidence/other/p5-t14-follow-up-handoff.2026-09-17T02-39.md:40-42`, which attributed this site to the owner-thread-id check. The message text "cross-thread" is broader than the mechanism; a wording-only follow-up, not a fix under #931. | +| 21 | `Viewers/ItemViewerBreadcrumbThreadAffinityTests.cs:332` | `InitializeBreadcrumbPipeline_NullOwningDispatcher_DoesNotThrow` | **AFFECTED** | The blocking `GetAwaiter().GetResult()` at `:335-336` can inline the work item onto the owner thread. The remark at `:311-317` claims the test discriminates against the pre-#781 context-reference guard; that holds only when the call is genuinely off the owner thread (on the inlined branch the ambient context equals the captured one and the pre-fix guard would admit the call). Issue #931 lists this site as remaining; the #900 handoff records the same conditionality (Entry 2, `:63-94`). | +| 22 | `Controllers/QfcItemController.UiThreadDispatcherFixtureTests.cs:222` | `Transaction_SecondCallerCannotInstallUntilTheFirstRestores` | UNAFFECTED | Asserts the value observed by a second gate acquirer (`observedByB`, `:244-254`), not any thread identity; `secondCallerStarted.Wait()` (`:239`) is a `ManualResetEventSlim` wait, not a task wait, so the body runs on a separate pool thread. Its intermittency is tracked under #823 (`:197-202`) for unrelated reasons. | + +Result: AFFECTED = {3, 21}; UNAFFECTED = the remaining 20. Of the 8 sites the issue named as candidates, 6 are UNAFFECTED (4, 5, 7, 9, 19, 20). + +### A3. AFFECTED sites: guard, in-thread assertion, apartment state + +**Site 3, `BreadcrumbPopupBoundaryCoverageTests.cs:58`** + +- Guard under test: `BreadcrumbUiDispatcher.IsCurrentBoundary()` owner-thread branch (`BreadcrumbUiDispatcher.cs:274-277`), reached from `Dispatch` (`:78`) with `_context == null`; the rejection path reports "cannot marshal" and returns without running the action (`:97-105`). +- In-thread assertion: the dispatcher exposes no `CheckAccess`. Capture `int ownerThreadId = Environment.CurrentManagedThreadId;` in Arrange (this is the value `CreateOwnerOnlyDispatcher` passes at `:133`) and assert inside the delegate, before `dispatcher.Dispatch(...)`: `Environment.CurrentManagedThreadId.Should().NotBe(ownerThreadId, "...")`. Optionally also `ReferenceEquals(Thread.CurrentThread, ownerThread).Should().BeFalse()`. +- Apartment state: none required. No control is touched; a null-context dispatcher never posts. Use the same background MTA thread shape as the canonical helper. +- After the rewrite the existing assertions (`executions == 0`, one error containing "cannot marshal") stay unchanged. + +**Site 21, `ItemViewerBreadcrumbThreadAffinityTests.cs:332`** + +- Guard under test: the null-owner escape of `ThrowIfOffUiBoundary` (`ItemViewer.Breadcrumb.cs:434-438`), entered from `InitializeBreadcrumbPipeline` (`:51`); the call then takes the already-initialized early return (`:60-72`). +- In-thread assertion: `scope.Viewer.UiDispatcher` is null after `ClearViewerDispatcher` (`:361-371`), so capture the owner first: `Dispatcher owner = scope.Viewer.UiDispatcher;` before `ClearViewerDispatcher(scope.Viewer);`, then inside the delegate assert `owner.CheckAccess().Should().BeFalse("...")` before the guarded call. This is the same `Thread`-identity proof used at `:230-236`. +- Apartment state: none required. The call path touches no control (early return); the two existing dedicated-thread tests call the same member without STA. +- The `Action act` / `NotThrow` shape can be preserved by asserting `RunOnDedicatedWorkerThread(...)` returned null, plus `BreadcrumbCoordinator.Should().BeSameAs(before)`. + +### A4. Demonstrating failure against a deliberately broken guard + +Precedent: #900 recorded two reverted, test-only mutations with before/after census and TRX-derived summaries (`.../evidence/regression-testing/p3-t1-mutation-guard-disabled.2026-09-17T02-24.md`, `p3-t3-mutation-inline-precondition.2026-09-17T02-25.md`), each followed by a revert record. The same evidence discipline applies here: record the mutated file's line count and token census, build, run the scoped test with a `/TestCaseFilter`, record the TRX-derived summary, revert with `git checkout -- `, and prove the revert with `git status --porcelain` showing no residual change to that file. + +**Site 3** + +- Test seam or fake: none. `BreadcrumbUiDispatcher` is `sealed`, `IsCurrentBoundary` is private, and every argument combination of the reflected 3-argument constructor yields a correct guard (null owner and null context -> reports; a different owner id -> a different owner, not a broken check). +- M1 (temporary production edit, reverted in the same task): replace `BreadcrumbUiDispatcher.cs:276-277` with `return true;`. `Dispatch` then runs the action inline on the worker thread: `executions == 1` and `errors` is empty, so the rewritten test fails at `executions.Should().Be(0)`. Expected failure text: "Expected executions to be 0, but found 1". +- M2 (test-only, reverted): insert `action();` as the first statement of the dedicated-thread helper, exactly as #900 P3-T3 did. The in-thread precondition fails ("Expected ... not to be "), proving the precondition is live. +- Fallback without any production edit: construct the owner-only dispatcher inside the dedicated thread (owner = worker). `Dispatch` then runs inline and the same assertions fail. This models a wrong owner rather than a broken check, so M1 is preferred when a temporary production edit is acceptable. +- Feasibility: M1 and M2 both feasible; M1 requires the temporary production edit the delegation permits. + +**Site 21** + +- Test seam or fake: none for the escape itself. `ClearViewerDispatcher` (reflection on `_uiDispatcher`) is the existing seam that *selects* the escape; it cannot disable it. #900's P3-T1 mutation (inserting `ClearViewerDispatcher` into the throw-tests) is not applicable here because the escape is the branch under test. +- M1 (temporary production edit, reverted): at `ItemViewer.Breadcrumb.cs:435-438` replace the bare `return;` with the pre-#781 context-reference check (`if (!ReferenceEquals(SynchronizationContext.Current, UiSyncContext)) throw new InvalidOperationException(...)`). On the dedicated thread the ambient context is null and differs from `UiSyncContext`, so the guard throws and the rewritten test fails at `NotThrow`. On the owner thread (the inlined branch of the old shape) the ambient context equals `scope.Context`, so the old test would have passed; this is the discrimination the rewrite restores and the remark at `:311-317` can then state unconditionally. +- M1-alt: delete the null check so `owning.CheckAccess()` dereferences null. Fails on any thread with `NullReferenceException`; proves the escape is reached but not the thread dependence. Use only as a supplement. +- M2 (test-only, reverted): inline `action();` in the helper; `owner.CheckAccess()` is then true and the `BeFalse` precondition fails with the "vacuously" reason text, as in P3-T3. +- Feasibility: M1, M1-alt and M2 all feasible. + +### A5. `ItemViewerBreadcrumbThreadAffinityTests.cs`: structure, split, line counts, csproj + +Current file: **490 lines**, one `[TestClass] public sealed class ItemViewerBreadcrumbThreadAffinityTests` (`:29-30`), namespace `QuickFiler.Test.Viewers`. + +| Lines | Member | Grouping | +|---|---|---| +| 39-75 | `InitializeBreadcrumbPipeline_ConstructedInsideDispatcherOperation_SucceedsUnderDifferentAmbientContext` | owner-thread admission | +| 89-122 | `InitializeBreadcrumbPipeline_OwningThreadNullAmbientContext_DoesNotThrow` | owner-thread admission | +| 129-160 | `InitializeBreadcrumbPipeline_OwningThreadDifferentPlainContext_DoesNotThrow` | owner-thread admission | +| 168-197 | `ConfigureBreadcrumbDropDown_OwningThreadInsideDispatcherOperation_DoesNotThrow` | owner-thread admission | +| 219-251 | `InitializeBreadcrumbPipeline_WorkerThread_ThrowsBoundaryDiagnostic` | cross-thread (dedicated thread) | +| 269-304 | `ConfigureBreadcrumbDropDown_WorkerThread_ThrowsBoundaryDiagnostic` | cross-thread (dedicated thread) | +| 319-346 | `InitializeBreadcrumbPipeline_NullOwningDispatcher_DoesNotThrow` | cross-thread (site 21, to be rewritten) | +| 352-355 | `InertOperations()` | shared helper | +| 361-371 | `ClearViewerDispatcher()` | used only by the null-owner test | +| 385-403 | `RunOnDedicatedWorkerThread()` | used only by the cross-thread tests | +| 406-434 | `InertDropDownHost` (nested) | used by tests at 176 and 274 | +| 442-461 | `DrainableSynchronizationContext` (nested) | used by `InertOperations` | +| 467-488 | `ViewerScope` (nested) | used by all tests except the first | + +Proposed split (repository convention for continuation partials: `*.Part2.cs`, e.g. `BreadcrumbPopupBoundaryCoverageTests.Part2.cs:17,23`, and 16 further `.Part2.cs` entries in `QuickFiler.Test.csproj`): + +- Make the class `partial`. Primary file `Viewers/ItemViewerBreadcrumbThreadAffinityTests.cs` keeps the four owner-thread admission tests (lines 1-197), `InertOperations` (348-355), and the three nested types (405-488). Estimated **about 290 lines**. +- New file `Viewers/ItemViewerBreadcrumbThreadAffinityTests.Part2.cs` holds the three cross-thread tests (199-346) and `ClearViewerDispatcher` (357-371). With the rewritten null-owner test (about +12 lines) and a file header (about 16 lines) the estimate is **about 200 lines**; about 230 if `RunOnDedicatedWorkerThread` stays in it. Private nested types and private static helpers remain accessible across partial declarations, so no accessibility is widened. +- `RunOnDedicatedWorkerThread` is needed by site 3 in another class. Recommended: move it to a new internal static helper `QuickFiler.Test/TestSupport/DedicatedWorkerThread.cs` (namespace `QuickFiler.Test.TestSupport`, matching `TestSupport/WinFormsPumpHost.cs:9`), keeping the same contract (`Exception Run(Action)`), and optionally adding a built-in generic precondition (`ReferenceEquals(Thread.CurrentThread, caller)` must be false) in addition to the site-specific precondition each test keeps in its delegate. Estimated 40-60 lines. + +`QuickFiler.Test.csproj` uses explicit `` entries. Existing entry for the file: `QuickFiler.Test/QuickFiler.Test.csproj:98` (``). Neighbouring entries for the other candidate files: `:77-79` (`BreadcrumbUiThreadDispatchTests.cs`, `BreadcrumbSelectorToggleUiBoundaryTests.cs`, `BreadcrumbPopupControlDispatchTests.cs`), `:105-106` (`BreadcrumbPopupBoundaryCoverageTests.cs`, `.Part2.cs`); `TestSupport` entries at `:227-228`. + +Line counts of every file the rewrite would edit: + +| File | Now | After (estimate) | Under 500 | +|---|---|---|---| +| `QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.cs` | 490 | about 290 | yes | +| `QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.Part2.cs` (new) | 0 | about 200-230 | yes | +| `QuickFiler.Test/TestSupport/DedicatedWorkerThread.cs` (new) | 0 | about 40-60 | yes | +| `QuickFiler.Test/Viewers/BreadcrumbPopupBoundaryCoverageTests.cs` | 361 | about 375 (site 3 rewrite) | yes | +| `UtilitiesCS.Test/HelperClasses/FileInfoWrapper_Tests.cs` | 359 | about 355-370 (`GetSolutionFile` removed, seam test added) | yes | +| `QuickFiler.Test/QuickFiler.Test.csproj` | build file | +2 `` entries | not subject to the code-file limit | + +Not edited (for reference): `BreadcrumbPopupControlDispatchTests.cs` 486, `BreadcrumbPopupBoundaryCoverageTests.Part2.cs` 483, `BreadcrumbSelectorToggleUiBoundaryTests.cs` 478, `BreadcrumbUiThreadDispatchTests.cs` 480. Each of these has fewer than 25 lines of headroom, which is a further reason not to classify their sites as affected without necessity and not to add helpers to them. + +--- + +## B. File-handle defect (issue #906 portion) + +### B6. The `TaskMaster.sln` fixture in `UtilitiesCS.Test/HelperClasses/FileInfoWrapper_Tests.cs` + +Test (current lines 55-67; the issue cites 56-62): + +```csharp +[TestMethod] +public void OpenRead_ShouldReturnReadableStreamForWrappedFile() +{ + // Arrange + var wrapper = new FileInfoWrapper(GetSolutionFile()); + + // Act + using var stream = wrapper.OpenRead(); + + // Assert + stream.CanRead.Should().BeTrue(); + stream.Length.Should().BeGreaterThan(0); +} +``` + +Helper (current lines 339-357; the issue cites 340-352): + +```csharp +private static FileInfo GetSolutionFile() +{ + var current = new DirectoryInfo(AppDomain.CurrentDomain.BaseDirectory); + + while (current is not null) + { + var solutionPath = Path.Combine(current.FullName, "TaskMaster.sln"); + if (File.Exists(solutionPath)) + { + return new FileInfo(solutionPath); + } + + current = current.Parent; + } + + throw new InvalidOperationException( + "The TaskMaster solution file could not be located from the test assembly path." + ); +} +``` + +Tests in this file that use `GetSolutionFile()` (all through the public `FileInfoWrapper(FileInfo)` constructor): + +| Lines | Test | Call site | Real-file dependency | +|---|---|---|---| +| 26-39 | `Properties_ShouldMirrorWrappedFileInfo` | `:29` | Reads `Exists`, `FullName`, `Name`, `Extension`, `DirectoryName`, `Directory.FullName` of `TaskMaster.sln` (metadata only, no handle). | +| 42-53 | `ExplicitDirectoryCast_ShouldReturnWrappedContainingDirectory` | `:45` | Path-derived only. | +| 55-67 | `OpenRead_ShouldReturnReadableStreamForWrappedFile` | `:59` | **Opens a read handle on `TaskMaster.sln`** (`FileInfo.OpenRead()` requests `FileShare.Read`); this is the #906 failure (IOException observed once in the #900 run: `p5-t14-follow-up-handoff...md:157-164`). | +| 70-81 | `ToString_ShouldDelegateToWrappedFileInfo` | `:73` | Path-derived only. | + +Other real-file use in the same file: `StreamAndCopyMethods_ShouldDelegateToWrappedIFileInfo` (184-295) opens the test's own loaded assembly six times (`typeof(FileInfoWrapper_Tests).Assembly.Location`, `FileMode.Open`, `FileAccess.Read`, `FileShare.ReadWrite`; lines 189-224) purely as identity sentinels returned by a strict `Mock`. This is a build output the test host already holds mapped, opened with a sharing mode that admits every other read or write holder; it is not a repository source file and it is not the #906 defect. The same shape is the repository's stated precedent (`UtilitiesCS.Test/HelperClasses/PhysicalFileSystemAdapters_Tests.cs:247-248, 258-275`). + +Out of #931 scope but the same defect class (record as follow-up potential entries, do not fix here): `PhysicalFileSystemAdapters_Tests.cs:173` and `:318` (`GetSolutionFile()`), `:186-198` (a `catch (IOException)` that swallows contention), `:213-234` (read opens on the real `.sln`); `DirectoryInfoWrapper_Tests.cs:60, 79, 381` (asserts that `TaskMaster.sln` is enumerated from the repository root). + +### B7. Existing seam in production (no production change required) + +- `UtilitiesCS/HelperClasses/FileSystem/FileInfoWrapper.cs:14-24`: public `FileInfoWrapper(FileInfo)` wraps a `PhysicalFileInfoAdapter`; `internal FileInfoWrapper(IFileInfo fileInfo)` (`:21-24`) is the seam. Every member of the wrapper delegates to `_fileInfo` (`:26-209`); `OpenRead()` is `:153-156`. +- Visibility: `UtilitiesCS/Properties/AssemblyInfo.cs:19` `[assembly: InternalsVisibleTo("UtilitiesCS.Test")]` (also `DynamicProxyGenAssembly2` at `:18`, so Moq can proxy internal members). +- The seam is already exercised in this file at `:139`, `:271`, `:322` (`new FileInfoWrapper(fileInfo.Object)`), including `OpenRead` delegation at `:263` and `:287` (`wrapper.OpenRead().Should().BeSameAs(openReadStream)`). +- Second, narrower seam one level down: `PhysicalFileInfoAdapter`'s internal delegate constructor (`UtilitiesCS/HelperClasses/FileSystem/PhysicalFileInfoAdapter.cs:34-48`) seams `AppendText`, `Open(FileMode)`, `Open(FileMode, FileAccess)` and `OpenWrite` only (`:118, :146-149, :158`); `OpenRead()` is unseamed (`:154`). Therefore `OpenRead` cannot be exercised through the public `FileInfoWrapper(FileInfo)` path without a real file; through the `IFileInfo` seam it can. +- Prior art searched: `PhysicalFileInfoAdapter` (production, above, and `PhysicalFileSystemAdapters_Tests.cs:170-311`), "sentinel" (`PhysicalFileSystemAdapters_Tests.cs:244-282`, `Bootstrap/AssemblyBindingFallbackTests.cs:28-29`), injectable `Func<>` seams (`UtilitiesCS/OneDriveHelpers/OneDriveDownloader.cs:106-133`). + +Conclusion: the issue's condition "add a seam to the wrapper only if one does not already exist" resolves to **no seam addition**. + +### B8. Replacement design and what each rewritten test verifies + +Type constraint: `IFileInfo.OpenRead()`, `Open(...)`, `Create()`, `OpenWrite()` return the concrete `FileStream` (`IFileInfo.cs:16, 23-26, 28`). A `MemoryStream` is not a `FileStream`, so an in-memory stream cannot be returned through the seam for these members. `MemoryStream` is usable only behind `StreamReader`/`StreamWriter` members (`AppendText`, `CreateText`, `OpenText`, `:13, 17, 27`), which the existing test already does at `:186-188`. A pipe-backed `FileStream` is rejected as an option because it is non-seekable and the test asserts `Length`. + +Recommended rewrite (all four tests keep their names and scenario intent; `GetSolutionFile()` and the `AppDomain` walk-up are deleted): + +1. `Properties_ShouldMirrorWrappedFileInfo`, `ExplicitDirectoryCast_ShouldReturnWrappedContainingDirectory`, `ToString_ShouldDelegateToWrappedFileInfo`: construct `new FileInfo(@"C:\Repo\fixture.sln")` (style matches the rooted literals already used at `:87, :113`). `FileInfo` construction, `FullName`, `Name`, `Extension`, `DirectoryName`, `Directory`, and `ToString()` are path computations with no handle; `Exists` is a metadata query with no handle, and the assertion mirrors `file.Exists` so it is deterministic either way. Do not assert `Length` on this fixture (it throws for a missing file). What is verified: the public constructor path `FileInfoWrapper(FileInfo)` -> `PhysicalFileInfoAdapter` (`FileInfoWrapper.cs:14-19`; adapter `:25-32, 68-108, 181`) and the explicit `DirectoryInfoWrapper` cast (`:211-214`), which the mocked tests cannot reach because a `Mock` is not a `DirectoryInfoWrapper`. This keeps coverage of `FileInfoWrapper.cs:18` and the adapter's property lines without any file handle. +2. `OpenRead_ShouldReturnReadableStreamForWrappedFile`: construct the wrapper through the seam with a strict `Mock` whose `OpenRead()` returns a test-owned `FileStream` opened on `typeof(FileInfoWrapper_Tests).Assembly.Location` with `FileMode.Open`, `FileAccess.Read`, `FileShare.ReadWrite` (the pattern at `:189-224` and `PhysicalFileSystemAdapters_Tests.cs:258-275`), disposed by `using`. Assert `wrapper.OpenRead().Should().BeSameAs(sentinel)`, `CanRead` true and `Length > 0`. What is verified: the wrapper's own behaviour is delegation (`FileInfoWrapper.cs:153-156`); identity of the returned instance is the complete contract, and the readability assertions confirm the returned object is the live stream. No repository source file and no temporary file is involved. This test then overlaps `:263/:287`; deleting it instead would lose no production coverage (line 155 stays covered) and is an acceptable alternative if the reviewer prefers fewer tests, but the rewrite preserves the named scenario the issue cites. + +Rejected alternatives: temporary files (prohibited by policy); adding an `OpenRead` delegate to `PhysicalFileInfoAdapter` (a production change the issue forbids when a seam exists); asserting through `PhysicalFileInfoAdapter` directly (that adapter's own tests are out of scope). + +--- + +## C. Toolchain facts + +### C9. Project files + +- `QuickFiler.Test/QuickFiler.Test.csproj` (explicit `` model) gains two entries: `Viewers\ItemViewerBreadcrumbThreadAffinityTests.Part2.cs` (adjacent to `:98`) and `TestSupport\DedicatedWorkerThread.cs` (adjacent to `:227-228`). Files present on disk but absent from the csproj are not compiled, so a missing entry fails silently at the test-count level; the executor should confirm the new test names appear in the TRX-derived summary. +- `UtilitiesCS.Test/UtilitiesCS.Test.csproj` also uses explicit entries (`:232-234` list `DirectoryInfoWrapper_Tests.cs`, `PhysicalFileSystemAdapters_Tests.cs`, `FileInfoWrapper_Tests.cs`). The recommended rewrite creates no new file there, so no entry is needed. + +### C10. Parallel regime and `[DoNotParallelize]` + +- `TaskMaster.runsettings:3-8`: `0ClassLevel` (plus a Code Coverage collector configuration at `:9-29`). `scripts/vscode/TaskMaster.cli.runsettings:3-8` carries the same `Workers=0`, `Scope=ClassLevel` without the collector (this is the file the #900 evidence ran under). +- `[DoNotParallelize]` today (grep over `*.cs`): none on `ItemViewerBreadcrumbThreadAffinityTests`, `BreadcrumbPopupBoundaryCoverageTests` (either partial), `BreadcrumbPopupControlDispatchTests`, `BreadcrumbSelectorToggleUiBoundaryTests`, `BreadcrumbUiThreadDispatchTests`, `BreadcrumbSelectorOpenRetryTests`, `BreadcrumbCoordinatorLifecycleTests`, `QfcItemController.UiThreadDispatcherFixtureTests`, or `FileInfoWrapper_Tests`. In `QuickFiler.Test` it appears only on `Helper Classes/EmailMoveMonitorTests.cs:24` and `Helper Classes/ViewerQueueStaticWrapperTests.cs:11` (both unaffected by this issue; leave untouched). The fix must add none. + +--- + +## D. Candidate approaches and recommendation + +**Approach 1 (recommended): dedicated-thread helper shared through `TestSupport`, partial split of the affinity file, seam-only rewrite of the file tests.** +Advantages: one implementation of the #900 helper reused at both affected sites (no copy-paste); the affinity file lands near 290 lines with headroom; no production code changes anywhere; matches the issue's stated fix and existing `.Part2.cs`/`TestSupport` conventions. Limitations: two new files and two csproj entries. + +**Approach 2 (rejected): duplicate the private helper into `BreadcrumbPopupBoundaryCoverageTests.cs` and split the affinity file.** Rejected for copy-paste of a 19-line helper whose rationale comment is the load-bearing part. + +**Approach 3 (rejected): move the helper out and skip the split.** The primary file would land near 475 lines, below the "with headroom" bar and contrary to the issue's stated fix. + +**Approach 4 (rejected): rewrite all eight cited candidate sites.** Six of them do not exercise a thread-identity guard (A2); rewriting them adds dedicated threads to files with fewer than 25 lines of headroom for no discriminating benefit. + +## E. Behaviour semantics and requirements mapping + +- AC "apply the #900 pattern at every triaged site": sites 3 and 21 only; record the UNAFFECTED verdicts for the six other candidates with the reasons in A2 so the reviewer can see the triage. +- AC "split and register": Part2 partial plus `TestSupport/DedicatedWorkerThread.cs`, both registered (C9). +- AC "replace the `.sln` fixture; add a seam only if none exists": B7/B8; no production edit. +- AC "each rewritten test shown to fail against a deliberately broken guard": A4 gives M1/M2 per site, each reverted in the same task with census and `git status --porcelain` evidence, never committed. For the file test the analogous demonstration is: point the mock's `OpenRead()` at a second sentinel (or make it throw) and observe `BeSameAs` fail, then restore. +- AC "run the full `QuickFiler.Test` and `UtilitiesCS.Test` suites in the parallel regime": run under `/Settings:TaskMaster.runsettings` (or the CLI twin) with `/InIsolation`, summarise from the TRX; no `Workers=1`, `[DoNotParallelize]`, or retry is permitted. +- Files changed: `QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.cs` (make partial, remove cross-thread tests and helpers), new `...ThreadAffinityTests.Part2.cs`, new `QuickFiler.Test/TestSupport/DedicatedWorkerThread.cs`, `QuickFiler.Test/Viewers/BreadcrumbPopupBoundaryCoverageTests.cs:52-62`, `QuickFiler.Test/QuickFiler.Test.csproj`, `UtilitiesCS.Test/HelperClasses/FileInfoWrapper_Tests.cs:25-81, 339-357`. No file under `QuickFiler/` or `UtilitiesCS/` changes. + +## F. Testing implications + +- The rewritten tests are deterministic under `Workers=0`/`ClassLevel`: a constructed `Thread` is distinct from every live thread; the in-thread precondition makes an inlined or same-thread execution fail loudly instead of passing vacuously (site 21) or failing spuriously (site 3). +- No wall-clock waits are introduced: `Thread.Join()` is a completion wait on one bounded synchronous call (rationale at `ItemViewerBreadcrumbThreadAffinityTests.cs:377-384`). +- Coverage: production lines touched by these tests are unchanged in count; `FileInfoWrapper.cs:18, 44-78, 153-156, 211-214` remain covered by the rewritten tests. New test-support code is excluded from the production denominator. +- Evidence to commit: TRX-derived summaries for the mutation runs and the final parallel suite runs, the JaCoCo projection and one-line coverage summary; never the raw TRX or Cobertura documents (CLAUDE.md "Committed Test Evidence Format"). +- Follow-ups to promote as potential entries, not fixed here: the `.sln` usages in `PhysicalFileSystemAdapters_Tests.cs` and `DirectoryInfoWrapper_Tests.cs` (B6); the "cross-thread" wording at `BreadcrumbUiThreadDispatchTests.cs:305` (A2 row 20); the mischaracterised second citation in the #900 handoff Entry 1. + +## Numeric Derivation Evidence + +- Complete Family: EmailMoveMonitorTests.cs:298, BreadcrumbCoordinatorLifecycleTests.cs:350, BreadcrumbPopupBoundaryCoverageTests.cs:58, BreadcrumbPopupControlDispatchTests.cs:29, BreadcrumbPopupControlDispatchTests.cs:111, BreadcrumbPopupControlDispatchTests.cs:300, BreadcrumbPopupBoundaryCoverageTests.Part2.cs:192, BreadcrumbSelectorToggleUiBoundaryTests.cs:61, BreadcrumbSelectorToggleUiBoundaryTests.cs:75, BreadcrumbSelectorToggleUiBoundaryTests.cs:137, BreadcrumbSelectorToggleUiBoundaryTests.cs:148, BreadcrumbSelectorToggleUiBoundaryTests.cs:154, BreadcrumbSelectorToggleUiBoundaryTests.cs:161, BreadcrumbSelectorOpenRetryTests.cs:37, BreadcrumbSelectorOpenRetryTests.cs:210, BreadcrumbSelectorOpenRetryTests.cs:218, BreadcrumbSelectorOpenRetryTests.cs:219, BreadcrumbUiThreadDispatchTests.cs:51, BreadcrumbUiThreadDispatchTests.cs:90, BreadcrumbUiThreadDispatchTests.cs:301, ItemViewerBreadcrumbThreadAffinityTests.cs:332, QfcItemController.UiThreadDispatcherFixtureTests.cs:222 +- Exhaustive Search Scope: the entire `QuickFiler.Test` source tree of the repository (every file under `QuickFiler.Test/`, all subdirectories: `Controllers/`, `Helper Classes/`, `TestSupport/`, `Viewers/`, and any other), searched with ripgrep from the directory root with no path filter beyond the tree itself; both strategies were run over this whole tree. +- Inclusion Rules: a line of C# code in which the identifier `Task.Run` is invoked (followed by `(` or a generic argument list), whether the result is awaited, blocked on, stored, or discarded, and whether inside a test method or a helper; every overload of `Task.Run` (`Action`, `Func`, `Func`, `Func>`, with or without a `CancellationToken`) is in the family. +- Exclusion Rules: (a) occurrences inside XML documentation or `//` comments (`ItemViewerBreadcrumbThreadAffinityTests.cs:205, 260, 378`; `QfcItemController.FolderHandlingTests.Part2.cs:315`; `QfcItemController.EventHandlersTests.cs:261`; `QfcItemControllerTests.cs:62`); (b) occurrences inside string literals (`QfcItemController.FolderHandlingTests.Part2.cs:351`); (c) substring matches on other identifiers (`flagTask.Run(` at `QfcItemController.EventHandlersTests.cs:261`, `TimeOutTask.RunWithTimeout` at `QfcItemControllerTests.cs:62`); (d) sibling APIs that are not `Task.Run` (`Task.Factory.StartNew`, `ThreadPool.QueueUserWorkItem`, `ThreadPool.UnsafeQueueUserWorkItem`: a grep over the same tree returned zero matches, so no sibling site exists to exclude). +- Primary Search Strategy: ripgrep regex `Task\.Run` over every file under `QuickFiler.Test/` (no glob, no type filter, unlimited results), returning 29 matching lines, then applying exclusion rules (a)-(c) by reading each line; retained members: EmailMoveMonitorTests.cs:298, BreadcrumbCoordinatorLifecycleTests.cs:350, BreadcrumbPopupBoundaryCoverageTests.cs:58, BreadcrumbPopupControlDispatchTests.cs:29, BreadcrumbPopupControlDispatchTests.cs:111, BreadcrumbPopupControlDispatchTests.cs:300, BreadcrumbPopupBoundaryCoverageTests.Part2.cs:192, BreadcrumbSelectorToggleUiBoundaryTests.cs:61, BreadcrumbSelectorToggleUiBoundaryTests.cs:75, BreadcrumbSelectorToggleUiBoundaryTests.cs:137, BreadcrumbSelectorToggleUiBoundaryTests.cs:148, BreadcrumbSelectorToggleUiBoundaryTests.cs:154, BreadcrumbSelectorToggleUiBoundaryTests.cs:161, BreadcrumbSelectorOpenRetryTests.cs:37, BreadcrumbSelectorOpenRetryTests.cs:210, BreadcrumbSelectorOpenRetryTests.cs:218, BreadcrumbSelectorOpenRetryTests.cs:219, BreadcrumbUiThreadDispatchTests.cs:51, BreadcrumbUiThreadDispatchTests.cs:90, BreadcrumbUiThreadDispatchTests.cs:301, ItemViewerBreadcrumbThreadAffinityTests.cs:332, QfcItemController.UiThreadDispatcherFixtureTests.cs:222 (7 lines excluded: 205, 260, 378 of ItemViewerBreadcrumbThreadAffinityTests.cs; 315, 351 of QfcItemController.FolderHandlingTests.Part2.cs; 261 of QfcItemController.EventHandlersTests.cs; 62 of QfcItemControllerTests.cs) +- Primary Member Set: EmailMoveMonitorTests.cs:298, BreadcrumbCoordinatorLifecycleTests.cs:350, BreadcrumbPopupBoundaryCoverageTests.cs:58, BreadcrumbPopupControlDispatchTests.cs:29, BreadcrumbPopupControlDispatchTests.cs:111, BreadcrumbPopupControlDispatchTests.cs:300, BreadcrumbPopupBoundaryCoverageTests.Part2.cs:192, BreadcrumbSelectorToggleUiBoundaryTests.cs:61, BreadcrumbSelectorToggleUiBoundaryTests.cs:75, BreadcrumbSelectorToggleUiBoundaryTests.cs:137, BreadcrumbSelectorToggleUiBoundaryTests.cs:148, BreadcrumbSelectorToggleUiBoundaryTests.cs:154, BreadcrumbSelectorToggleUiBoundaryTests.cs:161, BreadcrumbSelectorOpenRetryTests.cs:37, BreadcrumbSelectorOpenRetryTests.cs:210, BreadcrumbSelectorOpenRetryTests.cs:218, BreadcrumbSelectorOpenRetryTests.cs:219, BreadcrumbUiThreadDispatchTests.cs:51, BreadcrumbUiThreadDispatchTests.cs:90, BreadcrumbUiThreadDispatchTests.cs:301, ItemViewerBreadcrumbThreadAffinityTests.cs:332, QfcItemController.UiThreadDispatcherFixtureTests.cs:222 +- Primary Count: 22 +- Cross-check Search Strategy: ripgrep regex `(^|[^A-Za-z0-9_.])Task\.Run\s*[(<]` with glob `*.cs` over every file under `QuickFiler.Test/` (unlimited results; the leading character class rejects identifier-suffix matches such as `flagTask.Run(` and the trailing class requires an invocation or generic list, which also rejects `TimeOutTask.RunWithTimeout`), returning 27 matching lines, then applying exclusion rules (a)-(b) by reading each line; retained members: EmailMoveMonitorTests.cs:298, BreadcrumbCoordinatorLifecycleTests.cs:350, BreadcrumbPopupBoundaryCoverageTests.cs:58, BreadcrumbPopupControlDispatchTests.cs:29, BreadcrumbPopupControlDispatchTests.cs:111, BreadcrumbPopupControlDispatchTests.cs:300, BreadcrumbPopupBoundaryCoverageTests.Part2.cs:192, BreadcrumbSelectorToggleUiBoundaryTests.cs:61, BreadcrumbSelectorToggleUiBoundaryTests.cs:75, BreadcrumbSelectorToggleUiBoundaryTests.cs:137, BreadcrumbSelectorToggleUiBoundaryTests.cs:148, BreadcrumbSelectorToggleUiBoundaryTests.cs:154, BreadcrumbSelectorToggleUiBoundaryTests.cs:161, BreadcrumbSelectorOpenRetryTests.cs:37, BreadcrumbSelectorOpenRetryTests.cs:210, BreadcrumbSelectorOpenRetryTests.cs:218, BreadcrumbSelectorOpenRetryTests.cs:219, BreadcrumbUiThreadDispatchTests.cs:51, BreadcrumbUiThreadDispatchTests.cs:90, BreadcrumbUiThreadDispatchTests.cs:301, ItemViewerBreadcrumbThreadAffinityTests.cs:332, QfcItemController.UiThreadDispatcherFixtureTests.cs:222 (5 lines excluded: 205, 260, 378 of ItemViewerBreadcrumbThreadAffinityTests.cs, matched through the `<` of `` in XML remarks; 315, 351 of QfcItemController.FolderHandlingTests.Part2.cs) +- Cross-check Member Set: EmailMoveMonitorTests.cs:298, BreadcrumbCoordinatorLifecycleTests.cs:350, BreadcrumbPopupBoundaryCoverageTests.cs:58, BreadcrumbPopupControlDispatchTests.cs:29, BreadcrumbPopupControlDispatchTests.cs:111, BreadcrumbPopupControlDispatchTests.cs:300, BreadcrumbPopupBoundaryCoverageTests.Part2.cs:192, BreadcrumbSelectorToggleUiBoundaryTests.cs:61, BreadcrumbSelectorToggleUiBoundaryTests.cs:75, BreadcrumbSelectorToggleUiBoundaryTests.cs:137, BreadcrumbSelectorToggleUiBoundaryTests.cs:148, BreadcrumbSelectorToggleUiBoundaryTests.cs:154, BreadcrumbSelectorToggleUiBoundaryTests.cs:161, BreadcrumbSelectorOpenRetryTests.cs:37, BreadcrumbSelectorOpenRetryTests.cs:210, BreadcrumbSelectorOpenRetryTests.cs:218, BreadcrumbSelectorOpenRetryTests.cs:219, BreadcrumbUiThreadDispatchTests.cs:51, BreadcrumbUiThreadDispatchTests.cs:90, BreadcrumbUiThreadDispatchTests.cs:301, ItemViewerBreadcrumbThreadAffinityTests.cs:332, QfcItemController.UiThreadDispatcherFixtureTests.cs:222 +- Cross-check Count: 22 +- Member-set Comparison: the two sets are identical (22 members each, same files and line numbers, no member present in one set and absent from the other); the two strategies differ in their regular expression, their file filter, and the number of raw lines they returned (29 versus 27) and converge on the same member set after the declared exclusions, so the family count of 22 and the AFFECTED subset count of 2 (BreadcrumbPopupBoundaryCoverageTests.cs:58, ItemViewerBreadcrumbThreadAffinityTests.cs:332) are asserted. diff --git a/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/spec.md b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/spec.md new file mode 100644 index 000000000..0f8c77af7 --- /dev/null +++ b/docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/spec.md @@ -0,0 +1,279 @@ +# 2026-09-28-tests-depend-on-uncontrolled-environment (Spec) + +- **Issue:** #931 (consolidates #905 and #906) +- **Parent (optional):** none +- **Owner:** drmoisan +- **Last Updated:** 2026-09-28T20-45 +- **Status:** Ready for atomic planning +- **Version:** 1.0 +- **Work Mode:** full-bug. This file is the sole authoritative acceptance-criteria source for this feature. No user-story.md exists or is required. +- **Research record:** docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/research/2026-09-28T20-15-tests-depend-on-uncontrolled-environment-research.md (authoritative for site triage, line numbers, seam analysis and the split). Every line citation below was re-verified against the current tree of this worktree on 2026-09-28. + +> Formatting contract for the blast-radius scheduler: the only backtick-delimited repository paths in this document are the entries under `## Write Set`. Every other file reference, including the unaffected triaged sites, the production files cited for the guards, the runsettings file, the sibling defect sites, and the research and evidence artifacts, is written in plain prose without backticks. Backticked tokens elsewhere are C# identifiers, attribute names, or the mandated toolchain command strings from CLAUDE.md (which contain spaces and are not harvestable). Do not "fix" this formatting. + +## Context + +Two consolidated defects share one root cause: a unit test depends on environment state it does not control, so its outcome is decided by the scheduler or by other processes rather than by the code under test. + +**Distinct-thread defect (from #905).** Tests use `Task.Run` to obtain "another thread". `Task.Run` guarantees a thread-pool work item, not a different thread from the caller. When the test itself runs on a thread-pool thread, which is the case for every test under the parallel run configured in TaskMaster.runsettings (Workers zero, Scope ClassLevel), a blocking wait on the work item's task can execute the work item inline on the waiting thread. A guard that decides by thread identity is then either never exercised (it admits the call) or reports a result the test did not intend. PR #904 (issue #900) replaced two such sites in QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.cs with a dedicated, joined `Thread` and an in-thread distinctness precondition. The issue listed one remaining site (line 332 of that file) and six candidate sites to triage: BreadcrumbSelectorToggleUiBoundaryTests.cs line 75; BreadcrumbPopupControlDispatchTests.cs lines 29 and 111; BreadcrumbPopupBoundaryCoverageTests.cs line 58; BreadcrumbPopupBoundaryCoverageTests.Part2.cs line 192; BreadcrumbUiThreadDispatchTests.cs lines 90 and 301 (all under QuickFiler.Test/Viewers). The research enumerated every `Task.Run` call site in QuickFiler.Test (22 sites; derivation in the research record's Numeric Derivation Evidence section) and classified exactly two as AFFECTED: BreadcrumbPopupBoundaryCoverageTests.cs line 58 and ItemViewerBreadcrumbThreadAffinityTests.cs line 332. The remaining 20, including the other six candidates, either only complete a `TaskCompletionSource` or exercise a guard that decides by `SynchronizationContext` reference identity or by an executing-callback marker, so the identity of the calling thread cannot change their outcome. + +**File-handle defect (from #906).** Four tests in UtilitiesCS.Test/HelperClasses/FileInfoWrapper_Tests.cs (lines 25 to 81) obtain the repository's own TaskMaster.sln through a private `GetSolutionFile()` helper (lines 339 to 357) that walks up from `AppDomain.CurrentDomain.BaseDirectory`. One of them, `OpenRead_ShouldReturnReadableStreamForWrappedFile` (lines 55 to 67), opens a read handle on that file with the default `FileShare.Read` sharing mode of `FileInfo.OpenRead()`. Any other process that holds the solution file with a share mode that excludes readers (resident MSBuild node-reuse workers, an IDE, a hook) makes the open throw `IOException`. The outcome depends on build history, not on the wrapper. An `IOException` from exactly this test was recorded once during the #900 run. + +Environment: +- OS/version: Windows 11 Pro 10.0.26200 +- Runtime: C#, MSTest, net48; Moq and FluentAssertions +- Command/flags used: parallel regime through the TaskMaster.runsettings file at the repository root (Workers zero, Scope ClassLevel); the CLI twin under scripts/vscode carries the same two values +- Data source or fixture: files listed above, main at 177b6d78e + +Impact / Severity: +- [ ] Blocker +- [ ] High +- [x] Medium +- [ ] Low + +## Repro & Evidence + +Steps to Reproduce (static, verified on 2026-09-28 in this worktree): +1. Open QuickFiler.Test/Viewers/BreadcrumbPopupBoundaryCoverageTests.cs at lines 52 to 62. `CreateOwnerOnlyDispatcher` (lines 124 to 134) builds a `BreadcrumbUiDispatcher` with a null context and the test thread's managed id as owner. The test queues `dispatcher.Dispatch(...)` through `Task.Run` (line 58) and blocks on it (line 59), then asserts the action did not run and one "cannot marshal" error was reported. If the work item is inlined onto the test thread, `IsCurrentBoundary()` (QuickFiler/Viewers/BreadcrumbUiDispatcher.cs lines 255 to 278; the thread-id branch is lines 276 to 277) returns true, the action runs inline, `executions` is one and no error is reported: the test fails spuriously. +2. Open QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.cs at lines 319 to 346. The test nulls the viewer's owning dispatcher through `ClearViewerDispatcher` (lines 361 to 371), then re-enters `InitializeBreadcrumbPipeline` from a `Task.Run` work item (line 332) with a blocking wait (lines 335 to 336). If the work item is inlined onto the owner thread, the call is on-thread and the pre-#781 context-reference guard the remark at lines 311 to 317 claims to discriminate against would also have admitted it: the test passes vacuously. The null-owner escape under test is at QuickFiler/Viewers/ItemViewer.Breadcrumb.cs lines 434 to 438. +3. Open UtilitiesCS.Test/HelperClasses/FileInfoWrapper_Tests.cs at lines 55 to 67 and 339 to 357 and confirm `GetSolutionFile()` resolves the repository's TaskMaster.sln and that `OpenRead()` opens it. + +Expected: +- A test that needs a distinct thread uses a dedicated `Thread` that is started, joined, and asserts inside its own delegate that it is not the owner thread (for example `CheckAccess()` is false, or `Environment.CurrentManagedThreadId` differs from the captured owner id) before it exercises the guard. This is the pattern PR #904 introduced at lines 228 to 238 and 385 to 403 of ItemViewerBreadcrumbThreadAffinityTests.cs. +- A file-handle test uses a stream the test itself owns, supplied through the wrapper's existing `IFileInfo` seam. It never opens a repository-tracked file and never creates a temporary file. + +Actual: +- Both thread-affinity guards can go unexercised (or produce a spurious result) depending on where the thread pool runs the work item; the file-open test can pass or fail depending on which other processes hold TaskMaster.sln. + +Logs / Screenshots: +- Static findings, verified present on 2026-09-28. The single observed `IOException` for the file test is recorded in the #900 follow-up handoff under docs/features/active/breadcrumb-thread-affinity-tests-assume-taskrun-distinct-thread-900/evidence/other/ (plain reference, not modified by this feature). + +## Scope & Non-Goals + +- In scope: + - Rewrite the two AFFECTED `Task.Run` sites (`Dispatcher_OwnerOnlyWorker_ReportsWithoutRunningAction` and `InitializeBreadcrumbPipeline_NullOwningDispatcher_DoesNotThrow`) to the dedicated-thread pattern with an in-thread distinctness assertion. + - Move the private `RunOnDedicatedWorkerThread` helper into a shared internal test-support type so both affected classes use one implementation. + - Split ItemViewerBreadcrumbThreadAffinityTests.cs (490 total lines today) into a partial class across the existing file and a new Part2 file, and register the new files in the QuickFiler.Test project file. + - Remove `GetSolutionFile()` and every TaskMaster.sln dependency from FileInfoWrapper_Tests.cs; route the `OpenRead` scenario through the existing internal `FileInfoWrapper(IFileInfo)` seam with a test-owned `FileStream`; convert the three metadata-only tests to a rooted literal `FileInfo` that needs no handle. + - Demonstrate, with committed Markdown projections, that each rewritten guard test fails against a deliberately broken guard and passes after the mutation is reverted. + - Run both affected suites in the parallel regime and the full C# toolchain; commit projections only. +- Out of scope / non-goals (paths in this list are deliberately unbackticked; none of them is modified): + - The 20 UNAFFECTED `Task.Run` sites in QuickFiler.Test enumerated in the Triage table below. They are not rewritten, not reformatted, and not annotated. + - Any production file. No file under QuickFiler/ or UtilitiesCS/ changes; the seam already exists (UtilitiesCS/HelperClasses/FileSystem/FileInfoWrapper.cs lines 21 to 24, reachable through the `InternalsVisibleTo("UtilitiesCS.Test")` attribute at UtilitiesCS/Properties/AssemblyInfo.cs line 19). + - TaskMaster.runsettings and scripts/vscode/TaskMaster.cli.runsettings. Workers and Scope stay as they are. + - The two pre-existing `[DoNotParallelize]` attributes in QuickFiler.Test (Helper Classes/EmailMoveMonitorTests.cs line 24 and Helper Classes/ViewerQueueStaticWrapperTests.cs line 11). They are unrelated to this issue and stay untouched. + - Same-class defects in UtilitiesCS.Test/HelperClasses/PhysicalFileSystemAdapters_Tests.cs (lines 173 and 318 call its own `GetSolutionFile()`; lines 186 to 198 swallow `IOException`; lines 213 to 234 open the real solution file) and UtilitiesCS.Test/HelperClasses/DirectoryInfoWrapper_Tests.cs (lines 60, 79 and 381 assert that TaskMaster.sln is enumerated from the repository root). Recorded as follow-ups in Rollout & Follow-up. + - The message wording "cannot marshal cross-thread UI work" asserted at QuickFiler.Test/Viewers/BreadcrumbUiThreadDispatchTests.cs line 305 (broader than the mechanism; wording-only follow-up). + - The .claude directory and the two JSON files under config. +- Explicitly excluded systems, integrations, or datasets: Outlook, VSTO, and any live UI pump. Neither rewritten test touches a control. + +## Root Cause Analysis + +**Distinct-thread defect.** `Task.Run` queues a work item to the thread pool. When the queuing thread is itself a pool thread, the item lands on that thread's local queue. A blocking wait (`GetAwaiter().GetResult()`) on a not-yet-started task attempts inline execution through the task scheduler, and the thread-pool scheduler permits inlining when the waiting thread is a pool thread. Under the parallel regime every test method runs on a pool thread, so the "other thread" can be the calling thread. A guard that decides by thread identity (`Dispatcher.CheckAccess()`, which is `Thread` object identity; or the `_ownerThreadId` comparison in `BreadcrumbUiDispatcher.IsCurrentBoundary()`) then sees the owner and admits the call. A dedicated `Thread` object constructed by the test is distinct from every live thread by construction, so an in-thread precondition asserted on it holds under any scheduler, and an untimed `Join()` on it parks no pool slot waiting on another pool slot. + +The six other candidate sites do not share the defect because the guards they reach do not consult thread identity: `Dispatch` with a captured context compares `SynchronizationContext.Current` to the captured context by reference (BreadcrumbUiDispatcher.cs lines 269 to 272); `DispatchValue` never calls `IsCurrentBoundary()` and faults for every caller outside an executing callback when the context is null; `BreadcrumbDropDownHost.Close`, `Reset` and `Dispose` carry no thread guard; and `TaskCompletionSource` completions have no thread-dependent outcome. Full per-site reasoning is in the Triage table. + +**File-handle defect.** The test reaches for a file that exists in every checkout, the solution file, because the public `FileInfoWrapper(FileInfo)` constructor wraps a `PhysicalFileInfoAdapter` whose `OpenRead()` is not seamed (UtilitiesCS/HelperClasses/FileSystem/PhysicalFileInfoAdapter.cs line 154), so a real file is the only way to exercise `OpenRead` through the public constructor. `FileInfo.OpenRead()` requests `FileShare.Read`, so any concurrent holder that denies shared reads makes the open fail. The internal `FileInfoWrapper(IFileInfo)` constructor removes the need for a real file for the wrapper's own contract, which is pure delegation (FileInfoWrapper.cs lines 153 to 156 for `OpenRead`). The three metadata tests never needed a handle: `FullName`, `Name`, `Extension`, `DirectoryName`, `Directory`, `ToString()` are path computations and `Exists` is a metadata query. + +Both patterns were copied from earlier tests and survived because they usually pass. They must not be fixed by serialising the run: Workers one, `[DoNotParallelize]`, retries, sleeps and timeouts are prohibited as fixes for this issue. + +## Proposed Fix + +### Design summary (what changes where) + +1. **Shared dedicated-thread helper.** New internal static class `DedicatedWorkerThread` in the QuickFiler.Test TestSupport folder (namespace `QuickFiler.Test.TestSupport`, matching the existing WinFormsPumpHost.cs). One member, `internal static Exception Run(Action action)`, with the identical body and XML remarks of the private `RunOnDedicatedWorkerThread` at ItemViewerBreadcrumbThreadAffinityTests.cs lines 373 to 403: construct a `Thread`, set `IsBackground = true`, `Start()`, untimed `Join()`, return the exception captured by the delegate or null. The helper adds no assertion of its own: each test states its site-specific distinctness precondition inside its delegate so that a failure names the guard under test. The helper contains no `Thread.Sleep`, `Task.Delay`, timeout, or retry. Estimated 40 to 60 total lines. +2. **Affinity file split.** `ItemViewerBreadcrumbThreadAffinityTests` becomes `partial`. The existing file keeps the four owner-thread admission tests (current lines 39 to 197), `InertOperations` (lines 348 to 355) and the three nested types `InertDropDownHost`, `DrainableSynchronizationContext`, `ViewerScope` (lines 405 to 488). The new Part2 file holds the three cross-thread tests (current lines 199 to 346) and `ClearViewerDispatcher` (lines 357 to 371). `RunOnDedicatedWorkerThread` is deleted from the class; the three cross-thread tests call `DedicatedWorkerThread.Run`. Private nested types and private static helpers remain visible across partial declarations, so no accessibility is widened. Estimated totals: about 290 lines for the existing file and 200 to 230 for the Part2 file, both under the 500 total-line ceiling. +3. **Site rewrite, `InitializeBreadcrumbPipeline_NullOwningDispatcher_DoesNotThrow` (Part2 file).** Before `ClearViewerDispatcher(scope.Viewer)`, capture `Dispatcher owner = scope.Viewer.UiDispatcher;` (requires a `using System.Windows.Threading;` directive in the Part2 file). Replace the `Task.Run` block with `Exception captured = DedicatedWorkerThread.Run(() => { ... })` whose delegate first asserts `owner.CheckAccess().Should().BeFalse(reason)` and then calls `scope.Viewer.InitializeBreadcrumbPipeline(provider.Object, operations)`. Assert `captured.Should().BeNull(reason)` and `scope.Viewer.BreadcrumbCoordinator.Should().BeSameAs(before)` unchanged. Rewrite the remark at current lines 311 to 317 so it states unconditionally that the call is off the owner thread and therefore discriminates against the pre-#781 context-reference guard. +4. **Site rewrite, `Dispatcher_OwnerOnlyWorker_ReportsWithoutRunningAction` (BreadcrumbPopupBoundaryCoverageTests.cs, current lines 52 to 62).** Capture `int ownerThreadId = Environment.CurrentManagedThreadId;` in Arrange (this is the value `CreateOwnerOnlyDispatcher` passes as owner). Replace the `Task.Run` and its blocking wait with `Exception captured = DedicatedWorkerThread.Run(() => { ... })` whose delegate first asserts `Environment.CurrentManagedThreadId.Should().NotBe(ownerThreadId, reason)` and then calls `dispatcher.Dispatch(() => executions++)`. The rejection path reports and returns `Task.CompletedTask` synchronously (BreadcrumbUiDispatcher.cs lines 97 to 105), so no task wait is needed. Assert `captured.Should().BeNull()`, then the two existing assertions unchanged: `executions.Should().Be(0)` and a single error whose message contains "cannot marshal". The file is 361 total lines today and stays under the ceiling. No apartment state is set: no control is touched and a null-context dispatcher never posts. +5. **File tests (FileInfoWrapper_Tests.cs).** Delete `GetSolutionFile()` and the `AppDomain` walk-up. The four tests keep their names and scenario intent: + - `Properties_ShouldMirrorWrappedFileInfo`, `ExplicitDirectoryCast_ShouldReturnWrappedContainingDirectory`, `ToString_ShouldDelegateToWrappedFileInfo` construct a `FileInfo` over the rooted literal C:\Repo\fixture.sln (the rooted-literal style already used at lines 87 and 113 of the same file). The path is not expected to exist and does not point into the repository. The assertions stay as they are (`Exists` mirrors `file.Exists`, so it is deterministic either way; `Extension` is ".sln"; `FullName`, `Name`, `DirectoryName`, `Directory.FullName`, the explicit `DirectoryInfoWrapper` cast, and `ToString()` are path computations). `Length` is not asserted on this fixture because it throws for a missing file. These three tests are what keeps the public constructor path (FileInfoWrapper.cs lines 14 to 19 into `PhysicalFileInfoAdapter`) and the explicit cast operator covered; the mocked tests cannot reach them because a `Mock` is not a `DirectoryInfoWrapper`. + - `OpenRead_ShouldReturnReadableStreamForWrappedFile` constructs the wrapper through the internal seam with a strict `Mock` whose `OpenRead()` returns a test-owned `FileStream` sentinel, and asserts `wrapper.OpenRead().Should().BeSameAs(sentinel)`, `CanRead` is true, and `Length` is greater than zero. The wrapper's contract for `OpenRead` is delegation, so instance identity is the complete contract; the two readability assertions confirm the returned object is the live stream the test opened. + +**Chosen stream design and justification (binding operator constraint).** The sentinel is `new FileStream(typeof(FileInfoWrapper_Tests).Assembly.Location, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)`, disposed by a `using` declaration in the test. +- It is test-owned: the test constructs it, holds the only reference, passes it to a mock it also owns, and disposes it. Nothing outside the test method touches it. +- It is not a repository-tracked file: the test assembly is a build output under the test project's bin directory, which git ignores. It is not TaskMaster.sln or any source file. +- It is not a temporary file: the test creates nothing, writes nothing, and deletes nothing. The file pre-exists as the running test host's own loaded image and remains after the test. The unit-test policy's prohibition on temporary files is satisfied by construction, not by cleanup. +- It cannot be denied by another process's handle in any observed mode: the open requests read access only and `FileShare.ReadWrite`, so every concurrent reader and writer is admitted, and the running host's image mapping keeps the file in existence for the whole run. This exact open shape already executes six times in the same file (lines 189 to 224) and in PhysicalFileSystemAdapters_Tests.cs lines 258 to 275 with no recorded contention incident. +- Why not `MemoryStream`: `IFileInfo.OpenRead()` returns the concrete `FileStream` type (UtilitiesCS/Interfaces/IHelperClasses/IFileInfo.cs line 26), as do `Create()`, `Open(...)` and `OpenWrite()`; a `MemoryStream` cannot be returned through the seam for these members. `MemoryStream` remains usable only behind the `StreamReader` and `StreamWriter` members, which the existing test at lines 186 to 188 already does. +- Handle-free alternative evaluated and rejected: `FileStream` on net48 needs a path or a `SafeFileHandle`. The only file-less handle sources available are anonymous pipes and device pseudo-files; a `FileStream` over either is non-seekable, so `Length` throws `NotSupportedException`, and it is still an OS handle rather than "no file". Dropping the `Length` assertion to admit a pipe would weaken the scenario the issue names. The assembly-location `FileStream` is therefore the chosen design. + +### Boundaries and invariants to preserve + +- The parallel regime is unchanged: Workers zero, Scope ClassLevel. No `[DoNotParallelize]`, no `Workers` change, no retry attribute or loop, no `Thread.Sleep`, `Task.Delay`, timeout or wall-clock wait is introduced anywhere in the diff. +- No production file changes. The negative-control mutations in Test Strategy are applied and reverted inside the same task; the final diff contains no change under QuickFiler/ or UtilitiesCS/. +- Every existing test name in the three edited test files survives; no test is deleted or renamed. +- The two rewritten guard tests keep their original observable assertions and add only the in-thread precondition and the `captured` null check. +- The helper's contract is identical to the #900 private helper so the two existing worker-thread tests behave exactly as before. + +### Dependencies or blocked work + +- None. The seam exists in production; the split and the helper are test-project-only. + +### Implementation strategy (what changes, not sequencing) + +#### Files/modules to change +See `## Write Set`. Three existing test files are modified, two test files are created, one test project file gains two Compile entries, and three evidence directories are created under the feature folder. + +#### Functions/classes/CLI commands impacted +- `ItemViewerBreadcrumbThreadAffinityTests` (becomes `partial`; loses `RunOnDedicatedWorkerThread`; three tests and `ClearViewerDispatcher` relocate to the Part2 file). +- `BreadcrumbPopupBoundaryCoverageTests.Dispatcher_OwnerOnlyWorker_ReportsWithoutRunningAction` (rewritten body). +- `FileInfoWrapper_Tests` (four test bodies rewritten; `GetSolutionFile` removed). +- New `QuickFiler.Test.TestSupport.DedicatedWorkerThread.Run(Action)`. +- No CLI command changes. + +#### Data flow and validation changes +None in production. In tests, the distinctness precondition moves from "assumed by `Task.Run`" to "asserted inside the dedicated thread". + +#### Error handling and logging updates +None. + +#### Rollback/feature-flag considerations (if applicable) +Not applicable; test-only change. Reverting the commit restores the prior tests. + +### Technical specifications (interfaces/contracts) + +#### Inputs/outputs and formats +- `DedicatedWorkerThread.Run(Action action)`: runs `action` on a new background thread, joins it, returns the `Exception` the delegate threw or null. Same contract as the removed private helper. + +#### Required configuration keys and defaults +None. + +#### Backward-compatibility expectations +No public API changes. `DedicatedWorkerThread` is `internal` to QuickFiler.Test. + +#### Performance constraints (latency/throughput/memory) +Each rewritten test starts and joins exactly one thread; the join is a completion wait on one bounded synchronous call. No measurable suite-time change is expected. + +## Triage of every Task.Run site in QuickFiler.Test + +Verdicts and reasons follow the research record section A2 and were re-verified against the current tree. File paths are project-relative under QuickFiler.Test and deliberately unbackticked; only the two AFFECTED rows are edited. + +| Site (file:line) | Containing test or helper | Verdict | Reason | +|---|---|---|---| +| Helper Classes/EmailMoveMonitorTests.cs:298 | `UnhookItem_InvokedFromThreadPoolThread_RunsComAccessOnMarshalTargetThread` | UNAFFECTED | The asserted property is guaranteed by a fresh `new Thread` inside the marshal delegate, distinct from every live thread including an inlined caller; `UnhookItem` marshals unconditionally. Class already carries `[DoNotParallelize]` for unrelated reasons; left as is. | +| Viewers/BreadcrumbCoordinatorLifecycleTests.cs:350 | `QueuedCompletion_DisposedBeforeOwnerDrain_DoesNotPublish` | UNAFFECTED | Completes a `TaskCompletionSource` only. | +| Viewers/BreadcrumbPopupBoundaryCoverageTests.cs:58 | `Dispatcher_OwnerOnlyWorker_ReportsWithoutRunningAction` | **AFFECTED** | Owner-only dispatcher (null context) reaches the thread-id branch of `IsCurrentBoundary()`; the blocking wait can inline the work item onto the owner thread, in which case the action runs and the test fails spuriously. | +| Viewers/BreadcrumbPopupControlDispatchTests.cs:29 | `SurfaceFactory_WorkerCompletion_DispatchesEveryStageAndCleanup` | UNAFFECTED | Context-backed dispatcher; every stage goes through `DispatchValue`, which posts unless inside an executing callback and never reads thread identity. | +| Viewers/BreadcrumbPopupControlDispatchTests.cs:111 | `Readiness_DisposeFromAmbientNullWorker_DispatchesHandlerDetachment` | UNAFFECTED | Guard compares ambient context by reference; the body sets ambient null itself, so the post occurs on any thread. | +| Viewers/BreadcrumbPopupControlDispatchTests.cs:300 | helper `CompleteOnWorker` | UNAFFECTED | Completes a `TaskCompletionSource` only. | +| Viewers/BreadcrumbPopupBoundaryCoverageTests.Part2.cs:192 | `CaptureCurrent_NullAndControlledContexts_FailFastAndCapture` | UNAFFECTED | `PostAsync` compares ambient context by reference; on the test thread the ambient context is the restored previous one, never the captured one, so the post happens on any thread; `PostCount` pins the posted path. | +| Viewers/BreadcrumbSelectorToggleUiBoundaryTests.cs:61 | `WorkerProviderAndSelectorToggle_MarshalPostsAndCallbackEntryToOwningBoundary` | UNAFFECTED | Completes a `TaskCompletionSource` only. | +| Viewers/BreadcrumbSelectorToggleUiBoundaryTests.cs:75 | same test | UNAFFECTED | Context-backed coordinator; inbound `Dispatch` posts whenever ambient context is not the captured one and the body forces ambient null; a semaphore wait precedes the blocking wait, so the body has already run on another pool thread before inlining is possible. | +| Viewers/BreadcrumbSelectorToggleUiBoundaryTests.cs:137 | `PopupHost_WorkerCompletions_RunOnlyWhenCreatorThreadDrainsBoundary` | UNAFFECTED | Completes a `TaskCompletionSource` only. | +| Viewers/BreadcrumbSelectorToggleUiBoundaryTests.cs:148 | same test | UNAFFECTED | Completes a `TaskCompletionSource` only. | +| Viewers/BreadcrumbSelectorToggleUiBoundaryTests.cs:154 | same test | UNAFFECTED | `host.Close(...)` has no thread guard; the test asserts where drained work ran, not where `Close` originated. | +| Viewers/BreadcrumbSelectorToggleUiBoundaryTests.cs:161 | same test | UNAFFECTED | `host.Reset` has no thread guard; same reasoning. | +| Viewers/BreadcrumbSelectorOpenRetryTests.cs:37 | `MouseToggle_FirstOpenFaultsAfterAwait_SecondClickRetriesCleanly` | UNAFFECTED | Completes a `TaskCompletionSource` (`SetException`). | +| Viewers/BreadcrumbSelectorOpenRetryTests.cs:210 | `Dispose_WhenResetAndOpenWorkAreQueued_HasNoLateActivity` | UNAFFECTED | Completes a `TaskCompletionSource` only. | +| Viewers/BreadcrumbSelectorOpenRetryTests.cs:218 | same test | UNAFFECTED | `host.Reset` has no thread guard; assertions are operation counts after drain. | +| Viewers/BreadcrumbSelectorOpenRetryTests.cs:219 | same test | UNAFFECTED | `host.Dispose` has no thread guard. | +| Viewers/BreadcrumbUiThreadDispatchTests.cs:51 | `SetSuggestionsAsync_WorkerProviderCompletion_SchedulesPostOnOwningContext` | UNAFFECTED | Completes a `TaskCompletionSource`; awaited, so no wait-inlining. | +| Viewers/BreadcrumbUiThreadDispatchTests.cs:90 | `InboundWorkerMessage_SchedulesEveryPostAndCallbackOnOwningContext` | UNAFFECTED | Awaited; context-backed coordinator posts whenever ambient context is not the captured one; outcome identical on any thread. | +| Viewers/BreadcrumbUiThreadDispatchTests.cs:301 | `ProductionCaptureWithoutUiContext_FailsFast` | UNAFFECTED | `DispatchValue` on an owner-only dispatcher faults for every caller outside an executing callback and never reads `_ownerThreadId`; the expected exception is produced on the owner thread too. (The message wording is a follow-up, not a fix here.) | +| Viewers/ItemViewerBreadcrumbThreadAffinityTests.cs:332 | `InitializeBreadcrumbPipeline_NullOwningDispatcher_DoesNotThrow` | **AFFECTED** | The blocking wait can inline the work item onto the owner thread, on which branch the pre-#781 guard would also have admitted the call; the test then passes without discriminating. | +| Controllers/QfcItemController.UiThreadDispatcherFixtureTests.cs:222 | `Transaction_SecondCallerCannotInstallUntilTheFirstRestores` | UNAFFECTED | Asserts a value observed by a second gate acquirer, not thread identity; the wait is a `ManualResetEventSlim`, not a task wait. Its intermittency is tracked under #823. | + +## Assumptions, Constraints, Dependencies + +- Assumptions (environment, data, access): the test host loads the UtilitiesCS.Test assembly from its bin output, so `typeof(FileInfoWrapper_Tests).Assembly.Location` is a readable on-disk path for the whole run (already relied on six times in the same file). The `InternalsVisibleTo("UtilitiesCS.Test")` attribute remains in place. +- Constraints (budget, performance, compatibility): temporary files prohibited; parallel regime unchanged; no production change; 500 total-line ceiling on every .cs file in the Write Set; MSTest, Moq, FluentAssertions only; committed evidence is projections only, with no absolute host path, account name or host name. +- External dependencies (services, libraries, releases): none. + +## Data / API / Config Impact + +- User-facing or API changes: none. +- Data or migration considerations: none. +- Logging/telemetry updates (if any): none. +- Compatibility notes (CLI flags, config schemas, versioning): none. The project file gains two Compile Include entries in the existing explicit-include style. + +## Test Strategy + +**Baseline (before any edit).** Run both suites under the root runsettings file with the isolation switch and record a TRX-derived summary as a Markdown projection named test-run-baseline.md under the baseline evidence directory. Run the CLAUDE.md step-4 coverage route and record the package-level JaCoCo projection and the one-line first-party coverage summary as coverage-baseline.md in the same directory. Record total line counts of the three test files to be edited (490, 361, 359 today). + +**Regression tests updated (no new production behaviour, so no new production test).** +- `Dispatcher_OwnerOnlyWorker_ReportsWithoutRunningAction` (BreadcrumbPopupBoundaryCoverageTests.cs): rewritten per Proposed Fix item 4. +- `InitializeBreadcrumbPipeline_NullOwningDispatcher_DoesNotThrow` (moved to the Part2 file): rewritten per Proposed Fix item 3. +- `InitializeBreadcrumbPipeline_WorkerThread_ThrowsBoundaryDiagnostic` and `ConfigureBreadcrumbDropDown_WorkerThread_ThrowsBoundaryDiagnostic` (moved to the Part2 file): body unchanged except the helper call target. +- `Properties_ShouldMirrorWrappedFileInfo`, `ExplicitDirectoryCast_ShouldReturnWrappedContainingDirectory`, `OpenRead_ShouldReturnReadableStreamForWrappedFile`, `ToString_ShouldDelegateToWrappedFileInfo` (FileInfoWrapper_Tests.cs): rewritten per Proposed Fix item 5. + +**Negative controls (each rewritten guard test must be observed failing).** Every mutation is applied, built, run with a scoped test-case filter on the fully qualified test name under the root runsettings file with the isolation switch, recorded, reverted with a git checkout of the mutated file, and the revert proven with the porcelain status output showing no residual change to that file. Each control is then re-run unmutated in the same artifact so the before/after pair is visible. Controls are never committed; the final diff contains no production change. +1. Owner-only dispatcher guard (temporary production edit): replace the thread-id comparison at BreadcrumbUiDispatcher.cs lines 276 to 277 with `return true;`. Expected: the rewritten `Dispatcher_OwnerOnlyWorker_ReportsWithoutRunningAction` fails at the `executions` assertion ("Expected executions to be 0, but found 1") because the action ran inline on the worker. Projection: mutation-owner-only-dispatcher-guard.md. +2. Null-owner escape (temporary production edit): at ItemViewer.Breadcrumb.cs lines 435 to 438 replace the bare `return;` with the pre-#781 context-reference check that throws `InvalidOperationException` when `SynchronizationContext.Current` is not the captured context. Expected: the rewritten `InitializeBreadcrumbPipeline_NullOwningDispatcher_DoesNotThrow` fails at the `captured` null assertion because the dedicated thread's ambient context is null. This is the discrimination the old `Task.Run` shape only had conditionally. Projection: mutation-null-owner-escape.md. +3. Inline precondition (test-only edit): insert `action();` as the first statement of `DedicatedWorkerThread.Run` so the delegate also runs on the caller. Expected: both rewritten tests and the two existing worker-thread tests fail at their in-thread precondition with the stated reason text, proving the preconditions are live. Projection: mutation-inline-precondition.md. +4. OpenRead sentinel (test-only edit): point the mock's `OpenRead()` setup at a second, distinct `FileStream` sentinel. Expected: `OpenRead_ShouldReturnReadableStreamForWrappedFile` fails at `BeSameAs`. Projection: mutation-openread-sentinel.md. + +Isolation note for the controls: each projection records the runsettings file used, the isolation switch, the exact test-case filter, the mutated file and hunk, the failed test names with the failure message excerpt, and the porcelain status output after revert. A control that passes while the mutation is applied means the test does not exercise the guard and is a blocking finding. + +**Full suites in the parallel regime.** After the rewrite and after every control is reverted, run the full QuickFiler.Test and UtilitiesCS.Test assemblies under the root runsettings file (Workers zero, Scope ClassLevel) with the isolation switch. Record TRX-derived summaries as parallel-suite-quickfiler-test.md and parallel-suite-utilitiescs-test.md under the regression-testing evidence directory. The QuickFiler.Test summary must list the three cross-thread test names and `Dispatcher_OwnerOnlyWorker_ReportsWithoutRunningAction`, which is the proof that the Part2 file and the helper compiled (an unregistered file is silently absent from the run). If the local machine's known shell-icon stall in UtilitiesCS.Test reproduces, the filter that excludes those classes must be recorded verbatim in the projection together with the excluded class names and a note that the stall is pre-existing on main; no other exclusion is permitted. + +**Toolchain (CLAUDE.md order; restart from step one on any change or failure).** +1. `dotnet tool run csharpier format .` then `dotnet tool run csharpier check .` +2. `msbuild TaskMaster.sln /t:Rebuild /m /p:Configuration=Debug "/p:Platform=Any CPU" /p:EnableNETAnalyzers=true /p:EnforceCodeStyleInBuild=true` +3. `msbuild TaskMaster.sln /t:Rebuild /m /p:Configuration=Debug "/p:Platform=Any CPU" /p:TreatWarningsAsErrors=true` +4. The `test: MSTest with Coverage (Koverage)` route (Invoke-MSTestWithCoverage.ps1 under scripts/vscode), which writes the fixed-name TRX under the coverage results directory that the summary is derived from. +Record commands, exit codes and the count of "Skipping target CoreCompile" lines (must be zero for both rebuilds, proving the analyzers and the nullable gate actually compiled) in toolchain-pass.md under the qa-gates evidence directory. Record the post-change package-level JaCoCo projection and one-line summary as coverage-final.md in the same directory. + +**Coverage impact.** Changed lines are all test lines and sit outside the production denominator. Production lines the four file tests keep covered: FileInfoWrapper.cs lines 14 to 19 (public constructor), 21 to 24 (seam), 153 to 156 (`OpenRead`), 211 to 214 (explicit cast), and the `PhysicalFileInfoAdapter` property lines. The QuickFiler rewrite covers the same production branches as before. The final projection's per-package figures for UtilitiesCS and QuickFiler must be no lower than the baseline projection. + +**Evidence hygiene.** Committed evidence is Markdown projections only (CLAUDE.md "Committed Test Evidence Format" and the maintainer decision on issue 671): no raw TRX, Cobertura XML or .coverage document is added anywhere. Every projection replaces absolute host paths, account names and host names with the placeholders repo-root, user-profile, user and host, each enclosed in angle brackets. Projection filenames are fixed (not timestamped); the run timestamp is a Timestamp field inside each artifact. + +**Manual validation steps.** None. + +## Write Set + +- `QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.cs` (modify: make partial; remove the three cross-thread tests, `ClearViewerDispatcher` and `RunOnDedicatedWorkerThread`) +- `QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.Part2.cs` (create: partial continuation with the three cross-thread tests and `ClearViewerDispatcher`) +- `QuickFiler.Test/TestSupport/DedicatedWorkerThread.cs` (create: shared dedicated-thread helper) +- `QuickFiler.Test/Viewers/BreadcrumbPopupBoundaryCoverageTests.cs` (modify: rewrite `Dispatcher_OwnerOnlyWorker_ReportsWithoutRunningAction`) +- `QuickFiler.Test/QuickFiler.Test.csproj` (modify: two Compile Include entries) +- `UtilitiesCS.Test/HelperClasses/FileInfoWrapper_Tests.cs` (modify: rewrite four tests; remove `GetSolutionFile`) +- `docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/baseline/` (create: baseline projections) +- `docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/regression-testing/` (create: mutation and parallel-suite projections) +- `docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/qa-gates/` (create: toolchain and coverage projections) + +## Acceptance Criteria + +- [x] AC1. `Dispatcher_OwnerOnlyWorker_ReportsWithoutRunningAction` in BreadcrumbPopupBoundaryCoverageTests.cs contains no `Task.Run` call and no `GetAwaiter().GetResult()` wait; it captures the owner thread id before constructing the owner-only dispatcher, runs the guarded `Dispatch` call through `DedicatedWorkerThread.Run`, and the delegate asserts that `Environment.CurrentManagedThreadId` differs from the captured owner id before `Dispatch` is invoked; the returned exception is asserted null and the two pre-existing assertions (zero executions; exactly one reported error whose message contains "cannot marshal") are present unchanged. +- [x] AC2. `InitializeBreadcrumbPipeline_NullOwningDispatcher_DoesNotThrow`, now in the Part2 file, contains no `Task.Run` call and no `GetAwaiter().GetResult()` wait; it captures the owning `Dispatcher` before `ClearViewerDispatcher` is called, runs the guarded `InitializeBreadcrumbPipeline` call through `DedicatedWorkerThread.Run`, and the delegate asserts that the captured owner's `CheckAccess()` is false before the guarded call; the returned exception is asserted null and `BreadcrumbCoordinator` is asserted to be the same instance as before; the remark on the test states the off-owner-thread discrimination unconditionally. +- [x] AC3. Every other `Task.Run` site in QuickFiler.Test, the twenty UNAFFECTED rows of the Triage table, is byte-identical to the merge base: a diff of QuickFiler.Test against the merge base touches no line of EmailMoveMonitorTests.cs, BreadcrumbCoordinatorLifecycleTests.cs, BreadcrumbPopupControlDispatchTests.cs, BreadcrumbPopupBoundaryCoverageTests.Part2.cs, BreadcrumbSelectorToggleUiBoundaryTests.cs, BreadcrumbSelectorOpenRetryTests.cs, BreadcrumbUiThreadDispatchTests.cs or QfcItemController.UiThreadDispatcherFixtureTests.cs, and the two pre-existing `[DoNotParallelize]` attributes in QuickFiler.Test are untouched. +- [x] AC4. `ItemViewerBreadcrumbThreadAffinityTests` is declared `partial` in both ItemViewerBreadcrumbThreadAffinityTests.cs and ItemViewerBreadcrumbThreadAffinityTests.Part2.cs; the existing file holds the four owner-thread admission tests, `InertOperations` and the three nested types; the Part2 file holds the three cross-thread tests and `ClearViewerDispatcher`; no test method in the class is renamed or removed; the private `RunOnDedicatedWorkerThread` helper no longer exists in the class. +- [x] AC5. Each of ItemViewerBreadcrumbThreadAffinityTests.cs, ItemViewerBreadcrumbThreadAffinityTests.Part2.cs, DedicatedWorkerThread.cs, BreadcrumbPopupBoundaryCoverageTests.cs and FileInfoWrapper_Tests.cs is at or under five hundred total lines, measured as total newline-terminated lines; the project file and Markdown artifacts are exempt from this ceiling by policy. +- [x] AC6. The QuickFiler.Test project file contains a Compile Include entry for the Part2 file and a Compile Include entry for the DedicatedWorkerThread file, both in the project-relative backslash form used by the neighbouring entries, and the committed QuickFiler.Test parallel-suite projection lists `InitializeBreadcrumbPipeline_WorkerThread_ThrowsBoundaryDiagnostic`, `ConfigureBreadcrumbDropDown_WorkerThread_ThrowsBoundaryDiagnostic`, `InitializeBreadcrumbPipeline_NullOwningDispatcher_DoesNotThrow` and `Dispatcher_OwnerOnlyWorker_ReportsWithoutRunningAction` as executed and passed. +- [x] AC7. `DedicatedWorkerThread` is an `internal static` class in namespace `QuickFiler.Test.TestSupport` exposing `internal static Exception Run(Action action)` whose body constructs a `Thread`, sets `IsBackground` true, starts it, joins it without a timeout, and returns the exception the delegate threw or null; it carries the dedicated-thread rationale remark from the removed private helper; and it contains no `Thread.Sleep`, `Task.Delay`, timeout argument, retry loop, or assertion. All four dedicated-thread tests in QuickFiler.Test call this member. +- [x] AC8. FileInfoWrapper_Tests.cs contains no method named `GetSolutionFile`, no occurrence of the text "TaskMaster.sln", no reference to `AppDomain.CurrentDomain.BaseDirectory`, and no call that creates, writes to, or deletes a file: no `File.Create`, `File.WriteAll`, `File.Delete`, `Path.GetTempFileName`, `Path.GetTempPath`, no `FileMode` other than `Open`, and no `FileAccess` other than `Read`; the four tests `Properties_ShouldMirrorWrappedFileInfo`, `ExplicitDirectoryCast_ShouldReturnWrappedContainingDirectory`, `OpenRead_ShouldReturnReadableStreamForWrappedFile` and `ToString_ShouldDelegateToWrappedFileInfo` keep their names. +- [x] AC9. `OpenRead_ShouldReturnReadableStreamForWrappedFile` constructs the wrapper through the internal `FileInfoWrapper(IFileInfo)` constructor with a strict Moq mock of `IFileInfo` whose `OpenRead()` returns a `FileStream` the test opens over `typeof(FileInfoWrapper_Tests).Assembly.Location` with `FileMode.Open`, `FileAccess.Read` and `FileShare.ReadWrite` inside a `using` declaration; it asserts the wrapper's `OpenRead()` result is the same instance as that sentinel, that `CanRead` is true, and that `Length` is greater than zero. +- [x] AC10. `Properties_ShouldMirrorWrappedFileInfo`, `ExplicitDirectoryCast_ShouldReturnWrappedContainingDirectory` and `ToString_ShouldDelegateToWrappedFileInfo` construct their `FileInfo` from a rooted literal path that does not point into the repository, open no stream, and assert none of `Length`, `OpenRead`, `Open`, `Create` or `OpenWrite`; their existing assertions on `Exists`, `FullName`, `Name`, `Extension`, `DirectoryName`, `Directory`, the explicit `DirectoryInfoWrapper` cast and `ToString()` are retained. +- [x] AC11. A committed Markdown projection named mutation-owner-only-dispatcher-guard.md under the regression-testing evidence directory shows that, with the thread-id comparison in `BreadcrumbUiDispatcher.IsCurrentBoundary()` temporarily replaced by an unconditional true, the rewritten `Dispatcher_OwnerOnlyWorker_ReportsWithoutRunningAction` fails at its `executions` assertion under the root runsettings file with the isolation switch and a scoped test-case filter, and that the same filter passes after the revert; the projection records the mutated file and hunk, the failed test name, the failure message excerpt, and porcelain status output after the revert showing no residual change. +- [x] AC12. A committed projection named mutation-null-owner-escape.md under the regression-testing evidence directory shows that, with the null-owner `return` in `ItemViewer.ThrowIfOffUiBoundary` temporarily replaced by the pre-fix context-reference throw, the rewritten `InitializeBreadcrumbPipeline_NullOwningDispatcher_DoesNotThrow` fails at its `captured` null assertion, and passes after the revert, with the same recorded fields as AC11. +- [x] AC13. A committed projection named mutation-inline-precondition.md under the regression-testing evidence directory shows that, with `action()` temporarily inserted as the first statement of `DedicatedWorkerThread.Run`, all four dedicated-thread tests fail at their in-thread distinctness precondition, and pass after the revert, with the same recorded fields as AC11. +- [x] AC14. A committed projection named mutation-openread-sentinel.md under the regression-testing evidence directory shows that, with the mock's `OpenRead()` setup temporarily pointed at a second distinct `FileStream`, `OpenRead_ShouldReturnReadableStreamForWrappedFile` fails at its same-instance assertion, and passes after the revert, with the same recorded fields as AC11. +- [x] AC15. The final diff against the merge base modifies no file under the QuickFiler or UtilitiesCS production directories, nothing under the .claude directory, and neither JSON file under config; the set of changed, added and deleted repository files equals the Write Set entries plus documents inside the feature folder. +- [x] AC16. The full QuickFiler.Test and UtilitiesCS.Test assemblies pass under the root runsettings file (Workers zero, Scope ClassLevel) with the isolation switch, with zero failed tests, and the committed projections parallel-suite-quickfiler-test.md and parallel-suite-utilitiescs-test.md under the regression-testing evidence directory record the totals, the runsettings file, the switch and any test-case filter verbatim; the only permitted filter excludes the pre-existing local shell-icon stall classes in UtilitiesCS.Test and names them; the diff introduces no `[DoNotParallelize]`, no change to Workers or Scope, no retry attribute or loop, and no `Thread.Sleep`, `Task.Delay`, timeout or wall-clock wait. +- [x] AC17. The full C# toolchain passes in one final pass in CLAUDE.md order (csharpier check; analyzer rebuild; TreatWarningsAsErrors rebuild; the MSTest-with-coverage route), and the committed projection toolchain-pass.md under the qa-gates evidence directory records each command, its exit code, and that both rebuild logs contain zero "Skipping target CoreCompile" lines. +- [x] AC18. The committed projections coverage-baseline.md (baseline evidence directory) and coverage-final.md (qa-gates evidence directory) each contain a package-level JaCoCo projection and the one-line first-party coverage summary, and the final per-package line and branch figures for UtilitiesCS and QuickFiler are not lower than the baseline figures. +- [x] AC19. The diff adds no file with a .trx, .xml or .coverage extension anywhere in the repository, and no committed evidence artifact contains an absolute host path, an account name or a host name; the placeholders repo-root, user-profile, user and host (each enclosed in angle brackets) are used in their place. + +## Numeric Derivation Evidence + +The acceptance criteria above contain no standalone digits; every count is written in words. The population claims they rely on (22 `Task.Run` sites in QuickFiler.Test, of which exactly two are AFFECTED and twenty UNAFFECTED) are derived in the research record's `## Numeric Derivation Evidence` section, which supplies the complete family, exhaustive scope, inclusion and exclusion rules, two independently constructed search strategies with distinct regular expressions and file filters, two independently enumerated member sets, both counts, and an explicit member-set comparison showing the sets are identical. That section is at docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/research/2026-09-28T20-15-tests-depend-on-uncontrolled-environment-research.md. The primary grep was re-run in this worktree on 2026-09-28 and returned the same 29 raw lines and the same 22 retained members. + +## Risks & Mitigations + +- Technical or operational risks: + - The Part2 file or the helper is created on disk but not registered in the project file, so the moved tests silently disappear from the run. Mitigation: AC6 requires the four test names to appear as executed in the committed suite projection. + - A negative control passes while the mutation is applied (the rewritten test does not exercise the guard). Mitigation: AC11 to AC14 require an observed failure with message excerpt; a passing control is a blocking finding. + - A temporary production mutation is left in the tree. Mitigation: each control's projection records the porcelain status after revert, and AC15 forbids any production change in the final diff. + - The rooted literal path used by the three metadata tests happens to exist on some machine. Mitigation: the assertions mirror `file.Exists` and never assert `Length`, so the outcome is identical either way. + - The local shell-icon stall in UtilitiesCS.Test prevents a full local run. Mitigation: AC16 permits exactly that documented exclusion and no other; CI executes the excluded classes. +- Mitigations and rollbacks: test-only change; revert the commit to restore the previous tests. + +## Rollout & Follow-up + +- Release/rollout steps: merge through the normal PR gate after the toolchain and the parallel suite runs pass. No deployment impact. +- Post-fix monitoring or clean-up tasks: none beyond the follow-ups below. +- Follow-ups to record as potential entries through the promotion lifecycle (not fixed here; paths deliberately unbackticked): + 1. UtilitiesCS.Test/HelperClasses/PhysicalFileSystemAdapters_Tests.cs: its own `GetSolutionFile()` (lines 173, 318, 373 to 376), the `catch (IOException)` blocks at lines 43 and 195 that swallow contention, and the read opens on the real solution file at lines 213 to 234. Same defect class as #906. + 2. UtilitiesCS.Test/HelperClasses/DirectoryInfoWrapper_Tests.cs lines 60, 79 and 381: assertions that TaskMaster.sln is enumerated from the repository root. Same defect class as #906. + 3. QuickFiler.Test/Viewers/BreadcrumbUiThreadDispatchTests.cs line 305: the asserted message "cannot marshal cross-thread UI work" describes the mechanism more broadly than `DispatchValue` implements (it faults for every caller outside an executing callback, on any thread). Wording-only. + 4. The #900 follow-up handoff (docs/features/active/breadcrumb-thread-affinity-tests-assume-taskrun-distinct-thread-900/evidence/other/) attributes BreadcrumbUiThreadDispatchTests.cs line 301 to the owner-thread-id check; the research record shows it reaches `DispatchValue`, which never reads the owner id. Documentation correction only. +- Links: issue #931 (https://github.com/drmoisan/TaskMaster/issues/931), consolidated issues #905 and #906, precedent PR #904 (issue #900), maintainer decision on issue 671 (projections-only evidence), research record cited in the header. diff --git a/docs/features/potential/promoted/2026-09-28-tests-depend-on-uncontrolled-environment.md b/docs/features/potential/promoted/2026-09-28-tests-depend-on-uncontrolled-environment.md new file mode 100644 index 000000000..2382d3761 --- /dev/null +++ b/docs/features/potential/promoted/2026-09-28-tests-depend-on-uncontrolled-environment.md @@ -0,0 +1,67 @@ +# tests-depend-on-uncontrolled-environment (Issue #931) + +- Date captured: 2026-09-28 +- Author: Dan Moisan +- Status: Promoted -> docs/features/active/tests-depend-on-uncontrolled-environment/ (Issue #931) + +> Automation note: Keep the section headings below unchanged; the promotion tooling maps each of them into the GitHub bug issue template. + +- Issue: #931 +- Issue URL: https://github.com/drmoisan/TaskMaster/issues/931 +- Last Updated: 2026-09-28 +## Summary +Consolidates #905 and #906. The shared root cause: a unit test depends on environment state it does not control, so its result depends on scheduling or on other processes rather than on the code under test. + +1. **#905:** tests use `Task.Run` as the "other thread". `Task.Run` guarantees only a thread-pool thread, never a different one, so under parallel execution the guard under test can go unexercised. PR #904 (#900) fixed two instances. Remaining: + - `QuickFiler.Test/Viewers/ItemViewerBreadcrumbThreadAffinityTests.cs:332`. The file is 490 lines, so the fix requires a split to stay under 500. + - Candidates to triage: + - `BreadcrumbSelectorToggleUiBoundaryTests.cs:75` + - `BreadcrumbPopupControlDispatchTests.cs:29,111` + - `BreadcrumbPopupBoundaryCoverageTests.cs:58` + - `BreadcrumbPopupBoundaryCoverageTests.Part2.cs:192` + - `BreadcrumbUiThreadDispatchTests.cs:90,301` + - `Task.Run(() => tcs.SetResult(...))` calls that only complete a task are not affected. +2. **#906:** `UtilitiesCS.Test/HelperClasses/FileInfoWrapper_Tests.cs:56-62` opens the repository's own `TaskMaster.sln`, found by `GetSolutionFile()` at lines 340-352, as a fixture. Resident MSBuild node-reuse workers can hold that file open, so the outcome depends on build history. + +## Environment +- OS/version: Windows 11 Pro 10.0.26200 +- Python version: not applicable (C#, MSTest, net48) +- Command/flags used: parallel regime `/Settings:TaskMaster.runsettings` (Workers 0, Scope ClassLevel) +- Data source or fixture: files listed above, `main` at `177b6d78e` + +## Steps to Reproduce +1. Inspect the cited `Task.Run` sites and confirm that each asserts a thread-identity property against the thread it obtained. +2. Inspect `FileInfoWrapper_Tests.GetSolutionFile()` and confirm it resolves the repository's own solution file. + +## Expected Behavior +- A test that needs a distinct thread uses a dedicated `Thread` that is joined, and asserts inside that thread that it is distinct (for example `CheckAccess() == false`) before exercising the guard. This is the #900 pattern. +- A file-handle test uses a stream the test owns, supplied through the wrapper's seam or an in-memory stream. It never uses a repository file. +- **Temporary files are prohibited by the unit-test policy and must not be used.** + +## Actual Behavior +The guard under test can pass without being exercised, and the file-open test can fail or pass depending on MSBuild worker residency. + +## Logs / Screenshots +- [ ] Attached minimal logs or screenshot +- Snippet: none (static findings, verified present on 2026-09-28) + +## Impact / Severity +- [ ] Blocker +- [ ] High +- [x] Medium +- [ ] Low + +## Suspected Cause / Notes +Both patterns were copied from earlier tests and survived because they usually pass. Tests must run in parallel. Do not fix either defect with `Workers=1`, `[DoNotParallelize]` or retries. + +## Proposed Fix / Validation Ideas +- [ ] Apply the #900 dedicated-thread pattern at every triaged site. Split `ItemViewerBreadcrumbThreadAffinityTests.cs` so it stays at or under 500 lines, and register any new file in `QuickFiler.Test.csproj`. +- [ ] Replace the `TaskMaster.sln` fixture with a test-owned stream or an injected seam. Add a seam to the wrapper only if one does not already exist. +- [ ] Each rewritten test must be shown to fail against a deliberately broken guard, so the test demonstrably exercises the guard. +- [ ] Run the full `QuickFiler.Test` and `UtilitiesCS.Test` suites in the parallel regime. + +## Next Step +- [x] Promote to GitHub issue (bug-report template) +- [ ] Move to active fix folder / branch + +Consolidates: #905, #906. \ No newline at end of file