diff --git a/UtilitiesCS.Test/HelperClasses/DirectoryInfoWrapper_Tests.cs b/UtilitiesCS.Test/HelperClasses/DirectoryInfoWrapper_Tests.cs index 102a6cb47..6cc0ea0a9 100644 --- a/UtilitiesCS.Test/HelperClasses/DirectoryInfoWrapper_Tests.cs +++ b/UtilitiesCS.Test/HelperClasses/DirectoryInfoWrapper_Tests.cs @@ -13,6 +13,18 @@ namespace UtilitiesCS.Test.HelperClasses [TestClass] public class DirectoryInfoWrapper_Tests { + // Issue #940 fixtures. The rooted literal below is not expected to exist and does not point + // into the repository: the two metadata tests assert only path computations mirrored against + // the BCL object, so the outcome does not depend on the machine. The owned entries are the + // running host's own loaded assembly image and its directory, which exist for the whole run; + // the two enumeration tests only read them. + private const string RootedFixturePath = @"C:\Repo\fixture"; + + private static FileInfo OwnedAssemblyFile => + new FileInfo(typeof(DirectoryInfoWrapper_Tests).Assembly.Location); + + private static DirectoryInfo OwnedAssemblyDirectory => OwnedAssemblyFile.Directory; + [TestMethod] public void Constructor_WhenDirectoryInfoIsNull_ThrowsArgumentNullException() { @@ -27,7 +39,7 @@ public void Constructor_WhenDirectoryInfoIsNull_ThrowsArgumentNullException() public void Properties_ShouldMirrorWrappedDirectoryInfo() { // Arrange - var directory = GetRepositoryRoot(); + var directory = new DirectoryInfo(RootedFixturePath); var wrapper = new DirectoryInfoWrapper(directory); // Assert @@ -43,47 +55,54 @@ public void Properties_ShouldMirrorWrappedDirectoryInfo() public void GetDirectoriesAndGetFiles_ShouldReturnWrappedEntries() { // Arrange - var directory = GetRepositoryRoot(); + var directory = OwnedAssemblyDirectory; var wrapper = new DirectoryInfoWrapper(directory); + var parentWrapper = new DirectoryInfoWrapper(directory.Parent); // Act - var directories = wrapper.GetDirectories(); var files = wrapper.GetFiles(); + var directories = parentWrapper.GetDirectories(); // Assert - directories.Should().NotBeEmpty(); - directories.Should().OnlyContain(item => item is DirectoryInfoWrapper); - directories.Select(item => item.Name).Should().Contain("UtilitiesCS"); - files.Should().NotBeEmpty(); files.Should().OnlyContain(item => item is FileInfoWrapper); - files.Select(item => item.Name).Should().Contain("TaskMaster.sln"); + files.Select(item => item.Name).Should().Contain(OwnedAssemblyFile.Name); + + directories.Should().NotBeEmpty(); + directories.Should().OnlyContain(item => item is DirectoryInfoWrapper); + directories.Select(item => item.Name).Should().Contain(directory.Name); } [TestMethod] public void EnumerateFileSystemInfos_ShouldWrapDirectoriesAndFiles() { // Arrange - var directory = GetRepositoryRoot(); + var directory = OwnedAssemblyDirectory; var wrapper = new DirectoryInfoWrapper(directory); + var parentWrapper = new DirectoryInfoWrapper(directory.Parent); // Act var fileSystemInfos = wrapper.EnumerateFileSystemInfos().ToArray(); + var parentFileSystemInfos = parentWrapper.EnumerateFileSystemInfos().ToArray(); // Assert fileSystemInfos + .OfType() + .Select(item => item.Name) .Should() - .Contain(item => item is DirectoryInfoWrapper && item.Name == "UtilitiesCS"); - fileSystemInfos + .Contain(OwnedAssemblyFile.Name); + parentFileSystemInfos + .OfType() + .Select(item => item.Name) .Should() - .Contain(item => item is FileInfoWrapper && item.Name == "TaskMaster.sln"); + .Contain(directory.Name); } [TestMethod] public void ToString_ShouldDelegateToWrappedDirectoryInfo() { // Arrange - var directory = GetRepositoryRoot(); + var directory = new DirectoryInfo(RootedFixturePath); var wrapper = new DirectoryInfoWrapper(directory); // Act @@ -371,23 +390,5 @@ public void LifecycleAndAccessControlMethods_ShouldDelegateToWrappedIDirectoryIn wrapper.SetAccessControl(directorySecurity); wrapper.ToString().Should().Be("wrapped-directory"); } - - private static DirectoryInfo GetRepositoryRoot() - { - var current = new DirectoryInfo(AppDomain.CurrentDomain.BaseDirectory); - - while ( - current is not null - && !File.Exists(Path.Combine(current.FullName, "TaskMaster.sln")) - ) - { - current = current.Parent; - } - - current - .Should() - .NotBeNull("the test assembly should run inside the TaskMaster repository"); - return current; - } } } diff --git a/UtilitiesCS.Test/HelperClasses/PhysicalFileSystemAdapters_Tests.cs b/UtilitiesCS.Test/HelperClasses/PhysicalFileSystemAdapters_Tests.cs index cd03757e9..b7107cddf 100644 --- a/UtilitiesCS.Test/HelperClasses/PhysicalFileSystemAdapters_Tests.cs +++ b/UtilitiesCS.Test/HelperClasses/PhysicalFileSystemAdapters_Tests.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using System.Linq; using System.Reflection; using System.Runtime.Serialization; using System.Security.AccessControl; @@ -12,12 +13,28 @@ namespace UtilitiesCS.Test.HelperClasses [TestClass] public class PhysicalFileSystemAdapters_Tests { + // Issue #940 fixtures. The running host's own loaded assembly image exists for the whole + // run and its physical path is the project output directory, so that directory and its + // parent are owned by this test process; no repository file and no temporary file is + // involved. Every mutating member is invoked either on a path under the owned directory + // that is asserted absent beforehand, so the wrapped BCL member reports the missing path + // before touching the disk, or as a no-op on an owned entry that already exists. + private static FileInfo OwnedAssemblyFile => + new FileInfo(typeof(PhysicalFileSystemAdapters_Tests).Assembly.Location); + + private static DirectoryInfo OwnedAssemblyDirectory => OwnedAssemblyFile.Directory; + + private static string MissingOwnedPath(string suffix) => + Path.Combine(OwnedAssemblyDirectory.FullName, "__940_missing_" + suffix); + [TestMethod] - public void PhysicalDirectoryInfoAdapter_PropertiesEnumerationAndAccessors_MirrorDirectoryInfo() + public void PhysicalDirectoryInfoAdapter_AccessorsAndNoOpCreation_MirrorOwnedDirectory() { // Arrange - var directory = GetRepositoryRoot(); + var directory = OwnedAssemblyDirectory; + var parent = directory.Parent; var adapter = new PhysicalDirectoryInfoAdapter(directory); + var parentAdapter = new PhysicalDirectoryInfoAdapter(parent); var security = adapter.GetAccessControl(); var serialized = new SerializationInfo( typeof(PhysicalDirectoryInfoAdapter), @@ -25,134 +42,200 @@ public void PhysicalDirectoryInfoAdapter_PropertiesEnumerationAndAccessors_Mirro ); var context = new StreamingContext(StreamingContextStates.All); - // Act - adapter.Attributes = adapter.Attributes; - - // Timestamp-setter delegation is structurally identical to the getter delegation - // (_directoryInfo.CreationTime = value) but may throw IOException when VS Code - // file watchers or the test host hold the directory handle open. - try - { - adapter.CreationTime = adapter.CreationTime; - adapter.CreationTimeUtc = adapter.CreationTimeUtc; - adapter.LastAccessTime = adapter.LastAccessTime; - adapter.LastAccessTimeUtc = adapter.LastAccessTimeUtc; - adapter.LastWriteTime = adapter.LastWriteTime; - adapter.LastWriteTimeUtc = adapter.LastWriteTimeUtc; - } - catch (IOException) - { - // Filesystem contention is expected in shared environments. - } - + // Act: the creation members are invoked on entries that already exist, so nothing + // is created; the returned wrappers name the owned directory, which proves both the + // delegation and the wrapping. adapter.Create(); adapter.Create(directory.GetAccessControl()); - - var createdSubdirectory = adapter.CreateSubdirectory("UtilitiesCS"); - var createdSubdirectoryWithSecurity = adapter.CreateSubdirectory( - "UtilitiesCS", - directory.GetAccessControl() + var createdSubdirectory = parentAdapter.CreateSubdirectory(directory.Name); + var createdSubdirectoryWithSecurity = parentAdapter.CreateSubdirectory( + directory.Name, + parent.GetAccessControl() ); - var enumeratedDirectories = adapter.EnumerateDirectories(); - var enumeratedDirectoriesByPattern = adapter.EnumerateDirectories("UtilitiesCS*"); - var enumeratedDirectoriesByPatternAndOption = adapter.EnumerateDirectories( - "UtilitiesCS*", + var accessWithSections = adapter.GetAccessControl(AccessControlSections.Access); + adapter.GetObjectData(serialized, context); + adapter.Refresh(); + adapter.SetAccessControl(security); + var toStringValue = adapter.ToString(); + + // Assert + adapter.Exists.Should().BeTrue(); + adapter.Attributes.Should().Be(directory.Attributes); + adapter.CreationTime.Should().Be(directory.CreationTime); + adapter.CreationTimeUtc.Should().Be(directory.CreationTimeUtc); + adapter.LastAccessTime.Should().Be(directory.LastAccessTime); + adapter.LastAccessTimeUtc.Should().Be(directory.LastAccessTimeUtc); + adapter.LastWriteTime.Should().Be(directory.LastWriteTime); + adapter.LastWriteTimeUtc.Should().Be(directory.LastWriteTimeUtc); + adapter.Extension.Should().Be(directory.Extension); + adapter.FullName.Should().Be(directory.FullName); + adapter.Name.Should().Be(directory.Name); + adapter.Parent.FullName.Should().Be(parent.FullName); + adapter.Root.FullName.Should().Be(directory.Root.FullName); + createdSubdirectory.FullName.Should().Be(directory.FullName); + createdSubdirectoryWithSecurity.FullName.Should().Be(directory.FullName); + security.Should().NotBeNull(); + accessWithSections.Should().NotBeNull(); + toStringValue.Should().Be(directory.ToString()); + serialized.MemberCount.Should().BeGreaterThan(0, "GetObjectData adds values"); + } + + [TestMethod] + public void PhysicalDirectoryInfoAdapter_Enumeration_WrapsOwnedDirectoryEntries() + { + // Arrange + var directory = OwnedAssemblyDirectory; + var fileName = OwnedAssemblyFile.Name; + var directoryName = directory.Name; + var adapter = new PhysicalDirectoryInfoAdapter(directory); + var parentAdapter = new PhysicalDirectoryInfoAdapter(directory.Parent); + + // Act: files are enumerated from the owned directory and directories from its + // parent, because the parent containing the owned directory is guaranteed by + // construction while subdirectories inside the owned directory are not. + var enumeratedDirectories = parentAdapter.EnumerateDirectories(); + var enumeratedDirectoriesByPattern = parentAdapter.EnumerateDirectories(directoryName); + var enumeratedDirectoriesByPatternAndOption = parentAdapter.EnumerateDirectories( + directoryName, SearchOption.TopDirectoryOnly ); var enumeratedFiles = adapter.EnumerateFiles(); - var enumeratedFilesByPattern = adapter.EnumerateFiles("*.sln"); + var enumeratedFilesByPattern = adapter.EnumerateFiles(fileName); var enumeratedFilesByPatternAndOption = adapter.EnumerateFiles( - "*.sln", + fileName, SearchOption.TopDirectoryOnly ); - var enumeratedFileSystemInfos = adapter.EnumerateFileSystemInfos(); - var enumeratedFileSystemInfosByPattern = adapter.EnumerateFileSystemInfos("*"); - var enumeratedFileSystemInfosByPatternAndOption = adapter.EnumerateFileSystemInfos( - "*", + var enumeratedInfos = adapter.EnumerateFileSystemInfos(); + var enumeratedInfosByPattern = parentAdapter.EnumerateFileSystemInfos(directoryName); + var enumeratedInfosByPatternAndOption = adapter.EnumerateFileSystemInfos( + fileName, SearchOption.TopDirectoryOnly ); - var directories = adapter.GetDirectories(); - var directoriesByPattern = adapter.GetDirectories("UtilitiesCS*"); - var directoriesByPatternAndOption = adapter.GetDirectories( - "UtilitiesCS*", + var directories = parentAdapter.GetDirectories(); + var directoriesByPattern = parentAdapter.GetDirectories(directoryName); + var directoriesByPatternAndOption = parentAdapter.GetDirectories( + directoryName, SearchOption.TopDirectoryOnly ); var files = adapter.GetFiles(); - var filesByPattern = adapter.GetFiles("*.sln"); - var filesByPatternAndOption = adapter.GetFiles("*.sln", SearchOption.TopDirectoryOnly); - var fileSystemInfos = adapter.GetFileSystemInfos(); - var fileSystemInfosByPattern = adapter.GetFileSystemInfos("*"); - var fileSystemInfosByPatternAndOption = adapter.GetFileSystemInfos( - "*", + var filesByPattern = adapter.GetFiles(fileName); + var filesByPatternAndOption = adapter.GetFiles(fileName, SearchOption.TopDirectoryOnly); + var infos = parentAdapter.GetFileSystemInfos(); + var infosByPattern = adapter.GetFileSystemInfos(fileName); + var infosByPatternAndOption = parentAdapter.GetFileSystemInfos( + directoryName, SearchOption.TopDirectoryOnly ); - var accessWithSections = adapter.GetAccessControl(AccessControlSections.Access); - adapter.GetObjectData(serialized, context); - adapter.Refresh(); - adapter.SetAccessControl(security); - var toStringValue = adapter.ToString(); // Assert - adapter.Exists.Should().BeTrue(); - adapter.FullName.Should().Be(directory.FullName); - adapter.Name.Should().Be(directory.Name); - adapter.Parent.FullName.Should().Be(directory.Parent.FullName); - adapter.Root.FullName.Should().Be(directory.Root.FullName); - createdSubdirectory.FullName.Should().Contain("UtilitiesCS"); - createdSubdirectoryWithSecurity.FullName.Should().Contain("UtilitiesCS"); - enumeratedDirectories.Should().Contain(item => item.Name == "UtilitiesCS"); + files.Select(item => item.Name).Should().Contain(fileName); + files.Should().OnlyContain(item => item is FileInfoWrapper); + filesByPattern.Should().ContainSingle().Which.Name.Should().Be(fileName); + filesByPatternAndOption.Should().ContainSingle().Which.Name.Should().Be(fileName); + enumeratedFiles.Select(item => item.Name).Should().Contain(fileName); + enumeratedFilesByPattern.Should().ContainSingle().Which.Name.Should().Be(fileName); + enumeratedFilesByPatternAndOption + .Should() + .ContainSingle() + .Which.Name.Should() + .Be(fileName); + directories.Select(item => item.Name).Should().Contain(directoryName); + directories.Should().OnlyContain(item => item is DirectoryInfoWrapper); + directoriesByPattern.Should().ContainSingle().Which.Name.Should().Be(directoryName); + directoriesByPatternAndOption + .Should() + .ContainSingle() + .Which.Name.Should() + .Be(directoryName); + enumeratedDirectories.Select(item => item.Name).Should().Contain(directoryName); enumeratedDirectoriesByPattern .Should() - .ContainSingle(item => item.Name == "UtilitiesCS"); + .ContainSingle() + .Which.Name.Should() + .Be(directoryName); enumeratedDirectoriesByPatternAndOption .Should() - .ContainSingle(item => item.Name == "UtilitiesCS"); - enumeratedFiles.Should().Contain(item => item.Name == "TaskMaster.sln"); - enumeratedFilesByPattern.Should().ContainSingle(item => item.Name == "TaskMaster.sln"); - enumeratedFilesByPatternAndOption + .ContainSingle() + .Which.Name.Should() + .Be(directoryName); + enumeratedInfos + .OfType() + .Select(item => item.Name) .Should() - .ContainSingle(item => item.Name == "TaskMaster.sln"); - enumeratedFileSystemInfos.Should().Contain(item => item.Name == "UtilitiesCS"); - enumeratedFileSystemInfos.Should().Contain(item => item.Name == "TaskMaster.sln"); - enumeratedFileSystemInfosByPattern.Should().Contain(item => item.Name == "UtilitiesCS"); - enumeratedFileSystemInfosByPatternAndOption + .Contain(fileName); + enumeratedInfosByPattern .Should() - .Contain(item => item.Name == "UtilitiesCS"); - directories.Should().Contain(item => item.Name == "UtilitiesCS"); - directoriesByPattern.Should().ContainSingle(item => item.Name == "UtilitiesCS"); - directoriesByPatternAndOption + .ContainSingle() + .Which.Should() + .BeOfType(); + enumeratedInfosByPatternAndOption .Should() - .ContainSingle(item => item.Name == "UtilitiesCS"); - files.Should().Contain(item => item.Name == "TaskMaster.sln"); - filesByPattern.Should().ContainSingle(item => item.Name == "TaskMaster.sln"); - filesByPatternAndOption.Should().ContainSingle(item => item.Name == "TaskMaster.sln"); - fileSystemInfos.Should().Contain(item => item.Name == "TaskMaster.sln"); - fileSystemInfosByPattern.Should().Contain(item => item.Name == "UtilitiesCS"); - fileSystemInfosByPatternAndOption.Should().Contain(item => item.Name == "UtilitiesCS"); - security.Should().NotBeNull(); - accessWithSections.Should().NotBeNull(); - toStringValue.Should().Be(directory.ToString()); + .ContainSingle() + .Which.Should() + .BeOfType(); + infos + .OfType() + .Select(item => item.Name) + .Should() + .Contain(directoryName); + infosByPattern.Should().ContainSingle().Which.Should().BeOfType(); + infosByPatternAndOption + .Should() + .ContainSingle() + .Which.Should() + .BeOfType(); + } + + [TestMethod] + public void PhysicalDirectoryInfoAdapter_SettersOnMissingDirectory_ThrowWithoutCreatingEntries() + { + // Arrange + var adapter = new PhysicalDirectoryInfoAdapter( + new DirectoryInfo(MissingOwnedPath("directory-setters")) + ); + var stamp = new DateTime(2024, 1, 2, 3, 4, 5, DateTimeKind.Utc); + adapter.Exists.Should().BeFalse(); + + // Act + Action setCreationTime = () => adapter.CreationTime = stamp; + Action setCreationTimeUtc = () => adapter.CreationTimeUtc = stamp; + Action setLastAccessTime = () => adapter.LastAccessTime = stamp; + Action setLastAccessTimeUtc = () => adapter.LastAccessTimeUtc = stamp; + Action setLastWriteTime = () => adapter.LastWriteTime = stamp; + Action setLastWriteTimeUtc = () => adapter.LastWriteTimeUtc = stamp; + Action setAttributes = () => adapter.Attributes = FileAttributes.Directory; + + // Assert: each setter reaches the wrapped BCL member, which reports the missing path. + setCreationTime.Should().Throw(); + setCreationTimeUtc.Should().Throw(); + setLastAccessTime.Should().Throw(); + setLastAccessTimeUtc.Should().Throw(); + setLastWriteTime.Should().Throw(); + setLastWriteTimeUtc.Should().Throw(); + setAttributes.Should().Throw(); + adapter.Refresh(); + adapter.Exists.Should().BeFalse(); } [TestMethod] public void PhysicalDirectoryInfoAdapter_MissingDirectoryAndUnsupportedInfo_BranchesBehaveAsExpected() { // Arrange - var missingDirectoryPath = Path.Combine( - GetRepositoryRoot().FullName, - "__missing_physical_directory_adapter__" + var adapter = new PhysicalDirectoryInfoAdapter( + new DirectoryInfo(MissingOwnedPath("directory-delete")) ); - var adapter = new PhysicalDirectoryInfoAdapter(new DirectoryInfo(missingDirectoryPath)); var wrapMethod = typeof(PhysicalDirectoryInfoAdapter).GetMethod( "WrapFileSystemInfo", BindingFlags.Static | BindingFlags.NonPublic )!; + var directoryMoveTarget = MissingOwnedPath("directory-moved"); + adapter.Exists.Should().BeFalse(); + Directory.Exists(directoryMoveTarget).Should().BeFalse(); // Act Action delete = () => adapter.Delete(); Action deleteRecursive = () => adapter.Delete(recursive: true); - Action move = () => - adapter.MoveTo(Path.Combine(GetRepositoryRoot().FullName, "__moved")); + Action move = () => adapter.MoveTo(directoryMoveTarget); Action wrapUnsupported = () => wrapMethod.Invoke(null, new object[] { new UnsupportedInfo() }); @@ -170,7 +253,7 @@ public void PhysicalDirectoryInfoAdapter_MissingDirectoryAndUnsupportedInfo_Bran public void PhysicalFileInfoAdapter_PropertiesStreamsAndAccessors_MirrorFileInfo() { // Arrange - var file = GetSolutionFile(); + var file = OwnedAssemblyFile; var adapter = new PhysicalFileInfoAdapter(file); var security = adapter.GetAccessControl(); var serialized = new SerializationInfo( @@ -179,36 +262,9 @@ public void PhysicalFileInfoAdapter_PropertiesStreamsAndAccessors_MirrorFileInfo ); var context = new StreamingContext(StreamingContextStates.All); - // Act — exercise timestamp setters on the bin-dir copy of the test DLL to avoid - // IOException on the solution file, which may be held open by VS Code or MSBuild. - // If even the test DLL is locked, accept the IOException since the setter - // delegation is structurally identical to the getter delegation. - try - { - adapter.CreationTime = adapter.CreationTime; - adapter.CreationTimeUtc = adapter.CreationTimeUtc; - adapter.LastAccessTime = adapter.LastAccessTime; - adapter.LastAccessTimeUtc = adapter.LastAccessTimeUtc; - adapter.LastWriteTime = adapter.LastWriteTime; - adapter.LastWriteTimeUtc = adapter.LastWriteTimeUtc; - } - catch (IOException) - { - // Filesystem contention is expected in shared environments. - } - - adapter.IsReadOnly = adapter.IsReadOnly; - - // Read-only stream methods stay on the real solution file. OpenRead()/OpenText() - // internally request FileShare.Read (not FileShare.ReadWrite); FileShare.Read is - // compatible with any other handle on the file that also permits read-sharing (the - // default sharing mode used by checkout/build/coverage tooling), so those opens do not - // contend with the checkout/build process that may hold TaskMaster.sln open and remain - // deterministic under parallel CI execution. Only the exclusive FileShare.None request - // from the 2-arg Open(FileMode, FileAccess) overload conflicted with a concurrently - // open handle, which is why that overload is now seamed (see the seamAdapter section - // below) while OpenRead()/OpenText() are left unseamed and continue to run against the - // real TaskMaster.sln. + // Act: the read-only opens target the running host's own loaded assembly image. The + // three-argument Open requests read-write sharing; OpenRead and OpenText request read + // sharing, which admits the loader's own read handles on the image. bool openModeReadSharedCanRead; using ( var openModeReadShared = adapter.Open( @@ -233,21 +289,11 @@ public void PhysicalFileInfoAdapter_PropertiesStreamsAndAccessors_MirrorFileInfo openTextLine = openText.ReadLine(); } - // Write-mode opens (AppendText, Open(FileMode.Open) which defaults to ReadWrite, - // OpenWrite) must not target the shared TaskMaster.sln: under parallel CI the file is - // held open by another process and the write handle throws IOException. The seam also - // now covers the read-mode Open(FileMode, FileAccess) overload; its default - // FileShare.None behavior — not its read/write direction — is what requires the seam, - // since an exclusive handle request is what conflicts with a concurrently open handle - // on the shared file. They must also not touch any temporary/scratch file, which the - // unit-test policy prohibits. Instead, construct the adapter through its internal - // injectable-delegate seam so these members return test-owned sentinel streams. This - // covers the adapter's AppendText/Open(mode)/Open(mode, access)/OpenWrite delegation - // lines deterministically without acquiring any real write/append or FileShare.None - // handle. The sentinel streams are read-only opens of the test assembly DLL (the same - // deterministic pattern used in FileInfoWrapper_Tests). - // The append sentinel wraps an in-memory stream because StreamWriter requires a - // writable backing stream; the read-only DLL opens below cannot back a StreamWriter. + // The write-mode members and the two-argument Open are exercised through the + // adapter's internal injectable-delegate seam with test-owned sentinel streams, so no + // write handle and no exclusive handle is ever requested on the image. The append + // sentinel wraps an in-memory stream because StreamWriter requires a writable backing + // stream; the other sentinels are read-only opens of the image with read-write sharing. using var sentinelAppendStream = new MemoryStream(); using var sentinelAppendWriter = new StreamWriter( sentinelAppendStream, @@ -289,12 +335,20 @@ public void PhysicalFileInfoAdapter_PropertiesStreamsAndAccessors_MirrorFileInfo // Assert adapter.Exists.Should().BeTrue(); - adapter.Extension.Should().Be(".sln"); + adapter.Extension.Should().Be(".dll"); adapter.FullName.Should().Be(file.FullName); adapter.Name.Should().Be(file.Name); adapter.Directory.FullName.Should().Be(file.Directory.FullName); adapter.DirectoryName.Should().Be(file.DirectoryName); adapter.Length.Should().BeGreaterThan(0); + adapter.IsReadOnly.Should().Be(file.IsReadOnly); + adapter.Attributes.Should().Be(file.Attributes); + adapter.CreationTime.Should().Be(file.CreationTime); + adapter.CreationTimeUtc.Should().Be(file.CreationTimeUtc); + adapter.LastAccessTime.Should().Be(file.LastAccessTime); + adapter.LastAccessTimeUtc.Should().Be(file.LastAccessTimeUtc); + adapter.LastWriteTime.Should().Be(file.LastWriteTime); + adapter.LastWriteTimeUtc.Should().Be(file.LastWriteTimeUtc); openModeReadSharedCanRead.Should().BeTrue(); openReadCanRead.Should().BeTrue(); openTextLine.Should().NotBeNull(); @@ -308,34 +362,68 @@ public void PhysicalFileInfoAdapter_PropertiesStreamsAndAccessors_MirrorFileInfo security.Should().NotBeNull(); accessWithSections.Should().NotBeNull(); toStringValue.Should().Be(file.ToString()); + serialized.MemberCount.Should().BeGreaterThan(0, "GetObjectData adds values"); } [TestMethod] - public void PhysicalFileInfoAdapter_MissingFileBranches_ThrowOrNoOpWithoutCreatingFiles() + public void PhysicalFileInfoAdapter_SettersOnMissingFile_ThrowWithoutCreatingFiles() { // Arrange - var root = GetRepositoryRoot(); - var solution = GetSolutionFile(); - var missingPath = Path.Combine(root.FullName, "__missing_physical_file_adapter__.txt"); - var adapter = new PhysicalFileInfoAdapter(new FileInfo(missingPath)); + var adapter = new PhysicalFileInfoAdapter( + new FileInfo(MissingOwnedPath("file-setters.txt")) + ); + var stamp = new DateTime(2024, 1, 2, 3, 4, 5, DateTimeKind.Utc); + adapter.Exists.Should().BeFalse(); + + // Act + Action setIsReadOnly = () => adapter.IsReadOnly = true; + Action setAttributes = () => adapter.Attributes = FileAttributes.Normal; + Action setCreationTime = () => adapter.CreationTime = stamp; + Action setCreationTimeUtc = () => adapter.CreationTimeUtc = stamp; + Action setLastAccessTime = () => adapter.LastAccessTime = stamp; + Action setLastAccessTimeUtc = () => adapter.LastAccessTimeUtc = stamp; + Action setLastWriteTime = () => adapter.LastWriteTime = stamp; + Action setLastWriteTimeUtc = () => adapter.LastWriteTimeUtc = stamp; + + // Assert: each setter reaches the wrapped BCL member, which reports the missing path. + setIsReadOnly.Should().Throw(); + setAttributes.Should().Throw(); + setCreationTime.Should().Throw(); + setCreationTimeUtc.Should().Throw(); + setLastAccessTime.Should().Throw(); + setLastAccessTimeUtc.Should().Throw(); + setLastWriteTime.Should().Throw(); + setLastWriteTimeUtc.Should().Throw(); + adapter.Refresh(); + adapter.Exists.Should().BeFalse(); + } + + [TestMethod] + public void PhysicalFileInfoAdapter_MissingFileBranches_ThrowOrNoOpWithoutCreatingFiles() + { + // Arrange: every destination and backup is another missing owned path, so no existing + // file is a target under any outcome. + var adapter = new PhysicalFileInfoAdapter( + new FileInfo(MissingOwnedPath("file-source.txt")) + ); + var copyTarget = MissingOwnedPath("file-copy.txt"); + var moveTarget = MissingOwnedPath("file-moved.txt"); + var replaceTarget = MissingOwnedPath("file-replace.txt"); + var backupTarget = MissingOwnedPath("file-backup.bak"); + adapter.Exists.Should().BeFalse(); + File.Exists(copyTarget).Should().BeFalse(); + File.Exists(moveTarget).Should().BeFalse(); + File.Exists(replaceTarget).Should().BeFalse(); + File.Exists(backupTarget).Should().BeFalse(); // Act Action delete = () => adapter.Delete(); - Action copy = () => adapter.CopyTo(solution.FullName); - Action copyOverwrite = () => adapter.CopyTo(solution.FullName, overwrite: true); - Action move = () => - adapter.MoveTo(Path.Combine(root.FullName, "__moved_missing_file__.txt")); - Action replace = () => - adapter.Replace( - solution.FullName, - Path.Combine(root.FullName, "__missing_backup__.bak") - ); + Action copy = () => adapter.CopyTo(copyTarget); + Action copyOverwrite = () => adapter.CopyTo(copyTarget, overwrite: true); + Action move = () => adapter.MoveTo(moveTarget); + Action replace = () => adapter.Replace(replaceTarget, backupTarget); Action replaceIgnore = () => - adapter.Replace( - solution.FullName, - Path.Combine(root.FullName, "__missing_backup__.bak"), - ignoreMetadataErrors: true - ); + adapter.Replace(replaceTarget, backupTarget, ignoreMetadataErrors: true); // Assert delete.Should().NotThrow(); @@ -346,36 +434,6 @@ public void PhysicalFileInfoAdapter_MissingFileBranches_ThrowOrNoOpWithoutCreati replaceIgnore.Should().Throw(); } - private static DirectoryInfo GetRepositoryRoot() - { - // Assembly.Location gives the physical path of the test DLL, which is - // always inside the repository tree. AppDomain.CurrentDomain.BaseDirectory - // can point to the vstest host directory instead, breaking the walk-up. - var startPath = - Path.GetDirectoryName(typeof(PhysicalFileSystemAdapters_Tests).Assembly.Location) - ?? AppDomain.CurrentDomain.BaseDirectory; - var current = new DirectoryInfo(startPath); - - while ( - current is not null - && !File.Exists(Path.Combine(current.FullName, "TaskMaster.sln")) - ) - { - current = current.Parent; - } - - current - .Should() - .NotBeNull("the test assembly should run inside the TaskMaster repository"); - return current; - } - - private static FileInfo GetSolutionFile() - { - var repositoryRoot = GetRepositoryRoot(); - return new FileInfo(Path.Combine(repositoryRoot.FullName, "TaskMaster.sln")); - } - private sealed class UnsupportedInfo : FileSystemInfo { public override bool Exists => false; diff --git a/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/code-review.2026-09-30T12-00.md b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/code-review.2026-09-30T12-00.md new file mode 100644 index 000000000..17dde934d --- /dev/null +++ b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/code-review.2026-09-30T12-00.md @@ -0,0 +1,68 @@ +# Code Review: filesystem-wrapper-tests-open-repository-solution-file (Issue #940) + +- Branch: `bug/filesystem-wrapper-tests-open-repository-solution-file-940` at `bd71b8160e281054657280af8a7c54eeffe5c563` +- Base: `origin/main` at `66afa6372fd82fc1ffd7c81f85a1ad65eebc5817` (merged at `40e587ce2`) +- Files reviewed in full: `UtilitiesCS.Test/HelperClasses/PhysicalFileSystemAdapters_Tests.cs` (446 lines), `UtilitiesCS.Test/HelperClasses/DirectoryInfoWrapper_Tests.cs` (394 lines); production context read in full: `UtilitiesCS/HelperClasses/FileSystem/PhysicalDirectoryInfoAdapter.cs`, `PhysicalFileInfoAdapter.cs`, `DirectoryInfoWrapper.cs` (unchanged) +- Review timestamp label: `2026-09-30T12-00` +- Blocking findings: **0**. Non-blocking findings: **7** (CR-1 to CR-7). Follow-ups recommended: 3 (F-1 to F-3). + +## Executive Summary + +The change is a test-only rewrite that removes the repository-root walk, the `TaskMaster.sln` dependency and the `IOException` swallowing from two MSTest classes, and replaces them with three owned fixtures (the loaded test-assembly image, its directory, and its parent) plus prefixed missing paths under the output directory. The rewritten tests assert delegation by thrown exception type, by wrapper type, by reference identity against sentinel streams routed through the adapter's existing internal delegate seam, and by Moq strict-mock setups. The code follows MSTest, Moq and FluentAssertions, creates no temporary file, introduces no serialization, sleep, retry or timeout, and keeps both files under the 500-line limit. Eleven negative controls demonstrate that each rewritten test fails when the delegation it verifies is broken. + +No blocking defect was found. The seven non-blocking findings concern a real (idempotent) ACL write admitted by the amended acceptance criteria, coarse-grained test methods, missing AAA markers in untouched tests, a small duplication in the sentinel-stream setup, a rooted-literal probe outside the repository, a parent-directory assumption in the enumeration test, and the post-merge format check being delegated to CI. + +## Findings Table + +| Severity | File | Location | Finding | Recommendation | Rationale | Evidence | +| --- | --- | --- | --- | --- | --- | --- | +| Non-blocking (Minor) | `UtilitiesCS.Test/HelperClasses/PhysicalFileSystemAdapters_Tests.cs` | lines 58 and 333 (`adapter.SetAccessControl(security)`) | CR-1: `SetAccessControl` with the security object just read back is a real DACL write (identical content) on the test output directory and on the loaded assembly image. It is idempotent in content, but it is the one call in the suite that writes security metadata to an entry other test classes are concurrently reading (Workers 0, class-level parallelism), and it requires WRITE_DAC on the output tree. | Accept for this item (AC3 and AC4, as amended on 2026-09-30, admit `SetAccessControl` with an unmodified security object as a no-op by construction). File follow-up F-1: route `SetAccessControl` through the existing internal delegate seam of `PhysicalFileInfoAdapter` and a matching seam on `PhysicalDirectoryInfoAdapter`, so that the suite issues no ACL write at all. | UT4 "must not rely on mutable global state"; a DACL rewrite is a write even when the value is unchanged. Four full-suite runs and two scoped parallel runs passed, so no observed effect. | `p2-t14-ac3-mutating-call-inventory.2026-09-30T11-31.md` classes `owned output directory` and `existing owned entry`; issue.md AC3/AC4 text; `test-run-final.md`. | +| Non-blocking (Minor) | `UtilitiesCS.Test/HelperClasses/PhysicalFileSystemAdapters_Tests.cs` | `PhysicalDirectoryInfoAdapter_AccessorsAndNoOpCreation_MirrorOwnedDirectory` (lines 31 to 81), `PhysicalDirectoryInfoAdapter_Enumeration_WrapsOwnedDirectoryEntries` (84 to 187), `PhysicalFileInfoAdapter_PropertiesStreamsAndAccessors_MirrorFileInfo` (253 to 366) | CR-2: each method asserts 20 to 30 members of one type. A failure stops at the first failed assertion, so one run reports one broken member. | Keep for this item (the pre-existing files used the same shape, and splitting would exceed the 500-line limit without a second file). Consider splitting per member family in a follow-up if these tests start failing in practice. | UT1 "Isolation": failures should clearly identify the faulty unit. FluentAssertions names the member expression in the message (verified in the C1, C5 and C8 control messages), which mitigates. | Control records C1, C5, C8. | +| Non-blocking (Minor) | `UtilitiesCS.Test/HelperClasses/DirectoryInfoWrapper_Tests.cs` | `Properties_ShouldMirrorWrappedDirectoryInfo` (lines 39 to 52, no `// Act`); `PropertyDelegates_ShouldMirrorMockedIDirectoryInfo` (116 to 203), `EnumerationAndArrayMethods_ShouldDelegateToWrappedIDirectoryInfo` (206 to 348), `LifecycleAndAccessControlMethods_ShouldDelegateToWrappedIDirectoryInfo` (351 to 392) with no AAA markers | CR-3: AAA structure is present in substance but unmarked in four tests; the three Moq tests are untouched by this diff. | Leave unchanged (outside the rewrite; a marker-only edit after the negative controls would violate the plan's `WRITE SET CHANGED AFTER CONTROLS` rule). Add markers opportunistically when these tests are next touched. | UT3 Arrange-Act-Assert. | Diff spans in `p1-t4-diw-census.2026-09-30T07-31.md` (edits at pre-edit lines 30, 43 to 61, 64 to 80, 86, 374 to 391, and the fixture insert). | +| Non-blocking (Minor) | `UtilitiesCS.Test/HelperClasses/PhysicalFileSystemAdapters_Tests.cs` | lines 304 to 321 | CR-4: three separate read-only `FileStream` opens of the same image serve as three sentinels that are only compared by reference. One shared sentinel would suffice for the identity assertions of `Open(mode)`, `Open(mode, access)` and `OpenWrite()`. | Optional simplification in a follow-up; no behavior impact. | Design principle "simplicity first". | File read. | +| Non-blocking (Info) | `UtilitiesCS.Test/HelperClasses/DirectoryInfoWrapper_Tests.cs` | line 21 (`RootedFixturePath = @"C:\Repo\fixture"`), tests at 39 and 102 | CR-5: `wrapper.Exists.Should().Be(directory.Exists)` performs a read-only existence probe of a rooted path outside the repository. Both sides read the same `DirectoryInfo`, so the assertion holds whether or not the path exists on a machine; no disk write occurs. | None. Documented in the fixture comment (lines 16 to 20). | Determinism: outcome independent of the machine. | File read; control C8 and C11 messages print the literal, confirming it is a fixture constant, not a host path. | +| Non-blocking (Info) | `UtilitiesCS.Test/HelperClasses/PhysicalFileSystemAdapters_Tests.cs` | lines 25, 35, 91; `ContainSingle()` assertions over `parentAdapter` at 144 to 160 and 182 to 186 | CR-6: the enumeration test assumes the output directory's parent (`bin`) exists and contains exactly one directory named like the output directory (`Debug`). True for the Debug/AnyCPU build the plan and CI run; a shadow-copied or drive-root assembly location would break the assumption. | None for this item; note for future test authors. | Determinism under alternative runners. | File read; `evidence/qa-gates/p2-t10-scope-boundary` confirms runsettings unchanged. | +| Non-blocking (Evidence) | toolchain evidence | `evidence/qa-gates/toolchain-pass.md` line 12 vs `p2-t7-*` records | CR-7: the format check (`dotnet tool run csharpier check .`) was last run at P2-T2 on the pre-merge tree; the analyzer, nullable and coverage steps were re-run post-merge. The two changed files are hash-identical to the checked state, and the merged files come from origin/main. | Orchestrator: confirm the CI `_format-check.yml` job on the PR head before merge. | CLAUDE.md toolchain "in this exact order"; the coordinator ruling scoped the post-merge re-run. | `p2-t2-csharpier-check.2026-09-30T08-08.md`; `p2-t10-scope-boundary` hash table; plan Revision Log entry A. | + +## Detailed Review + +### Fixture design + +- `OwnedAssemblyFile => new FileInfo(typeof(...).Assembly.Location)`, `OwnedAssemblyDirectory => OwnedAssemblyFile.Directory`, `MissingOwnedPath(suffix) => Path.Combine(OwnedAssemblyDirectory.FullName, "__940_missing_" + suffix)`. Static expression-bodied properties construct a fresh `FileInfo`/`DirectoryInfo` per access, so no `FileSystemInfo` cache is shared between tests. Verified by reading lines 22 to 28 (PFS) and 23 to 26 (DIW). +- Every missing-path test asserts `adapter.Exists.Should().BeFalse()` before acting, and every destination path (`copyTarget`, `moveTarget`, `replaceTarget`, `backupTarget`, `directoryMoveTarget`) is asserted absent with `File.Exists`/`Directory.Exists` (lines 232 to 233, 413 to 417). The BCL members then fail on the missing source before touching the destination (`CopyTo`, `MoveTo`, `Replace` throw `FileNotFoundException`; `DirectoryInfo.Delete`/`MoveTo` throw `DirectoryNotFoundException`; `FileInfo.Delete` on a missing file is a documented no-op). No path under the output directory is created. + +### Delegation assertions + +- Physical adapters: getters are mirrored against the same wrapped BCL object, so a broken delegation (C1: `Exists` inverted; C8: `Name` returning `FullName`) is detected while a correct one is a tautology by design. Enumeration overloads assert both membership of the owned entry and the wrapper type (`FileInfoWrapper`, `DirectoryInfoWrapper`) so the wrapping step is covered (C2, C10). +- Write-mode members of `PhysicalFileInfoAdapter` (`AppendText`, `Open(mode)`, `Open(mode, access)`, `OpenWrite`) are exercised through the internal five-argument constructor with sentinel streams and asserted `BeSameAs`, so no write or exclusive handle is requested on the image. Verified against `PhysicalFileInfoAdapter.cs` lines 34 to 48 and 118, 146 to 149, 158. +- `DirectoryInfoWrapper` delegation to `IDirectoryInfo` is asserted with `MockBehavior.Strict` mocks and `BeSameAs` on returned children (pre-existing tests); the physical route through `PhysicalDirectoryInfoAdapter` is covered by the two retargeted enumeration tests. + +### Policy checks performed on the two files + +| Check | Result | +| --- | --- | +| Repository root walk / `TaskMaster.sln` / `AppDomain` | absent (Grep and read) | +| `catch` of any type | absent | +| Temporary file creation | absent; no `Path.GetTemp`, `File.Create`, `File.WriteAll`, `Directory.CreateDirectory` | +| `DoNotParallelize`, `[Timeout`, `Thread.Sleep`, `Task.Delay`, retry | absent | +| Runsettings, `coverage.config`, project file | unchanged (P2-T10 `OUT-OF-SET-DIFF-EXIT: 0`, `RUNSETTINGS-HASH-NOW` equal) | +| MSTest / Moq / FluentAssertions | used as required; no MSTest `Assert` | +| File length | 446 and 394 lines (limit 500) | +| Nullable directives, suppressions, `ExcludeFromCodeCoverage` | none added | +| Absolute host paths in artifacts | none in the feature folder (executor sweep 0 matches; reviewer Grep) | + +### Evidence fidelity checks + +- Commit and collector clocks agree with the artifact labels: reflog epochs 1790767471 (Phase 0 commit, 07:24:31 local) / 1790768099 (fix commit, 07:34:59) / 1790781435 (plan revision, 11:17:15) / 1790782454 (final head, 11:34:14) and Cobertura roots 1790767237 (baseline, 07:20:37) / 1790781715 (final3, 11:21:55) bracket the labels `07-24`, `07-35`, `11-19`, `11-33`, `07-22`, `11-22` in order. +- The two Cobertura documents carry different root counters (56084 vs 56092 covered lines) and different class-node rates for the three carried files, so they are two runs, not one copy. +- Each of the eleven control records carries a mutated-run `MESSAGE` containing the plan's predicted phrase, a `PROD_CSC_OUT_LINES: 2` proof that the mutated production file was recompiled, a revert with `REVERT-DIFF-EXIT: 0`, the production file's SHA-256 equal to its anchor, and a confirming pass. All eleven read as the plan's Control Table predicts. + +## Follow-ups (owed to the orchestrator; not filed by this item) + +- F-1: seam `SetAccessControl` (both adapters) so that no ACL write remains in the unit-test suite (CR-1). +- F-2: `PhysicalFileInfoAdapter` constructor null-guard branches (6 of 12 branches uncovered, pre-existing, 50% file-level branch coverage) - add the null-argument tests for the public and internal constructors. +- F-3: `UtilitiesCS.Test/EmailIntelligence/SortEmail_Tests.cs` `TrySaveAttachmentAsync_WhenSaveSucceeds_ReturnsTrueAndCallsSaveAsFile` calls `Directory.CreateDirectory` on the repository root through production code (AC7 classification section 4, `same defect class`); promote to an issue per the repository's potential-to-issue lifecycle. + +## Verdict + +PASS. Blocking findings: 0. Non-blocking findings: 7. The change may proceed to the PR gates listed in `policy-audit.2026-09-30T12-00.md` section 10. diff --git a/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/baseline/coverage-baseline.md b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/baseline/coverage-baseline.md new file mode 100644 index 000000000..019a51df8 --- /dev/null +++ b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/baseline/coverage-baseline.md @@ -0,0 +1,94 @@ +# Baseline Repository-Wide Test and Coverage Run (P0-T11) + +Timestamp: 2026-09-30T07-22 +Task: P0-T11 +Command: CMD-COVERAGE-DIRECT with STAGE baseline: dotnet-coverage collect --output coverage\baseline-940.cobertura.xml --output-format cobertura --settings coverage\effective-coverage-940.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\940\baseline" "/Logger:trx;LogFileName=baseline-940.trx" "/Blame:CollectHangDump;TestTimeout=4min;HangDumpType=None" (vstest resolved through vswhere; the runner script dot-sourced by absolute path for ConvertTo-DerivedCoverageSettingsXml); then CMD-COVERAGE-POST with STAGE baseline and RAW True (runner helpers ConvertTo-KoverageCoberturaXml, Assert-CoberturaLineCoverageThreshold, Assert-CoberturaBranchCoverageThreshold, Get-CoberturaFirstPartyCoverageReport, ConvertTo-JacocoPackageProjection, Assert-JacocoProjectionReconciliation, Get-TrxRunSummary, Format-TrxRunSummary); then CMD-FILE-COMPARE with STAGE baseline (baseline document compared with itself). +EXIT_CODE: 0 +Output Summary: DIRECT route, 9 test assemblies, 7323 tests executed and passed in about 59 seconds (collector started 07-19-34, ended 07-20-37); no hang; both CLAUDE.md floors met; branch (a) applies. +- COVERAGE-ROUTE: DIRECT +- ROUTE-FILTER: TestCategory!=LiveOutlook plus the four-class exclusion (FILTER-UCS-EXCLUDE terms) +- 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/65736 (85.32%), branches 13597/17054 (79.73%) +- FIRST-PARTY-LINE-PERCENT: 85.32 +- FIRST-PARTY-BRANCH-PERCENT: 79.73 +- FAILED-SET: (empty) +- BASELINE-DEFECT-CLASS-FAILURES: NONE +- BRANCH-TAKEN: (a) exit 0 with both floors met + +## TRX-derived summary + +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 + +## JaCoCo package projection + +PROJECTION-BEGIN + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +PROJECTION-END + +UtilitiesCS package (derived): LINE 38815/43423 covered (rate 0.893881), BRANCH 9413/11271 covered (rate 0.835153). + +## Per-file covered-line read (CMD-FILE-COMPARE, baseline against itself; positive control) + +- FILE PhysicalDirectoryInfoAdapter.cs baseline=81/91 final=81/91 NOT-LOWER=True +- FILE PhysicalFileInfoAdapter.cs baseline=69/75 final=69/75 NOT-LOWER=True +- FILE DirectoryInfoWrapper.cs baseline=123/123 final=123/123 NOT-LOWER=True + +coverage\baseline-940.jacoco.xml and coverage\baseline-940.cobertura.xml remain on disk under the git-ignored coverage tree for P2-T7; neither the Cobertura document nor the TRX is copied into the feature folder. diff --git a/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/baseline/p0-t12-pre-edit-census.2026-09-30T07-22.md b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/baseline/p0-t12-pre-edit-census.2026-09-30T07-22.md new file mode 100644 index 000000000..095493f44 --- /dev/null +++ b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/baseline/p0-t12-pre-edit-census.2026-09-30T07-22.md @@ -0,0 +1,439 @@ +# Pre-Edit Token Census (P0-T12) + +Timestamp: 2026-09-30T07-22 +Task: P0-T12 +Command: CMD-CENSUS once for each of UtilitiesCS.Test/HelperClasses/PhysicalFileSystemAdapters_Tests.cs, UtilitiesCS.Test/HelperClasses/DirectoryInfoWrapper_Tests.cs, UtilitiesCS/HelperClasses/FileSystem/PhysicalDirectoryInfoAdapter.cs, UtilitiesCS/HelperClasses/FileSystem/PhysicalFileInfoAdapter.cs, UtilitiesCS/HelperClasses/FileSystem/DirectoryInfoWrapper.cs (the five invocations issued as one payload iterating the five paths with the identical block body) +EXIT_CODE: 0 +Output Summary: every gated `pre` value of the Token Census Expectations table matches; every `at least` token meets its `pre` value; the five SHA256 values equal the P0-T4 PRE-EDIT-HASH values; LINES PFS 388 and DIW 393 as expected. No TREE DIVERGED FROM PLAN condition. +- CENSUS-TOKENS-APPENDED: NONE +- SHA256-MATCH-PRE-EDIT-HASH: PFS YES, DIW YES, PDA YES, PFA YES, DIWP YES + +## PFS: UtilitiesCS.Test/HelperClasses/PhysicalFileSystemAdapters_Tests.cs + +TOKEN GetRepositoryRoot = 6 +TOKEN GetSolutionFile = 3 +TOKEN TaskMaster.sln = 13 +TOKEN AppDomain = 2 +TOKEN File.Exists( = 1 +TOKEN catch (IOException) = 2 +TOKEN catch = 2 +TOKEN Assembly.Location = 5 +TOKEN RootedFixturePath = 0 +TOKEN OwnedAssemblyFile = 0 +TOKEN OwnedAssemblyDirectory = 0 +TOKEN MissingOwnedPath( = 0 +TOKEN __940_missing_ = 0 +TOKEN Exists.Should().BeFalse() = 0 +TOKEN new DirectoryInfo( = 2 +TOKEN new DirectoryInfo(RootedFixturePath) = 0 +TOKEN new DirectoryInfo(MissingOwnedPath( = 0 +TOKEN new FileInfo( = 2 +TOKEN new FileInfo(MissingOwnedPath( = 0 +TOKEN new FileInfo(typeof( = 0 +TOKEN [TestMethod] = 4 +TOKEN using System.Linq; = 0 +TOKEN using Moq; = 0 +TOKEN Thread.Sleep = 0 +TOKEN Task.Delay = 0 +TOKEN [Timeout = 0 +TOKEN DoNotParallelize = 0 +TOKEN Retry = 0 +TOKEN Path.GetTemp = 0 +TOKEN File.Create = 0 +TOKEN File.WriteAll = 0 +TOKEN Directory.CreateDirectory = 0 +TOKEN FileShare.ReadWrite = 5 +TOKEN FileShare.None = 3 +TOKEN .Create() = 1 +TOKEN .Create( = 2 +TOKEN .CreateSubdirectory( = 2 +TOKEN .EnumerateDirectories( = 3 +TOKEN .EnumerateFiles( = 3 +TOKEN .EnumerateFileSystemInfos( = 3 +TOKEN .GetDirectories( = 3 +TOKEN .GetFiles( = 3 +TOKEN .GetFileSystemInfos( = 3 +TOKEN .GetAccessControl( = 6 +TOKEN .GetObjectData( = 2 +TOKEN .Refresh() = 2 +TOKEN .SetAccessControl( = 2 +TOKEN .ToString() = 4 +TOKEN .Delete() = 2 +TOKEN .Delete(recursive: true) = 1 +TOKEN .MoveTo( = 2 +TOKEN .CopyTo( = 2 +TOKEN .Replace( = 2 +TOKEN .Open( = 3 +TOKEN .OpenRead() = 1 +TOKEN .OpenText() = 1 +TOKEN .AppendText() = 1 +TOKEN .OpenWrite() = 1 +TOKEN .Parent = 3 +TOKEN .Root = 2 +TOKEN .Length = 1 +TOKEN .IsReadOnly = 2 +TOKEN .Attributes = 2 +TOKEN .CreationTime = 9 +TOKEN .CreationTimeUtc = 4 +TOKEN .LastAccessTime = 8 +TOKEN .LastAccessTimeUtc = 4 +TOKEN .LastWriteTime = 8 +TOKEN .LastWriteTimeUtc = 4 +TOKEN .MemberCount = 0 +TOKEN Throw() = 5 +TOKEN Throw() = 3 +TOKEN !_directoryInfo.Exists = 0 +TOKEN Array.Empty() = 0 +TOKEN Enumerable.Empty() = 0 +TOKEN set { } = 0 +TOKEN public void Delete() { } = 0 +TOKEN _directoryInfo.FullName = 0 +TOKEN public long Length => 0; = 0 +TOKEN public void MoveTo(string destFileName) { } = 0 +TOKEN return string.Empty; = 0 +LINES = 388 +SHA256 = FD50F380509E730337CD342FC9C56989C41802378D2F773A221E59DBA7B90CF2 + +## DIW: UtilitiesCS.Test/HelperClasses/DirectoryInfoWrapper_Tests.cs + +TOKEN GetRepositoryRoot = 5 +TOKEN GetSolutionFile = 0 +TOKEN TaskMaster.sln = 3 +TOKEN AppDomain = 1 +TOKEN File.Exists( = 1 +TOKEN catch (IOException) = 0 +TOKEN catch = 0 +TOKEN Assembly.Location = 0 +TOKEN RootedFixturePath = 0 +TOKEN OwnedAssemblyFile = 0 +TOKEN OwnedAssemblyDirectory = 0 +TOKEN MissingOwnedPath( = 0 +TOKEN __940_missing_ = 0 +TOKEN Exists.Should().BeFalse() = 0 +TOKEN new DirectoryInfo( = 1 +TOKEN new DirectoryInfo(RootedFixturePath) = 0 +TOKEN new DirectoryInfo(MissingOwnedPath( = 0 +TOKEN new FileInfo( = 0 +TOKEN new FileInfo(MissingOwnedPath( = 0 +TOKEN new FileInfo(typeof( = 0 +TOKEN [TestMethod] = 8 +TOKEN using System.Linq; = 1 +TOKEN using Moq; = 1 +TOKEN Thread.Sleep = 0 +TOKEN Task.Delay = 0 +TOKEN [Timeout = 0 +TOKEN DoNotParallelize = 0 +TOKEN Retry = 0 +TOKEN Path.GetTemp = 0 +TOKEN File.Create = 0 +TOKEN File.WriteAll = 0 +TOKEN Directory.CreateDirectory = 0 +TOKEN FileShare.ReadWrite = 0 +TOKEN FileShare.None = 0 +TOKEN .Create() = 2 +TOKEN .Create( = 4 +TOKEN .CreateSubdirectory( = 4 +TOKEN .EnumerateDirectories( = 6 +TOKEN .EnumerateFiles( = 6 +TOKEN .EnumerateFileSystemInfos( = 7 +TOKEN .GetDirectories( = 7 +TOKEN .GetFiles( = 7 +TOKEN .GetFileSystemInfos( = 6 +TOKEN .GetAccessControl( = 4 +TOKEN .GetObjectData( = 2 +TOKEN .Refresh() = 2 +TOKEN .SetAccessControl( = 2 +TOKEN .ToString() = 4 +TOKEN .Delete() = 2 +TOKEN .Delete(recursive: true) = 1 +TOKEN .MoveTo( = 2 +TOKEN .CopyTo( = 0 +TOKEN .Replace( = 0 +TOKEN .Open( = 0 +TOKEN .OpenRead() = 0 +TOKEN .OpenText() = 0 +TOKEN .AppendText() = 0 +TOKEN .OpenWrite() = 0 +TOKEN .Parent = 5 +TOKEN .Root = 4 +TOKEN .Length = 0 +TOKEN .IsReadOnly = 0 +TOKEN .Attributes = 4 +TOKEN .CreationTime = 8 +TOKEN .CreationTimeUtc = 4 +TOKEN .LastAccessTime = 8 +TOKEN .LastAccessTimeUtc = 4 +TOKEN .LastWriteTime = 8 +TOKEN .LastWriteTimeUtc = 4 +TOKEN .MemberCount = 0 +TOKEN Throw() = 0 +TOKEN Throw() = 0 +TOKEN !_directoryInfo.Exists = 0 +TOKEN Array.Empty() = 0 +TOKEN Enumerable.Empty() = 0 +TOKEN set { } = 0 +TOKEN public void Delete() { } = 0 +TOKEN _directoryInfo.FullName = 0 +TOKEN public long Length => 0; = 0 +TOKEN public void MoveTo(string destFileName) { } = 0 +TOKEN return string.Empty; = 0 +LINES = 393 +SHA256 = DF1DFBBB4CD8806B029A99B41FDBCDEAFF7AD46D54177D4FAC081ACE728EB4B7 + +## PDA: UtilitiesCS/HelperClasses/FileSystem/PhysicalDirectoryInfoAdapter.cs + +TOKEN GetRepositoryRoot = 0 +TOKEN GetSolutionFile = 0 +TOKEN TaskMaster.sln = 0 +TOKEN AppDomain = 0 +TOKEN File.Exists( = 0 +TOKEN catch (IOException) = 0 +TOKEN catch = 0 +TOKEN Assembly.Location = 0 +TOKEN RootedFixturePath = 0 +TOKEN OwnedAssemblyFile = 0 +TOKEN OwnedAssemblyDirectory = 0 +TOKEN MissingOwnedPath( = 0 +TOKEN __940_missing_ = 0 +TOKEN Exists.Should().BeFalse() = 0 +TOKEN new DirectoryInfo( = 0 +TOKEN new DirectoryInfo(RootedFixturePath) = 0 +TOKEN new DirectoryInfo(MissingOwnedPath( = 0 +TOKEN new FileInfo( = 0 +TOKEN new FileInfo(MissingOwnedPath( = 0 +TOKEN new FileInfo(typeof( = 0 +TOKEN [TestMethod] = 0 +TOKEN using System.Linq; = 1 +TOKEN using Moq; = 0 +TOKEN Thread.Sleep = 0 +TOKEN Task.Delay = 0 +TOKEN [Timeout = 0 +TOKEN DoNotParallelize = 0 +TOKEN Retry = 0 +TOKEN Path.GetTemp = 0 +TOKEN File.Create = 0 +TOKEN File.WriteAll = 0 +TOKEN Directory.CreateDirectory = 0 +TOKEN FileShare.ReadWrite = 0 +TOKEN FileShare.None = 0 +TOKEN .Create() = 1 +TOKEN .Create( = 2 +TOKEN .CreateSubdirectory( = 2 +TOKEN .EnumerateDirectories( = 3 +TOKEN .EnumerateFiles( = 3 +TOKEN .EnumerateFileSystemInfos( = 3 +TOKEN .GetDirectories( = 3 +TOKEN .GetFiles( = 3 +TOKEN .GetFileSystemInfos( = 3 +TOKEN .GetAccessControl( = 2 +TOKEN .GetObjectData( = 1 +TOKEN .Refresh() = 1 +TOKEN .SetAccessControl( = 1 +TOKEN .ToString() = 1 +TOKEN .Delete() = 1 +TOKEN .Delete(recursive: true) = 0 +TOKEN .MoveTo( = 1 +TOKEN .CopyTo( = 0 +TOKEN .Replace( = 0 +TOKEN .Open( = 0 +TOKEN .OpenRead() = 0 +TOKEN .OpenText() = 0 +TOKEN .AppendText() = 0 +TOKEN .OpenWrite() = 0 +TOKEN .Parent = 2 +TOKEN .Root = 1 +TOKEN .Length = 0 +TOKEN .IsReadOnly = 0 +TOKEN .Attributes = 2 +TOKEN .CreationTime = 4 +TOKEN .CreationTimeUtc = 2 +TOKEN .LastAccessTime = 4 +TOKEN .LastAccessTimeUtc = 2 +TOKEN .LastWriteTime = 4 +TOKEN .LastWriteTimeUtc = 2 +TOKEN .MemberCount = 0 +TOKEN Throw() = 0 +TOKEN Throw() = 0 +TOKEN !_directoryInfo.Exists = 0 +TOKEN Array.Empty() = 0 +TOKEN Enumerable.Empty() = 0 +TOKEN set { } = 0 +TOKEN public void Delete() { } = 0 +TOKEN _directoryInfo.FullName = 1 +TOKEN public long Length => 0; = 0 +TOKEN public void MoveTo(string destFileName) { } = 0 +TOKEN return string.Empty; = 0 +LINES = 217 +SHA256 = FBAC7002BE4DF5979624F8AE649D52F1044BC40D54EBDA4618C6374EAB9CB242 + +## PFA: UtilitiesCS/HelperClasses/FileSystem/PhysicalFileInfoAdapter.cs + +TOKEN GetRepositoryRoot = 0 +TOKEN GetSolutionFile = 0 +TOKEN TaskMaster.sln = 0 +TOKEN AppDomain = 0 +TOKEN File.Exists( = 0 +TOKEN catch (IOException) = 0 +TOKEN catch = 0 +TOKEN Assembly.Location = 0 +TOKEN RootedFixturePath = 0 +TOKEN OwnedAssemblyFile = 0 +TOKEN OwnedAssemblyDirectory = 0 +TOKEN MissingOwnedPath( = 0 +TOKEN __940_missing_ = 0 +TOKEN Exists.Should().BeFalse() = 0 +TOKEN new DirectoryInfo( = 0 +TOKEN new DirectoryInfo(RootedFixturePath) = 0 +TOKEN new DirectoryInfo(MissingOwnedPath( = 0 +TOKEN new FileInfo( = 0 +TOKEN new FileInfo(MissingOwnedPath( = 0 +TOKEN new FileInfo(typeof( = 0 +TOKEN [TestMethod] = 0 +TOKEN using System.Linq; = 0 +TOKEN using Moq; = 0 +TOKEN Thread.Sleep = 0 +TOKEN Task.Delay = 0 +TOKEN [Timeout = 0 +TOKEN DoNotParallelize = 0 +TOKEN Retry = 0 +TOKEN Path.GetTemp = 0 +TOKEN File.Create = 0 +TOKEN File.WriteAll = 0 +TOKEN Directory.CreateDirectory = 0 +TOKEN FileShare.ReadWrite = 0 +TOKEN FileShare.None = 1 +TOKEN .Create() = 1 +TOKEN .Create( = 1 +TOKEN .CreateSubdirectory( = 0 +TOKEN .EnumerateDirectories( = 0 +TOKEN .EnumerateFiles( = 0 +TOKEN .EnumerateFileSystemInfos( = 0 +TOKEN .GetDirectories( = 0 +TOKEN .GetFiles( = 0 +TOKEN .GetFileSystemInfos( = 0 +TOKEN .GetAccessControl( = 2 +TOKEN .GetObjectData( = 1 +TOKEN .Refresh() = 1 +TOKEN .SetAccessControl( = 1 +TOKEN .ToString() = 1 +TOKEN .Delete() = 1 +TOKEN .Delete(recursive: true) = 0 +TOKEN .MoveTo( = 1 +TOKEN .CopyTo( = 2 +TOKEN .Replace( = 2 +TOKEN .Open( = 1 +TOKEN .OpenRead() = 1 +TOKEN .OpenText() = 1 +TOKEN .AppendText() = 0 +TOKEN .OpenWrite() = 0 +TOKEN .Parent = 0 +TOKEN .Root = 0 +TOKEN .Length = 1 +TOKEN .IsReadOnly = 2 +TOKEN .Attributes = 2 +TOKEN .CreationTime = 4 +TOKEN .CreationTimeUtc = 2 +TOKEN .LastAccessTime = 4 +TOKEN .LastAccessTimeUtc = 2 +TOKEN .LastWriteTime = 4 +TOKEN .LastWriteTimeUtc = 2 +TOKEN .MemberCount = 0 +TOKEN Throw() = 0 +TOKEN Throw() = 0 +TOKEN !_directoryInfo.Exists = 0 +TOKEN Array.Empty() = 0 +TOKEN Enumerable.Empty() = 0 +TOKEN set { } = 0 +TOKEN public void Delete() { } = 0 +TOKEN _directoryInfo.FullName = 0 +TOKEN public long Length => 0; = 0 +TOKEN public void MoveTo(string destFileName) { } = 0 +TOKEN return string.Empty; = 0 +LINES = 183 +SHA256 = 0337E5C1FF7A2E3FF5E5D67D383E68583838AFB5E5BA999A30502CF24333D948 + +## DIWP: UtilitiesCS/HelperClasses/FileSystem/DirectoryInfoWrapper.cs + +TOKEN GetRepositoryRoot = 0 +TOKEN GetSolutionFile = 0 +TOKEN TaskMaster.sln = 0 +TOKEN AppDomain = 0 +TOKEN File.Exists( = 0 +TOKEN catch (IOException) = 0 +TOKEN catch = 0 +TOKEN Assembly.Location = 0 +TOKEN RootedFixturePath = 0 +TOKEN OwnedAssemblyFile = 0 +TOKEN OwnedAssemblyDirectory = 0 +TOKEN MissingOwnedPath( = 0 +TOKEN __940_missing_ = 0 +TOKEN Exists.Should().BeFalse() = 0 +TOKEN new DirectoryInfo( = 0 +TOKEN new DirectoryInfo(RootedFixturePath) = 0 +TOKEN new DirectoryInfo(MissingOwnedPath( = 0 +TOKEN new FileInfo( = 0 +TOKEN new FileInfo(MissingOwnedPath( = 0 +TOKEN new FileInfo(typeof( = 0 +TOKEN [TestMethod] = 0 +TOKEN using System.Linq; = 1 +TOKEN using Moq; = 0 +TOKEN Thread.Sleep = 0 +TOKEN Task.Delay = 0 +TOKEN [Timeout = 0 +TOKEN DoNotParallelize = 0 +TOKEN Retry = 0 +TOKEN Path.GetTemp = 0 +TOKEN File.Create = 0 +TOKEN File.WriteAll = 0 +TOKEN Directory.CreateDirectory = 0 +TOKEN FileShare.ReadWrite = 0 +TOKEN FileShare.None = 0 +TOKEN .Create() = 1 +TOKEN .Create( = 2 +TOKEN .CreateSubdirectory( = 2 +TOKEN .EnumerateDirectories( = 3 +TOKEN .EnumerateFiles( = 3 +TOKEN .EnumerateFileSystemInfos( = 3 +TOKEN .GetDirectories( = 3 +TOKEN .GetFiles( = 3 +TOKEN .GetFileSystemInfos( = 3 +TOKEN .GetAccessControl( = 2 +TOKEN .GetObjectData( = 1 +TOKEN .Refresh() = 1 +TOKEN .SetAccessControl( = 1 +TOKEN .ToString() = 1 +TOKEN .Delete() = 1 +TOKEN .Delete(recursive: true) = 0 +TOKEN .MoveTo( = 1 +TOKEN .CopyTo( = 0 +TOKEN .Replace( = 0 +TOKEN .Open( = 0 +TOKEN .OpenRead() = 0 +TOKEN .OpenText() = 0 +TOKEN .AppendText() = 0 +TOKEN .OpenWrite() = 0 +TOKEN .Parent = 1 +TOKEN .Root = 1 +TOKEN .Length = 0 +TOKEN .IsReadOnly = 0 +TOKEN .Attributes = 2 +TOKEN .CreationTime = 4 +TOKEN .CreationTimeUtc = 2 +TOKEN .LastAccessTime = 4 +TOKEN .LastAccessTimeUtc = 2 +TOKEN .LastWriteTime = 4 +TOKEN .LastWriteTimeUtc = 2 +TOKEN .MemberCount = 0 +TOKEN Throw() = 0 +TOKEN Throw() = 0 +TOKEN !_directoryInfo.Exists = 0 +TOKEN Array.Empty() = 0 +TOKEN Enumerable.Empty() = 0 +TOKEN set { } = 0 +TOKEN public void Delete() { } = 0 +TOKEN _directoryInfo.FullName = 1 +TOKEN public long Length => 0; = 0 +TOKEN public void MoveTo(string destFileName) { } = 0 +TOKEN return string.Empty; = 0 +LINES = 246 +SHA256 = F77616271ABB36C9133ADE496550703B391B077AB68C8F5DC652AB73FD177DC3 diff --git a/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/baseline/p0-t13-phase0-commit.2026-09-30T07-24.md b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/baseline/p0-t13-phase0-commit.2026-09-30T07-24.md new file mode 100644 index 000000000..a765f4c70 --- /dev/null +++ b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/baseline/p0-t13-phase0-commit.2026-09-30T07-24.md @@ -0,0 +1,30 @@ +# Phase 0 Commit (P0-T13) + +Timestamp: 2026-09-30T07-24 +Task: P0-T13 +Command: git diff --cached --name-only; git status --porcelain -- docs/features/potential; git add -- docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940; git commit -m "docs(940): phase 0 baseline evidence for the file-system wrapper test fix" -m "Co-Authored-By: Claude Opus 5.5 noreply@anthropic.com" -- docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940; git show --name-only --format= HEAD; git status --porcelain -- UtilitiesCS UtilitiesCS.Test; git rev-parse HEAD +EXIT_CODE: 0 +Output Summary: EXIT_CODE is scoped to the source porcelain span `git status --porcelain -- UtilitiesCS UtilitiesCS.Test`, which printed nothing. The commit ran in the exemption-eligible form with no PreToolUse refusal; all 13 committed paths lie under the feature folder. +- PRE-STAGED: NONE +- POTENTIAL-PORCELAIN: EMPTY +- ADD-EXIT: 0 (git printed only LF-to-CRLF working-copy warnings) +- COMMIT-EXIT: 0 (`[bug/filesystem-wrapper-tests-open-repository-solution-file-940 bd39309bc] docs(940): phase 0 baseline evidence for the file-system wrapper test fix`, `13 files changed, 784 insertions(+), 12 deletions(-)`) +- PHASE0-COMMIT-PATHS: + - docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/baseline/coverage-baseline.md + - docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/baseline/p0-t12-pre-edit-census.2026-09-30T07-22.md + - docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/baseline/p0-t2-mode-preconditions.2026-09-30T07-11.md + - docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/baseline/p0-t3-worktree-context.2026-09-30T07-13.md + - docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/baseline/p0-t4-channel-and-toolchain.2026-09-30T07-14.md + - docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/baseline/p0-t5-nuget-restore.2026-09-30T07-14.md + - docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/baseline/p0-t6-csharpier-check.2026-09-30T07-16.md + - docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/baseline/p0-t7-msbuild-analyzers.2026-09-30T07-16.md + - docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/baseline/p0-t8-msbuild-nullable.2026-09-30T07-17.md + - docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/baseline/p0-t9-stall-probe.2026-09-30T07-18.md + - docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/baseline/phase0-instructions-read.md + - docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/baseline/test-run-baseline.md + - docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/plan.2026-09-29T23-02.md +- SOURCE-PORCELAIN (git status --porcelain -- UtilitiesCS UtilitiesCS.Test): (empty) +- PHASE0-HEAD: bd39309bc03a4de4b43746acf0ecef2674c93561 +- BASE-SHA (P0-T3): c295d1d60bf86e1eef952fdfc0c7c4551f6416b6 (PHASE0-HEAD differs) + +This artifact and the P0-T13 check-off in the plan are written after the commit and are committed by P1-T8, per the task text. diff --git a/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/baseline/p0-t2-mode-preconditions.2026-09-30T07-11.md b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/baseline/p0-t2-mode-preconditions.2026-09-30T07-11.md new file mode 100644 index 000000000..b4de6a031 --- /dev/null +++ b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/baseline/p0-t2-mode-preconditions.2026-09-30T07-11.md @@ -0,0 +1,23 @@ +# Minor-Audit Mode Preconditions (P0-T2) + +Timestamp: 2026-09-30T07-11 +Task: P0-T2 +Command: read-only inspection with the Read, Grep and Glob tools (Read of FEATURE/issue.md; Grep count of `^- \[[ x]\] AC[1-8]: ` over FEATURE/issue.md; Glob `**/*` over FEATURE) +EXIT_CODE: 0 +Output Summary: all six minor-audit preconditions hold; no stop condition raised. + +## Observations + +- WORK-MODE-LINE: PRESENT (FEATURE/issue.md line 12 reads exactly `- Work Mode: minor-audit`) +- AC-HEADING: PRESENT (FEATURE/issue.md line 62 reads exactly `## Acceptance Criteria`) +- AC-INVENTORY-MATCHES: 8 (lines 64 to 71, AC1 through AC8) +- AC-INVENTORY-ALL-UNCHECKED: YES (every one of the 8 lines begins `- [ ] `) +- AC3-LITERAL `its parent directory`: PRESENT (line 66) +- AC4-LITERAL `no-op by construction`: PRESENT (line 67) +- SPEC-MD: ABSENT +- USER-STORY-MD: ABSENT +- NON-EVIDENCE CONTENT: issue.md, plan.2026-09-29T23-02.md, research/2026-09-29T21-10-filesystem-wrapper-tests-open-repository-solution-file-research.md (no other `.md` file directly under FEATURE/) +- PRE-EXISTING-EVIDENCE: docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/other/preflight-clearance.2026-09-30T01-26.md +- Note: evidence/baseline/phase0-instructions-read.md was written by P0-T1 of this run and is not pre-existing. + +MODE-PRECONDITIONS: MET diff --git a/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/baseline/p0-t3-worktree-context.2026-09-30T07-13.md b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/baseline/p0-t3-worktree-context.2026-09-30T07-13.md new file mode 100644 index 000000000..f1d3c6ae2 --- /dev/null +++ b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/baseline/p0-t3-worktree-context.2026-09-30T07-13.md @@ -0,0 +1,60 @@ +# Worktree Context, Diff Anchor and Gate Readiness (P0-T3) + +Timestamp: 2026-09-30T07-13 +Task: P0-T3 +Command: git rev-parse --abbrev-ref HEAD; git rev-parse HEAD; git fetch origin main; git rev-parse origin/main; git merge-base HEAD origin/main; git rev-list --count HEAD..origin/main; git rev-list --count origin/main..HEAD; git diff --name-only origin/main...HEAD; git status --porcelain --untracked-files=all; git status --porcelain -- UtilitiesCS UtilitiesCS.Test; git rev-parse --show-toplevel; Read of artifacts/orchestration/orchestrator-state.json +EXIT_CODE: 0 +Output Summary: branch matches; origin/main has not advanced past the merge base (r0, no reconciliation); ANCHOR-SHA recorded; no Write Set path inherited; source trees clean; pre-implementation checkpoint ready. + +## Refs + +- BRANCH: bug/filesystem-wrapper-tests-open-repository-solution-file-940 +- BASE-SHA: c295d1d60bf86e1eef952fdfc0c7c4551f6416b6 +- FETCH-EXIT: 0 +- ORIGIN-MAIN-SHA: 231e1c0b55105aeb626bf5a6e8d0266a567cacad +- MERGE-BASE: 231e1c0b55105aeb626bf5a6e8d0266a567cacad +- BEHIND-COUNT: 0 +- AHEAD-COUNT: 1 +- AHEAD-PATHS: + - docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/other/preflight-clearance.2026-09-30T01-26.md + - docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/issue.md + - docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/plan.2026-09-29T23-02.md + - docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/research/2026-09-29T21-10-filesystem-wrapper-tests-open-repository-solution-file-research.md + - docs/features/potential/promoted/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file.md + +## Reconciliation + +- RECONCILE: NOT NEEDED +- RECONCILE-EXIT: NOT APPLICABLE +- UPSTREAM-TOUCHED-CENSUS-FILES: NOT APPLICABLE +- ANCHOR-SHA: 231e1c0b55105aeb626bf5a6e8d0266a567cacad + +## Inherited paths + +- INHERITED-CLAUSE-A (union of `git diff --name-only origin/main...HEAD` and `git status --porcelain --untracked-files=all`): + - docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/other/preflight-clearance.2026-09-30T01-26.md + - docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/issue.md + - docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/plan.2026-09-29T23-02.md + - docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/research/2026-09-29T21-10-filesystem-wrapper-tests-open-repository-solution-file-research.md + - docs/features/potential/promoted/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file.md + - docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/baseline/p0-t2-mode-preconditions.2026-09-30T07-11.md (untracked; written by P0-T2 of this run; its file name was corrected after P0-T3 ran from a pre-write-time label `07-12` to the observed write minute `07-11`) + - docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/baseline/phase0-instructions-read.md (untracked; written by P0-T1 of this run) + - Porcelain status verbatim: ` M` plan.2026-09-29T23-02.md (P0-T1 and P0-T2 check-offs), `??` the two evidence files above. +- INHERITED-PROMOTION-RECORD: docs/features/potential/promoted/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file.md +- WRITE-SET-PATHS-IN-CLAUSE-A: NONE +- SOURCE-PORCELAIN: EMPTY + +## Toplevel + +- TOPLEVEL CONTAINS FEATURE: YES +- TOPLEVEL LEAF: agent-a237392a2250d275b + +## Pre-implementation checkpoint (read only) + +- CHECKPOINT-EXISTS: YES +- CHECKPOINT-ISSUE-NUM: 940 +- CHECKPOINT-FEATURE-FOLDER: docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940 +- CHECKPOINT-ROUTE: small +- CHECKPOINT-PREPARATION-MODE: ABSENT +- CHECKPOINT-LIFECYCLE-READY: true +- PRE-IMPLEMENTATION GATE READY: YES diff --git a/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/baseline/p0-t4-channel-and-toolchain.2026-09-30T07-14.md b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/baseline/p0-t4-channel-and-toolchain.2026-09-30T07-14.md new file mode 100644 index 000000000..e328a2b67 --- /dev/null +++ b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/baseline/p0-t4-channel-and-toolchain.2026-09-30T07-14.md @@ -0,0 +1,32 @@ +# Command Channel and C# Toolchain Bootstrap (P0-T4) + +Timestamp: 2026-09-30T07-14 +Task: P0-T4 +Command: pwsh -NoProfile -Command 'Set-Location -LiteralPath "WORKTREE"; Write-Output ("PROBE-OK " + (Test-Path -LiteralPath "TaskMaster.sln"))'; then, in payloads: Get-FileHash -Algorithm SHA256 -LiteralPath ; & pwsh -NoProfile -File scripts\vscode\Install-RepoDotNetSdk.ps1 (absolute script path resolved at run time); 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: channel available (PROBE-OK True); six hashes recorded; SDK 8.0.205 installed; csharpier 1.2.6 restored; MSBuild and vstest resolved; dotnet-coverage 18.10.0 present (no install needed). + +## Part 1: channel + +- PROBE-OUTPUT: PROBE-OK True +- CHANNEL: COMMAND + +## Part 2: hashes + +- RUNSETTINGS-HASH: 98EF03A8D3B0EBB2ED7A765E3B5E1B58E774D20202DF2F294C03A7260B9CEF57 +- PRE-EDIT-HASH-PFS: FD50F380509E730337CD342FC9C56989C41802378D2F773A221E59DBA7B90CF2 +- PRE-EDIT-HASH-DIW: DF1DFBBB4CD8806B029A99B41FDBCDEAFF7AD46D54177D4FAC081ACE728EB4B7 +- PRE-EDIT-HASH-PDA: FBAC7002BE4DF5979624F8AE649D52F1044BC40D54EBDA4618C6374EAB9CB242 +- PRE-EDIT-HASH-PFA: 0337E5C1FF7A2E3FF5E5D67D383E68583838AFB5E5BA999A30502CF24333D948 +- PRE-EDIT-HASH-DIWP: F77616271ABB36C9133ADE496550703B391B077AB68C8F5DC652AB73FD177DC3 + +## Part 2: toolchain + +- INSTALL-SDK-EXIT: 0 (output: `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-CSHARPIER-ROW: csharpier 1.2.6 csharpier \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 global install run) diff --git a/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/baseline/p0-t5-nuget-restore.2026-09-30T07-14.md b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/baseline/p0-t5-nuget-restore.2026-09-30T07-14.md new file mode 100644 index 000000000..137bbdbdb --- /dev/null +++ b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/baseline/p0-t5-nuget-restore.2026-09-30T07-14.md @@ -0,0 +1,9 @@ +# NuGet Restore (P0-T5) + +Timestamp: 2026-09-30T07-14 +Task: P0-T5 +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` and set MSBUILDDISABLENODEREUSE to 1; output teed to coverage\logs\p0-t5.restore.log, git-ignored) +EXIT_CODE: 0 +Output Summary: Restore target succeeded for TaskMaster.sln (`Build succeeded.`, `0 Warning(s)`, `0 Error(s)`; 172 packages restored to packages.config projects). +- RESTORE_EXIT_CODE: 0 +- PACKAGE-DIR-COUNT: 172 diff --git a/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/baseline/p0-t6-csharpier-check.2026-09-30T07-16.md b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/baseline/p0-t6-csharpier-check.2026-09-30T07-16.md new file mode 100644 index 000000000..aa7c3a767 --- /dev/null +++ b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/baseline/p0-t6-csharpier-check.2026-09-30T07-16.md @@ -0,0 +1,8 @@ +# CSharpier Baseline Check (P0-T6) + +Timestamp: 2026-09-30T07-16 +Task: P0-T6 +Command: dotnet tool run csharpier check . +EXIT_CODE: 0 +Output Summary: formatting baseline clean; the tool printed no file list and its final summary line verbatim was `Checked 1625 files in 7157ms.` +- CHECKED-FILES: 1625 diff --git a/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/baseline/p0-t7-msbuild-analyzers.2026-09-30T07-16.md b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/baseline/p0-t7-msbuild-analyzers.2026-09-30T07-16.md new file mode 100644 index 000000000..b849840e1 --- /dev/null +++ b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/baseline/p0-t7-msbuild-analyzers.2026-09-30T07-16.md @@ -0,0 +1,16 @@ +# Analyzer Baseline Rebuild (P0-T7) + +Timestamp: 2026-09-30T07-16 +Task: P0-T7 +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; file logger coverage\logs\p0-t7.msbuild.log at normal verbosity, git-ignored; the console stream was discarded with Out-Null and every recorded value is read from the file log, as CMD-REBUILD defines) +EXIT_CODE: 0 +Output Summary: analyzer rebuild clean; CoreCompile ran for both the test project and its production project; zero warnings, zero errors, no Write Set diagnostic. +- ANALYZE-BASELINE-EXIT: 0 +- SKIP_CORECOMPILE_LINES: 0 +- UCS_TEST_CSC_OUT_LINES: 2 +- UCS_CSC_OUT_LINES: 2 +- ZERO_ERRORS_LINES: 1 +- WARNINGS: 0 +- ANALYZE-BASELINE-WARNINGS: 0 +- ERRORS: 0 +- WRITESET_DIAGNOSTIC_LINES: 0 diff --git a/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/baseline/p0-t8-msbuild-nullable.2026-09-30T07-17.md b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/baseline/p0-t8-msbuild-nullable.2026-09-30T07-17.md new file mode 100644 index 000000000..5f6bf94b7 --- /dev/null +++ b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/baseline/p0-t8-msbuild-nullable.2026-09-30T07-17.md @@ -0,0 +1,17 @@ +# Nullable / Type-Check Baseline Rebuild (P0-T8) + +Timestamp: 2026-09-30T07-17 +Task: P0-T8 +Command: 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; file logger coverage\logs\p0-t8.msbuild.log at normal verbosity, git-ignored; console stream discarded with Out-Null, every value read from the file log) +EXIT_CODE: 0 +Output Summary: TreatWarningsAsErrors rebuild clean; CoreCompile ran for both projects; zero warnings and errors; the test assembly exists for P0-T9 and P0-T10. +- NULLABLE-BASELINE-EXIT: 0 +- SKIP_CORECOMPILE_LINES: 0 +- UCS_TEST_CSC_OUT_LINES: 2 +- UCS_CSC_OUT_LINES: 2 +- ZERO_ERRORS_LINES: 1 +- WARNINGS: 0 +- NULLABLE-BASELINE-WARNINGS: 0 +- ERRORS: 0 +- WRITESET_DIAGNOSTIC_LINES: 0 +- UCS-TEST-DLL-EXISTS: True diff --git a/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/baseline/p0-t9-stall-probe.2026-09-30T07-18.md b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/baseline/p0-t9-stall-probe.2026-09-30T07-18.md new file mode 100644 index 000000000..77acd4e51 --- /dev/null +++ b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/baseline/p0-t9-stall-probe.2026-09-30T07-18.md @@ -0,0 +1,24 @@ +# Stall Probe (P0-T9) + +Timestamp: 2026-09-30T07-18 +Task: P0-T9 +Command: vstest.console.exe UtilitiesCS.Test\bin\Debug\UtilitiesCS.Test.dll "/Settings:scripts\vscode\TaskMaster.cli.runsettings" /InIsolation "/TestCaseFilter:FullyQualifiedName~HelperClasses.ShellUtilities_Tests|FullyQualifiedName~HelperClasses.ShellUtilitiesStatic_Tests|FullyQualifiedName~HelperClasses.SysImageListHelperTests|FullyQualifiedName~EmailIntelligence.OSBrowser_Tests" "/ResultsDirectory:coverage\test-results\940\p0-t9" "/Logger:trx;LogFileName=p0-t9.trx" "/Blame:CollectHangDump;TestTimeout=4min;HangDumpType=None" (resolved through vswhere) +EXIT_CODE: 1 +ExpectedExitCode: 1 +Output Summary: single invocation; 23 tests ran in about 4.7 seconds, 22 passed and 1 failed on an invalid Win32 icon handle; no hang (no Sequence document). The failure reproduces the pre-existing local shell-icon defect recorded by #931. +- RUNSETTINGS-HASH-NOW: 98EF03A8D3B0EBB2ED7A765E3B5E1B58E774D20202DF2F294C03A7260B9CEF57 (equals RUNSETTINGS-HASH of P0-T4) +- TRX_PRESENT: True +- SEQUENCE_FILES: 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. + +## Derived selections + +STALL-PROBE: REPRODUCES +UCS-FILTERARG: "/TestCaseFilter:FullyQualifiedName!~HelperClasses.ShellUtilities_Tests&FullyQualifiedName!~HelperClasses.ShellUtilitiesStatic_Tests&FullyQualifiedName!~HelperClasses.SysImageListHelperTests&FullyQualifiedName!~EmailIntelligence.OSBrowser_Tests" +COVERAGE-ROUTE: DIRECT + +The four excluded classes (HelperClasses.ShellUtilities_Tests, HelperClasses.ShellUtilitiesStatic_Tests, HelperClasses.SysImageListHelperTests, EmailIntelligence.OSBrowser_Tests) are a pre-existing local stall and failure reproduced on main and are executed by CI (plan fact 18). + +Rule applied: `CLEAR` requires EXIT_CODE 0, failed 0 and SEQUENCE_FILES 0; the observed EXIT_CODE 1 with failed 1 yields `REPRODUCES`. The probe was invoked once and not re-run. diff --git a/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/baseline/phase0-instructions-read.md b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/baseline/phase0-instructions-read.md new file mode 100644 index 000000000..f845de2e4 --- /dev/null +++ b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/baseline/phase0-instructions-read.md @@ -0,0 +1,24 @@ +# Phase 0 Instructions Read (P0-T1) + +Timestamp: 2026-09-30T07-11 +Task: P0-T1 +Policy Order: 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/policy-compliance-order/SKILL.md, .claude/skills/atomic-plan-contract/SKILL.md, .claude/skills/acceptance-criteria-tracking/SKILL.md, .claude/skills/evidence-and-timestamp-conventions/SKILL.md, then the feature issue.md and the feature research record. +Command: pwsh -NoProfile -Command (per file: @(Get-Content -LiteralPath ).Count) +EXIT_CODE: 0 +Output Summary: thirteen files read in the order above from the item worktree; line counts below are @(Get-Content).Count values. + +## Files read (repository-relative path = line count) + +1. CLAUDE.md = 463 +2. .claude/rules/general-code-change.md = 80 +3. .claude/rules/general-unit-test.md = 105 +4. .claude/rules/quality-tiers.md = 51 +5. .claude/rules/csharp.md = 96 +6. .claude/rules/tonality.md = 80 +7. .claude/rules/plan-acceptance-gates.md = 257 +8. .claude/skills/policy-compliance-order/SKILL.md = 40 +9. .claude/skills/atomic-plan-contract/SKILL.md = 245 +10. .claude/skills/acceptance-criteria-tracking/SKILL.md = 104 +11. .claude/skills/evidence-and-timestamp-conventions/SKILL.md = 176 +12. docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/issue.md = 76 +13. docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/research/2026-09-29T21-10-filesystem-wrapper-tests-open-repository-solution-file-research.md = 217 diff --git a/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/baseline/test-run-baseline.md b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/baseline/test-run-baseline.md new file mode 100644 index 000000000..e64787559 --- /dev/null +++ b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/baseline/test-run-baseline.md @@ -0,0 +1,26 @@ +# Baseline Scoped Test Run of the Two Classes (P0-T10) + +Timestamp: 2026-09-30T07-18 +Task: P0-T10 +Command: vstest.console.exe UtilitiesCS.Test\bin\Debug\UtilitiesCS.Test.dll "/Settings:scripts\vscode\TaskMaster.cli.runsettings" /InIsolation "/TestCaseFilter:FullyQualifiedName~HelperClasses.PhysicalFileSystemAdapters_Tests|FullyQualifiedName~HelperClasses.DirectoryInfoWrapper_Tests" "/ResultsDirectory:coverage\test-results\940\p0-t10" "/Logger:trx;LogFileName=p0-t10.trx" "/Blame:CollectHangDump;TestTimeout=4min;HangDumpType=None" (resolved through vswhere; runsettings scripts\vscode\TaskMaster.cli.runsettings; isolation switch /InIsolation) +EXIT_CODE: 0 +Output Summary: pre-edit population of 12 tests ran and all passed; no hang; the TRX stays under the git-ignored coverage tree. +- RUNSETTINGS-HASH-NOW: 98EF03A8D3B0EBB2ED7A765E3B5E1B58E774D20202DF2F294C03A7260B9CEF57 (equals RUNSETTINGS-HASH) +- TRX_PRESENT: True +- COUNTERS total=12 executed=12 passed=12 failed=0 +- RESULT_COUNT: 12 +- SEQUENCE_FILES: 0 +- RESULT ToString_ShouldDelegateToWrappedDirectoryInfo = Passed +- RESULT PhysicalDirectoryInfoAdapter_PropertiesEnumerationAndAccessors_MirrorDirectoryInfo = Passed +- RESULT EnumerateFileSystemInfos_ShouldWrapDirectoriesAndFiles = Passed +- RESULT GetDirectoriesAndGetFiles_ShouldReturnWrappedEntries = Passed +- RESULT Properties_ShouldMirrorWrappedDirectoryInfo = Passed +- RESULT Constructor_WhenDirectoryInfoIsNull_ThrowsArgumentNullException = Passed +- RESULT EnumerationAndArrayMethods_ShouldDelegateToWrappedIDirectoryInfo = Passed +- RESULT PhysicalDirectoryInfoAdapter_MissingDirectoryAndUnsupportedInfo_BranchesBehaveAsExpected = Passed +- RESULT PropertyDelegates_ShouldMirrorMockedIDirectoryInfo = Passed +- RESULT PhysicalFileInfoAdapter_MissingFileBranches_ThrowOrNoOpWithoutCreatingFiles = Passed +- RESULT PhysicalFileInfoAdapter_PropertiesStreamsAndAccessors_MirrorFileInfo = Passed +- RESULT LifecycleAndAccessControlMethods_ShouldDelegateToWrappedIDirectoryInfo = Passed +- MESSAGE lines: none (no failed test) +- BASELINE-FAILED-TWO-CLASSES: NONE diff --git a/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/other/preflight-clearance.2026-09-30T01-26.md b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/other/preflight-clearance.2026-09-30T01-26.md new file mode 100644 index 000000000..abaacdae6 --- /dev/null +++ b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/other/preflight-clearance.2026-09-30T01-26.md @@ -0,0 +1,40 @@ +# Preflight Clearance Record (Issue #940) + +Timestamp: 2026-09-30T01-26 +Feature folder: docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940 +Plan path: docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/plan.2026-09-29T23-02.md +Plan blob SHA (git hash-object, computed before the preparation commit): 91105f7d94c13ee507a7820cc3fc20f271de91e3 +Work Mode: minor-audit +Base: origin/main at 231e1c0b5 (the item branch was fast-forwarded from ddbab26a0 during preparation, before any commit) + +PREFLIGHT: ALL CLEAR +CONVERGENCE: NO FURTHER ROUNDS EXPECTED +Preflight rounds: 3 + +## Round history + +| Round | Reviewer signal | Convergence | Defects reported | +|---|---|---|---| +| 1 | PREFLIGHT: REVISIONS REQUIRED | FURTHER ROUNDS LIKELY | 10 | +| 2 | PREFLIGHT: REVISIONS REQUIRED | FURTHER ROUNDS LIKELY | 8 | +| 3 | PREFLIGHT: ALL CLEAR | NO FURTHER ROUNDS EXPECTED | 0 | + +All preflight rounds were validation-only reviews by atomic-executor (no build, no test run, no edit). The plan was revised in place by atomic-planner between rounds; no sibling plan file exists. + +## Acceptance-criteria amendments made during preparation + +- AC3 closing clause extended to allow a no-op-by-construction call on the test assembly output directory itself or its parent directory (round 1, defect 7). +- AC4 extended with a clause for members whose effect on an existing test-owned entry is a no-op by construction (`Create`, `Refresh`, `SetAccessControl` with an unmodified security object), which must complete without an exception (round 1, defect 9). + +## Validator + +mcp validate_orchestration_artifacts, artifact_type plan: ok (no warnings), run after each revision and on the cleared text. + +## Round 3 non-blocking advisory (no delta applied) + +P2-T10 `CLAUDE-CHANGED:` is not filtered by `INHERITED-CLAUSE-A:`. The preparation commit touches only docs/features/, so the condition cannot arise; if it did, the outcome is a stop, not a false pass. + +## Execution notes for the executing orchestrator + +- Execution must run from a non-isolated executor: the plan's C# and coverage steps require pwsh, which the harness refuses inside isolated agent worktrees. Phase 0 includes a pwsh channel probe that stops with `CHANNEL UNAVAILABLE`. +- The preparation checkpoint (route_id preparation) is not an execution checkpoint; the plan does not depend on its route_id and the executing orchestrator re-seeds its own checkpoint. diff --git a/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/other/root-walk-site-classification.2026-09-30T07-27.md b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/other/root-walk-site-classification.2026-09-30T07-27.md new file mode 100644 index 000000000..69e53eb53 --- /dev/null +++ b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/other/root-walk-site-classification.2026-09-30T07-27.md @@ -0,0 +1,41 @@ +# Root-Walk Site Classification (P1-T1, AC7) + +Timestamp: 2026-09-30T07-27 +Task: P1-T1 +Command: Read tool over the four files; no command executed +EXIT_CODE: 0 (scoped to the read-only classification) +Output Summary: four sections over the three files AC7 names; RibbonControllerTests and FSharpCoreHintPathAlignmentTests classified `legitimate repository-file read`; the four string-only SortEmail_Tests consumers classified `layout dependence without file-system I/O`; the SortEmail_Tests `TrySaveAttachmentAsync` site classified `same defect class`. Every classification matches the plan's expected classification after the re-read; none was corrected. No file was modified; no potential entry and no issue was created by this task (D-11). + +Ranges re-read: TaskMaster.Test/Ribbon/RibbonControllerTests.cs 296 to 311 and 422 to 437; TaskMaster.Test/Bootstrap/FSharpCoreHintPathAlignmentTests.cs 11 to 107 with its consumers at 156 and 181; UtilitiesCS.Test/EmailIntelligence/SortEmail_Tests.cs 186 to 296 and 393 to 415; UtilitiesCS/EmailIntelligence/EmailParsingSorting/SortEmail.cs 888 to 897. Supporting read (to confirm the string-only claim of section 3): the production methods `GetAttachmentsInfo` (SortEmail.cs 637 to 659), `SaveMessageAsMsgAsync` (1081 to 1089), `SaveMessageAsMSG` (1092 to 1098) and `AttachmentHelper.AdjustForMaxPath` (UtilitiesCS/OutlookObjects/Attachment/AttachmentHelper.cs 216 to 234). + +## Section 1 + +- SITE: TaskMaster.Test/Ribbon/RibbonControllerTests.cs lines 296 to 311 (test `RibbonFolderOperations_DoNotConstructThrowawayFolderTrees`) and 422 to 437 (`FindRepositoryRoot()`) +- ROOT-USE: the located root is combined with TaskMaster/Ribbon/RibbonController.FolderTree.cs and read with `File.ReadAllText`, and the assertions check the checked-in source text for three forbidden substrings. +- CLASS: legitimate repository-file read +- RECOMMENDATION: out of scope for #940; no change +- MODIFIED: NONE + +## Section 2 + +- SITE: TaskMaster.Test/Bootstrap/FSharpCoreHintPathAlignmentTests.cs lines 11 to 107 (class remarks 15 to 21, `FindRepositoryRoot()` 47 to 67, `DiscoverFSharpCoreHintPaths()` 83 onward) with consumers at 156 and 181 +- ROOT-USE: the located root is the starting directory of a read-only enumeration of every project file (skipping dot-prefixed, packages, bin, obj and node_modules directories), whose FSharp.Core HintPath values are asserted, which the class remarks state is the design ("These assertions read the project files directly"). +- CLASS: legitimate repository-file read +- RECOMMENDATION: out of scope for #940; no change +- MODIFIED: NONE + +## Section 3 + +- SITE: UtilitiesCS.Test/EmailIntelligence/SortEmail_Tests.cs lines 186 to 296 (consumers at 196, 223, 255 and 280) and 393 to 415 (`GetRepositoryRoot()`) +- ROOT-USE: the located root's `FullName` is passed as a folder-path string to `GetAttachmentsInfo`, `GetAttachmentsInfoAsync`, `SaveMessageAsMsgAsync` and `SaveMessageAsMSG`, which combine strings on it; the only save calls reached are `SaveAs` on a strict Moq `MailItem` mock, so no file-system entry is read or written through the path. +- CLASS: layout dependence without file-system I/O +- RECOMMENDATION: out of scope for #940; no change +- MODIFIED: NONE + +## Section 4 + +- SITE: UtilitiesCS.Test/EmailIntelligence/SortEmail_Tests.cs lines 237 to 249 (`TrySaveAttachmentAsync_WhenSaveSucceeds_ReturnsTrueAndCallsSaveAsFile`, root use at 241) with UtilitiesCS/EmailIntelligence/EmailParsingSorting/SortEmail.cs lines 888 to 897 +- ROOT-USE: the located root is combined with `saved.txt` and passed to the production method `TrySaveAttachmentAsync`, which at SortEmail.cs line 896 calls `System.IO.Directory.CreateDirectory` on the directory part of that path, the repository root; the call is a real creation API that is a no-op only because the root exists (the method carries `ExcludeFromCodeCoverage` at line 888; `SaveAsFile` itself is a Moq mock). +- CLASS: same defect class +- RECOMMENDATION: out of scope for #940; follow-up handed to the orchestrator +- MODIFIED: NONE diff --git a/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/qa-gates/coverage-final.md b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/qa-gates/coverage-final.md new file mode 100644 index 000000000..899425029 --- /dev/null +++ b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/qa-gates/coverage-final.md @@ -0,0 +1,436 @@ +# Final Repository-Wide Test and Coverage Run (P2-T6) + +Timestamp: 2026-09-30T08-14 +Task: P2-T6 +ITERATION: 1 +MEASUREMENT: 1 +Command: CMD-COVERAGE-DIRECT with STAGE final: dotnet-coverage collect --output coverage\final-940.cobertura.xml --output-format cobertura --settings coverage\effective-coverage-940.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\940\final" "/Logger:trx;LogFileName=final-940.trx" "/Blame:CollectHangDump;TestTimeout=4min;HangDumpType=None" (vstest resolved through vswhere; the runner script dot-sourced by absolute path for ConvertTo-DerivedCoverageSettingsXml; started as a background invocation and polled; the collector console stream went to the git-ignored Tee-Object log coverage\logs\final-940.collect.log only); then CMD-COVERAGE-POST with STAGE final and RAW True (runner helpers ConvertTo-KoverageCoberturaXml, Assert-CoberturaLineCoverageThreshold, Assert-CoberturaBranchCoverageThreshold, Get-CoberturaFirstPartyCoverageReport, ConvertTo-JacocoPackageProjection, Assert-JacocoProjectionReconciliation, Get-TrxRunSummary, Format-TrxRunSummary). +EXIT_CODE: 0 +Output Summary: DIRECT route, 9 test assemblies, 7326 tests executed and passed (7323 at baseline plus the three test methods the PFS rewrite adds; collector started 08-11-25, ended 08-12-27, console `Total time: 57.5790 Seconds`); no hang document; both CLAUDE.md floors met; no failure, so NEWLY-FAILING is NONE; branch (a) applies. +- COVERAGE-ROUTE: DIRECT +- ROUTE-FILTER: TestCategory!=LiveOutlook plus the four-class exclusion (FILTER-UCS-EXCLUDE terms) +- 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 56092/65736 (85.33%), branches 13595/17054 (79.72%) +- FIRST-PARTY-LINE-PERCENT: 85.33 +- FIRST-PARTY-BRANCH-PERCENT: 79.72 +- FAILED-SET: (empty) +- NEWLY-FAILING: NONE +- BRANCH-TAKEN: (a) exit 0 with FAILED-SET empty and both floors met + +## TRX-derived summary + +SUMMARY-BEGIN +Test run outcome: Completed +Total 7326, executed 7326, passed 7326, 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 + +## JaCoCo package projection + +PROJECTION-BEGIN + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +PROJECTION-END + +UtilitiesCS package (derived): LINE 38823/43423 covered (rate 0.894066), BRANCH 9411/11271 covered (rate 0.834975). + +coverage\final-940.jacoco.xml and coverage\final-940.cobertura.xml remain on disk under the git-ignored coverage tree for P2-T7; neither the Cobertura document nor the TRX is copied into the feature folder. + +## Comparison against coverage-baseline.md (MEASUREMENT: 1, STAGE final) + +Timestamp: 2026-09-30T08-15 +Command: CMD-PACKAGE-COMPARE with STAGE final, then CMD-FILE-COMPARE with STAGE final (issued in one payload; the per-file block read the two Cobertura documents into its own variables because the package block had already bound the two JaCoCo projections; the computation is the Command Reference block unchanged) +EXIT_CODE: 0 +Output Summary: UtilitiesCS LINE rate, REPO-LINE and all three FILE lines are not lower than baseline; the UtilitiesCS BRANCH rate is lower by two covered branches (9411 against 9413 of 11271), so D-7 requires one identical second measurement (STAGE final2, MEASUREMENT: 2, below). + +- PACKAGE UtilitiesCS LINE baseline=38815/43423 rate=0.893881 final=38823/43423 rate=0.894065 NOT-LOWER=True +- PACKAGE UtilitiesCS BRANCH baseline=9413/11271 rate=0.835152 final=9411/11271 rate=0.834975 NOT-LOWER=False +- REPO-LINE baseline=56084/65736 rate=0.85317 final=56092/65736 rate=0.853292 NOT-LOWER=True +- FILE PhysicalDirectoryInfoAdapter.cs baseline=81/91 final=91/91 NOT-LOWER=True +- FILE PhysicalFileInfoAdapter.cs baseline=69/75 final=71/75 NOT-LOWER=True +- FILE DirectoryInfoWrapper.cs baseline=123/123 final=123/123 NOT-LOWER=True +- BASELINE-FIRST-PARTY: First-party coverage: lines 56084/65736 (85.32%), branches 13597/17054 (79.73%) (equal, figure for figure, to the line coverage-baseline.md transcribed) +- FINAL-FIRST-PARTY: First-party coverage: lines 56092/65736 (85.33%), branches 13595/17054 (79.72%) (equal, figure for figure, to the line transcribed above from CMD-COVERAGE-POST) +- COMPARABILITY-MEASUREMENT-1: A (both first-party `lines` denominators are 65736) +- MEASUREMENT-1-OUTCOME: SECOND MEASUREMENT REQUIRED (PACKAGE UtilitiesCS BRANCH NOT-LOWER=False) + +## Second measurement (MEASUREMENT: 2, STAGE final2; D-7) + +Timestamp: 2026-09-30T08-19 +MEASUREMENT: 2 +Command: CMD-COVERAGE-DIRECT with STAGE final2 (identical to the MEASUREMENT 1 command with every `final` stage token replaced by `final2`: output coverage\final2-940.cobertura.xml, results directory coverage\test-results\940\final2, trx log file name final2-940.trx; started as a background invocation and polled), then CMD-COVERAGE-POST with STAGE final2 and RAW True, then CMD-PACKAGE-COMPARE and CMD-FILE-COMPARE with STAGE final2. +EXIT_CODE: 0 (scoped to the collector invocation) +Output Summary: collector exit 0, 9 assemblies, 7326 of 7326 passed, no hang document, both floors met; the second measurement again reads lower than baseline, now on the UtilitiesCS LINE and BRANCH rates and on REPO-LINE. Under P2-T7 a second `False` on a `PACKAGE` or `REPO-LINE` line is `AC8: NOT MET`: the run stops here and reports. + +- COLLECT_EXIT_CODE: 0 +- ASSEMBLY_COUNT: 9 (the same nine `ASSEMBLY:` paths as MEASUREMENT 1) +- SEQUENCE_FILES: 0 +- TRX_PRESENT: True +- RAW: True +- LINE-FLOOR: MET +- BRANCH-FLOOR: MET +- First-party coverage: lines 56082/65736 (85.31%), branches 13594/17054 (79.71%) +- FIRST-PARTY-LINE-PERCENT: 85.31 +- FIRST-PARTY-BRANCH-PERCENT: 79.71 +- FAILED-SET: (empty) +- NEWLY-FAILING: NONE + +### TRX-derived summary (final2) + +SUMMARY-BEGIN +Test run outcome: Completed +Total 7326, executed 7326, passed 7326, 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 + +### JaCoCo package projection (final2) + +PROJECTION-BEGIN + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +PROJECTION-END + +### Comparison against coverage-baseline.md (final2) + +- PACKAGE UtilitiesCS LINE baseline=38815/43423 rate=0.893881 final=38813/43423 rate=0.893835 NOT-LOWER=False +- PACKAGE UtilitiesCS BRANCH baseline=9413/11271 rate=0.835152 final=9410/11271 rate=0.834886 NOT-LOWER=False +- REPO-LINE baseline=56084/65736 rate=0.85317 final=56082/65736 rate=0.85314 NOT-LOWER=False +- FILE PhysicalDirectoryInfoAdapter.cs baseline=81/91 final=91/91 NOT-LOWER=True +- FILE PhysicalFileInfoAdapter.cs baseline=69/75 final=71/75 NOT-LOWER=True +- FILE DirectoryInfoWrapper.cs baseline=123/123 final=123/123 NOT-LOWER=True +- BASELINE-FIRST-PARTY: First-party coverage: lines 56084/65736 (85.32%), branches 13597/17054 (79.73%) +- FINAL-FIRST-PARTY: First-party coverage: lines 56082/65736 (85.31%), branches 13594/17054 (79.71%) (equal, figure for figure, to the line transcribed above from CMD-COVERAGE-POST for final2) +- COMPARABILITY: A (both first-party `lines` denominators are 65736) +- CHANGED-PRODUCTION-LINES: 0 (the P1-T8 `git show` path list contains no path under UtilitiesCS/, and P1-T31 recorded `PRODUCTION-DIFF-EXIT: 0`, so every changed line of this item is test code outside the coverage denominator and the no-regression-on-changed-lines clause has an empty subject) + +### Both REPO-LINE lines, quoted + +- MEASUREMENT 1: REPO-LINE baseline=56084/65736 rate=0.85317 final=56092/65736 rate=0.853292 NOT-LOWER=True +- MEASUREMENT 2: REPO-LINE baseline=56084/65736 rate=0.85317 final=56082/65736 rate=0.85314 NOT-LOWER=False + +### Diagnostic observation (read-only line and branch difference of each final document against the baseline document; not a gate) + +Every covered-line and covered-branch shortfall against the baseline lies in UtilitiesCS files that this item does not change and that the two Write Set test classes do not exercise; every difference in the three adapter and wrapper files is a gain. + +- MEASUREMENT 1 losses: UtilitiesCS\Interfaces\IWinForm\PropertyStore.cs lines 687, 688, 695, 696 (covered at baseline, not covered) and branch line 684 (4 covered at baseline, 2 covered). +- MEASUREMENT 1 gains: UtilitiesCS\HelperClasses\FileSystem\PhysicalDirectoryInfoAdapter.cs lines 30, 31, 42, 43, 48, 49, 54, 55, 60, 61; UtilitiesCS\HelperClasses\FileSystem\PhysicalFileInfoAdapter.cs lines 52, 53. +- MEASUREMENT 2 losses: PropertyStore.cs lines 571, 573, 687, 688, 695, 696 and branch lines 570 (2 to 1) and 684 (4 to 2); UtilitiesCS\OutlookObjects\Table\OlTableExtensions.Etl.cs lines 291 to 294; UtilitiesCS\EmailIntelligence\SubjectMap\SubjectMapSco.Orchestration.cs lines 97 to 100. +- MEASUREMENT 2 gains: the same twelve adapter lines as MEASUREMENT 1. +- Reading: the loss set differs between two identical runs of the same committed tree, which is consistent with run-to-run variance in which other tests reach those files; that attribution is an inference from the two observations and was not verified further. + +### Outcome + +- AC8: NOT MET (PACKAGE UtilitiesCS LINE and BRANCH, and REPO-LINE, read NOT-LOWER=False on the second measurement) +- STOP: AC8: NOT MET (P2-T7). P2-T7 is left unchecked; P2-T8 onward is not executed. + +## Coordinator ruling (2026-09-30) + +Timestamp: 2026-09-30T11-25 + +The coordinator ruling of 2026-09-30 (option 1; plan D-7 and D-8, plan version 1.3) replaced the `PACKAGE` and `REPO-LINE` not-lower comparison and the second-measurement rule recorded above with a per-file rule: for each of the three changed-coverage files PhysicalDirectoryInfoAdapter.cs, PhysicalFileInfoAdapter.cs and DirectoryInfoWrapper.cs, the covered-line count and the covered-branch count of the gating measurement are not lower than this item's Phase 0 baseline, plus the CLAUDE.md first-party floors (lines at least 80 percent, branches at least 75 percent). MEASUREMENT 1 and MEASUREMENT 2 above, their Outcome section included, remain as the record of the superseded package-level comparison. MEASUREMENT 3 below is the gating measurement, taken on the merged head that contains the orchestrator's post-P2-T6 merge of origin/main; no re-measurement follows it. + +## Post-merge anchor + +Timestamp: 2026-09-30T11-19 +Command: git rev-parse HEAD; git merge-base --is-ancestor 40e587ce20bbd41cd6915707271faae937f1a8e2 HEAD; git rev-parse 40e587ce20bbd41cd6915707271faae937f1a8e2^2; git merge-base HEAD origin/main; git rev-parse origin/main; git diff --exit-code ANCHOR-SHA ANCHOR-SHA-2 -- UtilitiesCS UtilitiesCS.Test (ANCHOR-SHA 231e1c0b55105aeb626bf5a6e8d0266a567cacad, ANCHOR-SHA-2 66afa6372fd82fc1ffd7c81f85a1ad65eebc5817); git diff --name-only ANCHOR-SHA ANCHOR-SHA-2 -- UtilitiesCS TaskMaster.runsettings scripts/vscode/TaskMaster.cli.runsettings coverage.config UtilitiesCS.Test/UtilitiesCS.Test.csproj TaskMaster.Test/Ribbon/RibbonControllerTests.cs TaskMaster.Test/Bootstrap/FSharpCoreHintPathAlignmentTests.cs UtilitiesCS.Test/EmailIntelligence/SortEmail_Tests.cs; git status --porcelain -- UtilitiesCS UtilitiesCS.Test +EXIT_CODE: 0 (scoped to the `git diff --exit-code ANCHOR-SHA ANCHOR-SHA-2` span) +Output Summary: the merge commit is in HEAD's history, the merge base equals the merge commit's second parent, and the two anchors agree on both source trees, so every PRE-EDIT-HASH- and FIX-HASH- anchor, the Token Census Expectations and the Target Source C citations remain valid after the merge. +- P2-T7-HEAD: d3f01551991a93ce2038db79415540992dc8b5fe +- MERGE-COMMIT-IN-HEAD-EXIT: 0 +- ANCHOR-SHA-2: 66afa6372fd82fc1ffd7c81f85a1ad65eebc5817 +- MERGE-BASE-NOW: 66afa6372fd82fc1ffd7c81f85a1ad65eebc5817 (equals ANCHOR-SHA-2) +- ORIGIN-MAIN-NOW: 66afa6372fd82fc1ffd7c81f85a1ad65eebc5817 (observation) +- MERGE-UCS-DIFF-EXIT: 0 +- MERGE-OUT-OF-SET-DELTA: NONE +- SOURCE-PORCELAIN-P2-T7: EMPTY + +## Foreign test-process probe + +Timestamp: 2026-09-30T11-20 +Command: CMD-FOREIGN-PROCESS-PROBE (the Command Reference block, with one appended statement printing the probe time as `PROBE-TIME:`; the appended line prints no path) +EXIT_CODE: 0 +Output Summary: no vstest.console, testhost or dotnet-coverage process was running on the workstation at either invocation; the first invocation preceded step (d) and the second immediately preceded the step (e) coverage run, as the delegation's concurrency rule requires before every coverage run. +- FOREIGN-PROBE-INVOCATIONS: 2 +- First probe output (2026-09-30T11-19, before step d), verbatim: + - TEST-PROCESSES: 0 + - FOREIGN-TEST-PROCESSES: 0 +- Last probe output (2026-09-30T11-20, before step e), verbatim: + - TEST-PROCESSES: 0 + - FOREIGN-TEST-PROCESSES: 0 + +## Post-merge rebuilds + +Timestamp: 2026-09-30T11-20 +Command: CMD-REBUILD with the analyzer GATEARGS and TASKID p2-t7a; CMD-REBUILD with the nullable GATEARGS and TASKID p2-t7b (both msbuild TaskMaster.sln /t:Rebuild /m /p:Configuration=Debug "/p:Platform=Any CPU" plus the gate properties, resolved through vswhere, plus /nodeReuse:false) +EXIT_CODE: 0 (scoped to the nullable rebuild; the analyzer rebuild exit is recorded in its own sub-record) +Output Summary: both post-merge rebuilds are clean; sub-records FEATURE/evidence/qa-gates/p2-t7-msbuild-analyzers.2026-09-30T11-19.md (MSBUILD_EXIT_CODE 0, ERRORS 0, SKIP_CORECOMPILE_LINES 0, UCS_TEST_CSC_OUT_LINES 2, UCS_CSC_OUT_LINES 2, WRITESET_DIAGNOSTIC_LINES 0) and FEATURE/evidence/qa-gates/p2-t7-msbuild-nullable.2026-09-30T11-20.md (the same values). + +## Post-merge measurement (MEASUREMENT: 3, STAGE final3) + +Timestamp: 2026-09-30T11-22 +MEASUREMENT: 3 +Command: CMD-COVERAGE-DIRECT with STAGE final3: dotnet-coverage collect --output coverage\final3-940.cobertura.xml --output-format cobertura --settings coverage\effective-coverage-940.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\940\final3" "/Logger:trx;LogFileName=final3-940.trx" "/Blame:CollectHangDump;TestTimeout=4min;HangDumpType=None" (vstest resolved through vswhere; the runner script dot-sourced by absolute path for ConvertTo-DerivedCoverageSettingsXml; started as a background invocation and polled; the collector console stream went to the git-ignored Tee-Object log coverage\logs\final3-940.collect.log only, as in MEASUREMENT 1); then CMD-COVERAGE-POST with STAGE final3 and RAW True. +EXIT_CODE: 0 (scoped to the collector invocation) +Output Summary: DIRECT route on the merged head, 9 test assemblies, 7327 tests executed and passed (one more than MEASUREMENT 1 and 2; the additional test arrives with the merged origin/main tree, since this item's two Write Set files are unchanged since P1-T8); collector console `Total time: 54.7679 Seconds`; no hang document; both CLAUDE.md floors met; no failure; branch (a) applies. +- 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 56092/65736 (85.33%), branches 13597/17054 (79.73%) +- FIRST-PARTY-LINE-PERCENT: 85.33 +- FIRST-PARTY-BRANCH-PERCENT: 79.73 +- FAILED-SET: (empty) +- NEWLY-FAILING: NONE +- BRANCH-TAKEN: (a) exit 0 with FAILED-SET empty and both floors met + +### TRX-derived summary (final3) + +SUMMARY-BEGIN +Test run outcome: Completed +Total 7327, executed 7327, passed 7327, 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 + +### JaCoCo package projection (final3) + +PROJECTION-BEGIN + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +PROJECTION-END + +coverage\final3-940.jacoco.xml and coverage\final3-940.cobertura.xml remain on disk under the git-ignored coverage tree; neither the Cobertura document nor the TRX is copied into the feature folder. + +## Comparison against coverage-baseline.md (MEASUREMENT: 3, STAGE final3) + +Timestamp: 2026-09-30T11-23 +Command: CMD-PACKAGE-COMPARE with STAGE final3 (observational); CMD-FILE-COMPARE with STAGE final3 (the gate), then with STAGE final and STAGE final2 (observational re-reads, issued in one payload that ran the Command Reference block unchanged once per stage); CMD-UNTOUCHED-LINES once +EXIT_CODE: 0 (scoped to the CMD-FILE-COMPARE payload) +Output Summary: all three final3 FILE lines read NOT-LOWER=True on covered lines and covered branches with non-zero denominators on both sides, so the per-file rule of the coordinator ruling is met; the observational PACKAGE and REPO-LINE lines for final3 also read NOT-LOWER=True. + +Observations (no acceptance reads their NOT-LOWER= field): + +- PACKAGE UtilitiesCS LINE baseline=38815/43423 rate=0.893881 final=38823/43423 rate=0.894065 NOT-LOWER=True +- PACKAGE UtilitiesCS BRANCH baseline=9413/11271 rate=0.835152 final=9413/11271 rate=0.835152 NOT-LOWER=True +- REPO-LINE baseline=56084/65736 rate=0.85317 final=56092/65736 rate=0.853292 NOT-LOWER=True + +Gate lines (final3): + +- FILE PhysicalDirectoryInfoAdapter.cs LINES baseline=81/91 final=91/91 BRANCHES baseline=36/42 final=36/42 NOT-LOWER=True +- FILE PhysicalFileInfoAdapter.cs LINES baseline=69/75 final=71/75 BRANCHES baseline=6/12 final=6/12 NOT-LOWER=True +- FILE DirectoryInfoWrapper.cs LINES baseline=123/123 final=123/123 BRANCHES baseline=3/4 final=3/4 NOT-LOWER=True + +OBSERVATIONAL-FILE-LINES: + +- final: FILE PhysicalDirectoryInfoAdapter.cs LINES baseline=81/91 final=91/91 BRANCHES baseline=36/42 final=36/42 NOT-LOWER=True +- final: FILE PhysicalFileInfoAdapter.cs LINES baseline=69/75 final=71/75 BRANCHES baseline=6/12 final=6/12 NOT-LOWER=True +- final: FILE DirectoryInfoWrapper.cs LINES baseline=123/123 final=123/123 BRANCHES baseline=3/4 final=3/4 NOT-LOWER=True +- final2: FILE PhysicalDirectoryInfoAdapter.cs LINES baseline=81/91 final=91/91 BRANCHES baseline=36/42 final=36/42 NOT-LOWER=True +- final2: FILE PhysicalFileInfoAdapter.cs LINES baseline=69/75 final=71/75 BRANCHES baseline=6/12 final=6/12 NOT-LOWER=True +- final2: FILE DirectoryInfoWrapper.cs LINES baseline=123/123 final=123/123 BRANCHES baseline=3/4 final=3/4 NOT-LOWER=True + +UNTOUCHED lines: + +- UNTOUCHED baseline Interfaces\IWinForm\PropertyStore.cs covered=565/663 branches=168 +- UNTOUCHED baseline OutlookObjects\Table\OlTableExtensions.Etl.cs covered=268/273 branches=37 +- UNTOUCHED baseline EmailIntelligence\SubjectMap\SubjectMapSco.Orchestration.cs covered=127/131 branches=30 +- UNTOUCHED final Interfaces\IWinForm\PropertyStore.cs covered=561/663 branches=166 +- UNTOUCHED final OutlookObjects\Table\OlTableExtensions.Etl.cs covered=268/273 branches=37 +- UNTOUCHED final EmailIntelligence\SubjectMap\SubjectMapSco.Orchestration.cs covered=127/131 branches=30 +- UNTOUCHED final2 Interfaces\IWinForm\PropertyStore.cs covered=559/663 branches=165 +- UNTOUCHED final2 OutlookObjects\Table\OlTableExtensions.Etl.cs covered=264/273 branches=37 +- UNTOUCHED final2 EmailIntelligence\SubjectMap\SubjectMapSco.Orchestration.cs covered=123/131 branches=30 +- UNTOUCHED final3 Interfaces\IWinForm\PropertyStore.cs covered=565/663 branches=168 +- UNTOUCHED final3 OutlookObjects\Table\OlTableExtensions.Etl.cs covered=268/273 branches=37 +- UNTOUCHED final3 EmailIntelligence\SubjectMap\SubjectMapSco.Orchestration.cs covered=123/131 branches=30 + +First-party lines: + +- BASELINE-FIRST-PARTY: First-party coverage: lines 56084/65736 (85.32%), branches 13597/17054 (79.73%) (equal, figure for figure, to the line coverage-baseline.md transcribed) +- FINAL-FIRST-PARTY: First-party coverage: lines 56092/65736 (85.33%), branches 13597/17054 (79.73%) (equal, figure for figure, to the MEASUREMENT 3 line transcribed above from CMD-COVERAGE-POST) +- COMPARABILITY-MEASUREMENT-3: A (both first-party `lines` denominators are 65736) +- CHANGED-PRODUCTION-LINES: 0 (the P1-T8 `git show` path list contains no path under UtilitiesCS/, P1-T31 recorded `PRODUCTION-DIFF-EXIT: 0`, and this task recorded `MERGE-UCS-DIFF-EXIT: 0`, so every changed line of this item is test code outside the coverage denominator and the policy's no-regression-on-changed-lines clause is discharged by the per-file rule over the three files whose coverage the rewritten tests carry) + +## Per-file comparator negative control (in memory) + +Timestamp: 2026-09-30T11-24 +Command: CMD-FILE-COMPARE-CONTROL with STAGE final3 +EXIT_CODE: 0 +Output Summary: the comparator reads NOT-LOWER=True when a document is compared with itself and NOT-LOWER=False when one covered line or one covered branch is removed in memory, for each of the three files; the document on disk is unchanged (hashes equal), so the comparator is shown able to fail. + +- CONTROL-DOC-HASH-BEFORE: E806131BE302EFB40A7EFEF078DAC420DCBD8FE948077D1F90B8ED3A0D41D7F9 +- FILE PhysicalDirectoryInfoAdapter.cs CONTROL-SELF LINES baseline=91/91 final=91/91 BRANCHES baseline=36/42 final=36/42 NOT-LOWER=True +- FILE PhysicalDirectoryInfoAdapter.cs CONTROL-LINE LINES baseline=91/91 final=90/91 BRANCHES baseline=36/42 final=36/42 NOT-LOWER=False +- FILE PhysicalDirectoryInfoAdapter.cs CONTROL-BRANCH LINES baseline=91/91 final=91/91 BRANCHES baseline=36/42 final=35/42 NOT-LOWER=False +- FILE PhysicalFileInfoAdapter.cs CONTROL-SELF LINES baseline=71/75 final=71/75 BRANCHES baseline=6/12 final=6/12 NOT-LOWER=True +- FILE PhysicalFileInfoAdapter.cs CONTROL-LINE LINES baseline=71/75 final=70/75 BRANCHES baseline=6/12 final=6/12 NOT-LOWER=False +- FILE PhysicalFileInfoAdapter.cs CONTROL-BRANCH LINES baseline=71/75 final=71/75 BRANCHES baseline=6/12 final=5/12 NOT-LOWER=False +- FILE DirectoryInfoWrapper.cs CONTROL-SELF LINES baseline=123/123 final=123/123 BRANCHES baseline=3/4 final=3/4 NOT-LOWER=True +- FILE DirectoryInfoWrapper.cs CONTROL-LINE LINES baseline=123/123 final=122/123 BRANCHES baseline=3/4 final=3/4 NOT-LOWER=False +- FILE DirectoryInfoWrapper.cs CONTROL-BRANCH LINES baseline=123/123 final=123/123 BRANCHES baseline=3/4 final=2/4 NOT-LOWER=False +- CONTROL-DOC-HASH-AFTER: E806131BE302EFB40A7EFEF078DAC420DCBD8FE948077D1F90B8ED3A0D41D7F9 + +## Two-measurement variance table (observation, not a gate) + +Every cell is `/`. The baseline row is transcribed from coverage-baseline.md and from the `baseline=` halves of the FILE lines; the measurement 1 and measurement 2 rows from the PACKAGE and First-party lines of the MEASUREMENT 1 and MEASUREMENT 2 sections above and from the OBSERVATIONAL-FILE-LINES; the measurement 3 row from the MEASUREMENT 3 sections above. PDA is PhysicalDirectoryInfoAdapter.cs, PFA is PhysicalFileInfoAdapter.cs, DIW is DirectoryInfoWrapper.cs. + +| Row | UtilitiesCS lines | UtilitiesCS branches | first-party lines | first-party branches | PDA lines | PDA branches | PFA lines | PFA branches | DIW lines | DIW branches | +|---|---|---|---|---|---|---|---|---|---|---| +| baseline | 38815/43423 | 9413/11271 | 56084/65736 | 13597/17054 | 81/91 | 36/42 | 69/75 | 6/12 | 123/123 | 3/4 | +| measurement 1 (final) | 38823/43423 | 9411/11271 | 56092/65736 | 13595/17054 | 91/91 | 36/42 | 71/75 | 6/12 | 123/123 | 3/4 | +| measurement 2 (final2) | 38813/43423 | 9410/11271 | 56082/65736 | 13594/17054 | 91/91 | 36/42 | 71/75 | 6/12 | 123/123 | 3/4 | +| measurement 3 (final3, post-merge) | 38823/43423 | 9413/11271 | 56092/65736 | 13597/17054 | 91/91 | 36/42 | 71/75 | 6/12 | 123/123 | 3/4 | + +Untouched UtilitiesCS files (covered lines, transcribed from the UNTOUCHED lines): + +| File | baseline | final | final2 | final3 | +|---|---|---|---|---| +| Interfaces\IWinForm\PropertyStore.cs | covered=565/663 | covered=561/663 | covered=559/663 | covered=565/663 | +| OutlookObjects\Table\OlTableExtensions.Etl.cs | covered=268/273 | covered=268/273 | covered=264/273 | covered=268/273 | +| EmailIntelligence\SubjectMap\SubjectMapSco.Orchestration.cs | covered=127/131 | covered=127/131 | covered=123/131 | covered=123/131 | + +### MEASUREMENT 3 outcome + +- AC8 (amended, D-7): the three final3 FILE lines read NOT-LOWER=True on covered lines and covered branches; FIRST-PARTY-LINE-PERCENT 85.33 (at least 80) and FIRST-PARTY-BRANCH-PERCENT 79.73 (at least 75); NEWLY-FAILING: NONE. +- AC4 (D-8): the same three FILE lines read NOT-LOWER=True. diff --git a/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/qa-gates/p2-t1-csharpier-format.2026-09-30T08-08.md b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/qa-gates/p2-t1-csharpier-format.2026-09-30T08-08.md new file mode 100644 index 000000000..dec499bb3 --- /dev/null +++ b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/qa-gates/p2-t1-csharpier-format.2026-09-30T08-08.md @@ -0,0 +1,20 @@ +# Repository-Wide CSharpier Format (P2-T1) + +Timestamp: 2026-09-30T08-08 +Task: P2-T1 +ITERATION: 1 +Command: dotnet tool run csharpier format . (inside one payload that also took Get-FileHash -Algorithm SHA256 of the two Write Set files and the SHA-256 of the text printed by `git diff ANCHOR-SHA -- UtilitiesCS.Test` immediately before and after the command, ANCHOR-SHA substituted with 231e1c0b55105aeb626bf5a6e8d0266a567cacad, then ran `git status --porcelain -- UtilitiesCS UtilitiesCS.Test`) +EXIT_CODE: 0 +Output Summary: formatter exit 0; neither Write Set file was rewritten and the anchored patch is unchanged; scoped porcelain empty. Fact 22 prediction `REWRITTEN: 0`: met. + +- FORMATTER-LINE: `Formatted 1625 files in 8795ms.` (processed-file count, not a rewrite count) +- PFS-HASH-BEFORE (UtilitiesCS.Test/HelperClasses/PhysicalFileSystemAdapters_Tests.cs, before command): C88A785C23D8DB2960E9FAA53DF9EF91F6F00359683485BEC7EDFC44F9A2F998 +- PFS-HASH-AFTER (UtilitiesCS.Test/HelperClasses/PhysicalFileSystemAdapters_Tests.cs, after command): C88A785C23D8DB2960E9FAA53DF9EF91F6F00359683485BEC7EDFC44F9A2F998 +- DIW-HASH-BEFORE (UtilitiesCS.Test/HelperClasses/DirectoryInfoWrapper_Tests.cs, before command): 6650B33204BABCAAB7CD6E97C8B4BA7012ABB1F1320DB6AF7764ECD8714B7910 +- DIW-HASH-AFTER (UtilitiesCS.Test/HelperClasses/DirectoryInfoWrapper_Tests.cs, after command): 6650B33204BABCAAB7CD6E97C8B4BA7012ABB1F1320DB6AF7764ECD8714B7910 +- PATCH-HASH-BEFORE (SHA-256 of the UTF-8 text of `git diff ANCHOR-SHA -- UtilitiesCS.Test`, before command): 28A94D180B392D995EAFF412BBD3BAA9EF8F01B15003C4DF945698B3CF2963D9 +- PATCH-HASH-AFTER (same, after command): 28A94D180B392D995EAFF412BBD3BAA9EF8F01B15003C4DF945698B3CF2963D9 +- REWRITTEN: 0 +- FORMAT_CHANGED_TREE: False +- PORCELAIN (`git status --porcelain -- UtilitiesCS UtilitiesCS.Test`, verbatim): EMPTY (no output) +- REWRITTEN-PREDICTION (fact 22): MET diff --git a/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/qa-gates/p2-t10-scope-boundary.2026-09-30T11-27.md b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/qa-gates/p2-t10-scope-boundary.2026-09-30T11-27.md new file mode 100644 index 000000000..33eb2b375 --- /dev/null +++ b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/qa-gates/p2-t10-scope-boundary.2026-09-30T11-27.md @@ -0,0 +1,136 @@ +# Footprint and Scope Boundary (P2-T10) + +Timestamp: 2026-09-30T11-27 +Task: P2-T10 +Command: git diff --name-only origin/main...HEAD; git status --porcelain --untracked-files=all; git diff --name-only origin/main...HEAD -- UtilitiesCS.Test; git diff --name-only origin/main...HEAD -- .claude; git diff --name-only origin/main...HEAD -- .claude config; git ls-files --error-unmatch -- UtilitiesCS TaskMaster.runsettings scripts/vscode/TaskMaster.cli.runsettings coverage.config UtilitiesCS.Test/UtilitiesCS.Test.csproj TaskMaster.Test/Ribbon/RibbonControllerTests.cs TaskMaster.Test/Bootstrap/FSharpCoreHintPathAlignmentTests.cs UtilitiesCS.Test/EmailIntelligence/SortEmail_Tests.cs UtilitiesCS/HelperClasses/FileSystem/PhysicalDirectoryInfoAdapter.cs UtilitiesCS/HelperClasses/FileSystem/PhysicalFileInfoAdapter.cs UtilitiesCS/HelperClasses/FileSystem/DirectoryInfoWrapper.cs (run inside a pwsh payload that printed only its exit code and the listed-path count, because the tracked list under UtilitiesCS is 603 lines); git diff --exit-code ANCHOR-SHA-2 -- UtilitiesCS TaskMaster.runsettings scripts/vscode/TaskMaster.cli.runsettings coverage.config UtilitiesCS.Test/UtilitiesCS.Test.csproj TaskMaster.Test/Ribbon/RibbonControllerTests.cs TaskMaster.Test/Bootstrap/FSharpCoreHintPathAlignmentTests.cs UtilitiesCS.Test/EmailIntelligence/SortEmail_Tests.cs; git diff --exit-code ANCHOR-SHA-2 -- UtilitiesCS/HelperClasses/FileSystem/PhysicalDirectoryInfoAdapter.cs; git diff --exit-code ANCHOR-SHA-2 -- UtilitiesCS/HelperClasses/FileSystem/PhysicalFileInfoAdapter.cs; git diff --exit-code ANCHOR-SHA-2 -- UtilitiesCS/HelperClasses/FileSystem/DirectoryInfoWrapper.cs; git rev-parse origin/main; git merge-base HEAD origin/main; CMD-ADDED-LINES; Get-FileHash -Algorithm SHA256 of the five files; RUNSETTINGS-HASH-NOW (ANCHOR-SHA-2 substituted with 66afa6372fd82fc1ffd7c81f85a1ad65eebc5817, the P2-T7 `ANCHOR-SHA-2:` value) +EXIT_CODE: 0 (scoped to the `OUT-OF-SET-DIFF-EXIT` span) +Output Summary: the footprint is exactly the two Write Set code paths plus paths under the feature folder; no production file, runsettings file, coverage.config, project file or other root-walk site differs from the merged origin/main tip; no .claude or config path is committed; every added-line prohibition count is 0 over 276 added lines; the five hashes equal their anchors; all twelve acceptance items hold. + +## Committed list (`git diff --name-only origin/main...HEAD`, verbatim) + +UtilitiesCS.Test/HelperClasses/DirectoryInfoWrapper_Tests.cs +UtilitiesCS.Test/HelperClasses/PhysicalFileSystemAdapters_Tests.cs +docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/baseline/coverage-baseline.md +docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/baseline/p0-t12-pre-edit-census.2026-09-30T07-22.md +docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/baseline/p0-t13-phase0-commit.2026-09-30T07-24.md +docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/baseline/p0-t2-mode-preconditions.2026-09-30T07-11.md +docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/baseline/p0-t3-worktree-context.2026-09-30T07-13.md +docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/baseline/p0-t4-channel-and-toolchain.2026-09-30T07-14.md +docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/baseline/p0-t5-nuget-restore.2026-09-30T07-14.md +docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/baseline/p0-t6-csharpier-check.2026-09-30T07-16.md +docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/baseline/p0-t7-msbuild-analyzers.2026-09-30T07-16.md +docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/baseline/p0-t8-msbuild-nullable.2026-09-30T07-17.md +docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/baseline/p0-t9-stall-probe.2026-09-30T07-18.md +docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/baseline/phase0-instructions-read.md +docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/baseline/test-run-baseline.md +docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/other/preflight-clearance.2026-09-30T01-26.md +docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/other/root-walk-site-classification.2026-09-30T07-27.md +docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/qa-gates/coverage-final.md +docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/qa-gates/p2-t1-csharpier-format.2026-09-30T08-08.md +docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/qa-gates/p2-t2-csharpier-check.2026-09-30T08-08.md +docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/qa-gates/p2-t3-msbuild-analyzers.2026-09-30T08-09.md +docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/qa-gates/p2-t4-msbuild-nullable.2026-09-30T08-10.md +docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/regression-testing/fail-before-exception.2026-09-30T07-28.md +docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/regression-testing/mutation-directory-creationtime-setter-noop.md +docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/regression-testing/mutation-directory-delete-noop.md +docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/regression-testing/mutation-directory-exists-inverted.md +docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/regression-testing/mutation-directory-getfiles-empty.md +docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/regression-testing/mutation-directory-name-fullname.md +docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/regression-testing/mutation-file-isreadonly-setter-noop.md +docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/regression-testing/mutation-file-length-zero.md +docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/regression-testing/mutation-file-moveto-noop.md +docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/regression-testing/mutation-wrapper-enumeratefilesysteminfos-empty.md +docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/regression-testing/mutation-wrapper-getfiles-empty.md +docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/regression-testing/mutation-wrapper-tostring-empty.md +docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/regression-testing/p1-t3-pfs-census.2026-09-30T07-30.md +docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/regression-testing/p1-t31-post-control-clean-tree.2026-09-30T08-04.md +docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/regression-testing/p1-t4-diw-census.2026-09-30T07-31.md +docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/regression-testing/p1-t5-csharpier-scoped.2026-09-30T07-32.md +docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/regression-testing/p1-t6-build-after-fix.2026-09-30T07-33.md +docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/regression-testing/p1-t7-exception-type-observation.2026-09-30T07-34.md +docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/regression-testing/p1-t8-fix-commit.2026-09-30T07-35.md +docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/regression-testing/test-run-final.md +docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/issue.md +docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/plan.2026-09-29T23-02.md +docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/research/2026-09-29T21-10-filesystem-wrapper-tests-open-repository-solution-file-research.md +docs/features/potential/promoted/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file.md + +## Porcelain companion (`git status --porcelain --untracked-files=all`, verbatim) + + M docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/qa-gates/coverage-final.md + M docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/plan.2026-09-29T23-02.md +?? docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/qa-gates/p2-t7-msbuild-analyzers.2026-09-30T11-19.md +?? docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/qa-gates/p2-t7-msbuild-nullable.2026-09-30T11-20.md +?? docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/qa-gates/p2-t8-post-format-census.2026-09-30T11-25.md +?? docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/qa-gates/toolchain-pass.md + +## Derived sets + +- INHERITED-AND-EXCLUDED: + - Clause A: docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/other/preflight-clearance.2026-09-30T01-26.md + - Clause A: docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/issue.md + - Clause A: docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/plan.2026-09-29T23-02.md + - Clause A: docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/research/2026-09-29T21-10-filesystem-wrapper-tests-open-repository-solution-file-research.md + - Clause A: docs/features/potential/promoted/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file.md + - Clause A: docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/baseline/p0-t2-mode-preconditions.2026-09-30T07-11.md + - Clause A: docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/baseline/phase0-instructions-read.md + - Clause B: no listed path begins .claude/agent-memory/ (none removed) +- THIS-ITEM-FOOTPRINT: + - UtilitiesCS.Test/HelperClasses/DirectoryInfoWrapper_Tests.cs + - UtilitiesCS.Test/HelperClasses/PhysicalFileSystemAdapters_Tests.cs + - every other path of the two lists above, each under docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/ (37 committed feature-folder paths not in Clause A, plus the two modified and four untracked evidence paths of the porcelain companion) +- UCS-TEST-CHANGED: + - UtilitiesCS.Test/HelperClasses/DirectoryInfoWrapper_Tests.cs + - UtilitiesCS.Test/HelperClasses/PhysicalFileSystemAdapters_Tests.cs +- CLAUDE-CHANGED: NONE (empty list) +- COMMITTED-CLAUDE-OR-CONFIG-RAW: NONE (empty list) +- COMMITTED-CLAUDE-OR-CONFIG: NONE +- INHERITED-PROMOTION-RECORD: docs/features/potential/promoted/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file.md +- ORIGIN-MAIN-NOW: 66afa6372fd82fc1ffd7c81f85a1ad65eebc5817 +- MERGE-BASE-NOW: 66afa6372fd82fc1ffd7c81f85a1ad65eebc5817 (equals the P2-T7 `ANCHOR-SHA-2:` value) +- PATHSPEC-MATCH-EXIT: 0 (603 tracked paths listed) +- OUT-OF-SET-DIFF-EXIT: 0 +- PDA-DIFF-EXIT: 0 +- PFA-DIFF-EXIT: 0 +- DIWP-DIFF-EXIT: 0 + +## CMD-ADDED-LINES (anchored at ANCHOR-SHA-2) + +- ADDED-LINE-COUNT: 276 +- ADDED-DONOTPARALLELIZE: 0 +- ADDED-THREAD-SLEEP: 0 +- ADDED-TASK-DELAY: 0 +- ADDED-TIMEOUT: 0 +- ADDED-RETRY: 0 +- ADDED-WORKERS: 0 +- ADDED-SCOPE: 0 +- ADDED-TEMP: 0 +- ADDED-CATCH: 0 +- ADDED-ROOTWALK: 0 + +## Hashes + +| File | SHA-256 | Anchor | Equal | +| --- | --- | --- | --- | +| UtilitiesCS.Test/HelperClasses/PhysicalFileSystemAdapters_Tests.cs | C88A785C23D8DB2960E9FAA53DF9EF91F6F00359683485BEC7EDFC44F9A2F998 | P2-T1 PFS-HASH-AFTER C88A785C23D8DB2960E9FAA53DF9EF91F6F00359683485BEC7EDFC44F9A2F998 | yes | +| UtilitiesCS.Test/HelperClasses/DirectoryInfoWrapper_Tests.cs | 6650B33204BABCAAB7CD6E97C8B4BA7012ABB1F1320DB6AF7764ECD8714B7910 | P2-T1 DIW-HASH-AFTER 6650B33204BABCAAB7CD6E97C8B4BA7012ABB1F1320DB6AF7764ECD8714B7910 | yes | +| UtilitiesCS/HelperClasses/FileSystem/PhysicalDirectoryInfoAdapter.cs | FBAC7002BE4DF5979624F8AE649D52F1044BC40D54EBDA4618C6374EAB9CB242 | PRE-EDIT-HASH-PDA: FBAC7002BE4DF5979624F8AE649D52F1044BC40D54EBDA4618C6374EAB9CB242 | yes | +| UtilitiesCS/HelperClasses/FileSystem/PhysicalFileInfoAdapter.cs | 0337E5C1FF7A2E3FF5E5D67D383E68583838AFB5E5BA999A30502CF24333D948 | PRE-EDIT-HASH-PFA: 0337E5C1FF7A2E3FF5E5D67D383E68583838AFB5E5BA999A30502CF24333D948 | yes | +| UtilitiesCS/HelperClasses/FileSystem/DirectoryInfoWrapper.cs | F77616271ABB36C9133ADE496550703B391B077AB68C8F5DC652AB73FD177DC3 | PRE-EDIT-HASH-DIWP: F77616271ABB36C9133ADE496550703B391B077AB68C8F5DC652AB73FD177DC3 | yes | + +- RUNSETTINGS-HASH-NOW: 98EF03A8D3B0EBB2ED7A765E3B5E1B58E774D20202DF2F294C03A7260B9CEF57 (equals P0-T4 `RUNSETTINGS-HASH:`) + +## Acceptance + +1. PATHSPEC-MATCH-EXIT: 0 - met. +2. MERGE-BASE-NOW equals ANCHOR-SHA-2 - met. +3. THIS-ITEM-FOOTPRINT is the two Write Set code paths plus feature-folder paths only - met. +4. UCS-TEST-CHANGED is exactly the two Write Set paths - met. +5. every CLAUDE-CHANGED path begins .claude/agent-memory/ - met (list empty). +6. COMMITTED-CLAUDE-OR-CONFIG: NONE - met. +7. OUT-OF-SET-DIFF-EXIT: 0 - met. +8. PDA-DIFF-EXIT, PFA-DIFF-EXIT and DIWP-DIFF-EXIT all 0 - met. +9. the ten ADDED- counts other than ADDED-LINE-COUNT are 0 over ADDED-LINE-COUNT 276 - met. +10. production hashes equal PRE-EDIT-HASH- anchors and test hashes equal P2-T1 after-format hashes - met. +11. RUNSETTINGS-HASH-NOW equals RUNSETTINGS-HASH - met. +12. the porcelain span is present as the name-listing diff's companion - met. diff --git a/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/qa-gates/p2-t11-hygiene-sweep.2026-09-30T11-29.md b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/qa-gates/p2-t11-hygiene-sweep.2026-09-30T11-29.md new file mode 100644 index 000000000..50e6b14db --- /dev/null +++ b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/qa-gates/p2-t11-hygiene-sweep.2026-09-30T11-29.md @@ -0,0 +1,18 @@ +# Host-Identifier and Raw-Document Sweep (P2-T11) + +Timestamp: 2026-09-30T11-29 +Task: P2-T11 +Command: CMD-SWEEP over docs\features\active\2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940 (this plan included); then git diff --name-only origin/main...HEAD with git status --porcelain --untracked-files=all (issued inside one pwsh payload that filtered the listed paths for the extensions .trx, .xml and .coverage) +EXIT_CODE: 0 (scoped to the CMD-SWEEP payload) +Output Summary: no account token, profile leaf, machine token, worktree root or user-profile path appears in any of the 48 files of the feature folder; no raw document is present; no committed or pending path of this branch ends in a raw-document extension. The tokens themselves are derived at run time and are not written here. +- FILES: 48 +- ACCOUNT-TOKEN-MATCHES: 0 +- PROFILE-LEAF-MATCHES: 0 +- MACHINE-TOKEN-MATCHES: 0 +- WORKTREE-ROOT-MATCHES: 0 +- USERS-PATH-MATCHES: 0 +- HYGIENE-PROFILE-PATTERN-MATCHES: 0 +- RAW-DOCUMENT-FILES: 0 +- DIFF-EXIT: 0 (46 paths listed; the three-dot range is anchored at the merged origin/main tip 66afa6372fd82fc1ffd7c81f85a1ad65eebc5817, the P2-T7 `ANCHOR-SHA-2:` value) +- STATUS-EXIT: 0 (7 paths listed: two modified and five untracked feature-folder evidence or plan paths) +- RAW-EXTENSION-PATHS: NONE diff --git a/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/qa-gates/p2-t14-ac3-mutating-call-inventory.2026-09-30T11-31.md b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/qa-gates/p2-t14-ac3-mutating-call-inventory.2026-09-30T11-31.md new file mode 100644 index 000000000..0b57b8928 --- /dev/null +++ b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/qa-gates/p2-t14-ac3-mutating-call-inventory.2026-09-30T11-31.md @@ -0,0 +1,83 @@ +# AC3 Mutating-Call Inventory (P2-T14) + +Timestamp: 2026-09-30T11-31 +Task: P2-T14 +Command: Read tool over the two formatted Write Set files; no command executed +EXIT_CODE: 0 (scoped to the read-only derivation) +Output Summary: PFS carries 34 inventoried mutating call sites (owned output directory 4, its parent 2, existing owned entry 2, missing owned path 26, mock 0), equal to the plan's expected inventory; every DIW call site (16) lies in one of the three Moq-based tests and targets a wrapper built over `Mock.Object`; no call names the repository root, a tracked repository path or the solution file. Mock `Setup` and `SetupSet` expressions configure the mock and are not calls on the object under test, so they are not inventoried. Every missing-owned-path site is preceded in its method by an `adapter.Exists.Should().BeFalse()` precondition; the file copy, move, replace and backup destinations by `File.Exists(...).Should().BeFalse()`; the directory move destination by `Directory.Exists(directoryMoveTarget).Should().BeFalse()`. + +MUTATING-CALL-INVENTORY: + +PFS (UtilitiesCS.Test/HelperClasses/PhysicalFileSystemAdapters_Tests.cs): + +- CALL: PhysicalDirectoryInfoAdapter_AccessorsAndNoOpCreation_MirrorOwnedDirectory | adapter.Create() | owned output directory +- CALL: PhysicalDirectoryInfoAdapter_AccessorsAndNoOpCreation_MirrorOwnedDirectory | adapter.Create(directory.GetAccessControl()) | owned output directory +- CALL: PhysicalDirectoryInfoAdapter_AccessorsAndNoOpCreation_MirrorOwnedDirectory | parentAdapter.CreateSubdirectory(directory.Name) | its parent +- CALL: PhysicalDirectoryInfoAdapter_AccessorsAndNoOpCreation_MirrorOwnedDirectory | parentAdapter.CreateSubdirectory(directory.Name, parent.GetAccessControl()) | its parent +- CALL: PhysicalDirectoryInfoAdapter_AccessorsAndNoOpCreation_MirrorOwnedDirectory | adapter.Refresh() | owned output directory +- CALL: PhysicalDirectoryInfoAdapter_AccessorsAndNoOpCreation_MirrorOwnedDirectory | adapter.SetAccessControl(security) | owned output directory +- CALL: PhysicalDirectoryInfoAdapter_SettersOnMissingDirectory_ThrowWithoutCreatingEntries | adapter.CreationTime = stamp | missing owned path +- CALL: PhysicalDirectoryInfoAdapter_SettersOnMissingDirectory_ThrowWithoutCreatingEntries | adapter.CreationTimeUtc = stamp | missing owned path +- CALL: PhysicalDirectoryInfoAdapter_SettersOnMissingDirectory_ThrowWithoutCreatingEntries | adapter.LastAccessTime = stamp | missing owned path +- CALL: PhysicalDirectoryInfoAdapter_SettersOnMissingDirectory_ThrowWithoutCreatingEntries | adapter.LastAccessTimeUtc = stamp | missing owned path +- CALL: PhysicalDirectoryInfoAdapter_SettersOnMissingDirectory_ThrowWithoutCreatingEntries | adapter.LastWriteTime = stamp | missing owned path +- CALL: PhysicalDirectoryInfoAdapter_SettersOnMissingDirectory_ThrowWithoutCreatingEntries | adapter.LastWriteTimeUtc = stamp | missing owned path +- CALL: PhysicalDirectoryInfoAdapter_SettersOnMissingDirectory_ThrowWithoutCreatingEntries | adapter.Attributes = FileAttributes.Directory | missing owned path +- CALL: PhysicalDirectoryInfoAdapter_SettersOnMissingDirectory_ThrowWithoutCreatingEntries | adapter.Refresh() | missing owned path +- CALL: PhysicalDirectoryInfoAdapter_MissingDirectoryAndUnsupportedInfo_BranchesBehaveAsExpected | adapter.Delete() | missing owned path +- CALL: PhysicalDirectoryInfoAdapter_MissingDirectoryAndUnsupportedInfo_BranchesBehaveAsExpected | adapter.Delete(recursive: true) | missing owned path +- CALL: PhysicalDirectoryInfoAdapter_MissingDirectoryAndUnsupportedInfo_BranchesBehaveAsExpected | adapter.MoveTo(directoryMoveTarget) | missing owned path +- CALL: PhysicalFileInfoAdapter_PropertiesStreamsAndAccessors_MirrorFileInfo | adapter.Refresh() | existing owned entry +- CALL: PhysicalFileInfoAdapter_PropertiesStreamsAndAccessors_MirrorFileInfo | adapter.SetAccessControl(security) | existing owned entry +- CALL: PhysicalFileInfoAdapter_SettersOnMissingFile_ThrowWithoutCreatingFiles | adapter.IsReadOnly = true | missing owned path +- CALL: PhysicalFileInfoAdapter_SettersOnMissingFile_ThrowWithoutCreatingFiles | adapter.Attributes = FileAttributes.Normal | missing owned path +- CALL: PhysicalFileInfoAdapter_SettersOnMissingFile_ThrowWithoutCreatingFiles | adapter.CreationTime = stamp | missing owned path +- CALL: PhysicalFileInfoAdapter_SettersOnMissingFile_ThrowWithoutCreatingFiles | adapter.CreationTimeUtc = stamp | missing owned path +- CALL: PhysicalFileInfoAdapter_SettersOnMissingFile_ThrowWithoutCreatingFiles | adapter.LastAccessTime = stamp | missing owned path +- CALL: PhysicalFileInfoAdapter_SettersOnMissingFile_ThrowWithoutCreatingFiles | adapter.LastAccessTimeUtc = stamp | missing owned path +- CALL: PhysicalFileInfoAdapter_SettersOnMissingFile_ThrowWithoutCreatingFiles | adapter.LastWriteTime = stamp | missing owned path +- CALL: PhysicalFileInfoAdapter_SettersOnMissingFile_ThrowWithoutCreatingFiles | adapter.LastWriteTimeUtc = stamp | missing owned path +- CALL: PhysicalFileInfoAdapter_SettersOnMissingFile_ThrowWithoutCreatingFiles | adapter.Refresh() | missing owned path +- CALL: PhysicalFileInfoAdapter_MissingFileBranches_ThrowOrNoOpWithoutCreatingFiles | adapter.Delete() | missing owned path +- CALL: PhysicalFileInfoAdapter_MissingFileBranches_ThrowOrNoOpWithoutCreatingFiles | adapter.CopyTo(copyTarget) | missing owned path +- CALL: PhysicalFileInfoAdapter_MissingFileBranches_ThrowOrNoOpWithoutCreatingFiles | adapter.CopyTo(copyTarget, overwrite: true) | missing owned path +- CALL: PhysicalFileInfoAdapter_MissingFileBranches_ThrowOrNoOpWithoutCreatingFiles | adapter.MoveTo(moveTarget) | missing owned path +- CALL: PhysicalFileInfoAdapter_MissingFileBranches_ThrowOrNoOpWithoutCreatingFiles | adapter.Replace(replaceTarget, backupTarget) | missing owned path +- CALL: PhysicalFileInfoAdapter_MissingFileBranches_ThrowOrNoOpWithoutCreatingFiles | adapter.Replace(replaceTarget, backupTarget, ignoreMetadataErrors: true) | missing owned path + +DIW (UtilitiesCS.Test/HelperClasses/DirectoryInfoWrapper_Tests.cs): + +- CALL: PropertyDelegates_ShouldMirrorMockedIDirectoryInfo | wrapper.Attributes = FileAttributes.ReadOnly | mock +- CALL: PropertyDelegates_ShouldMirrorMockedIDirectoryInfo | wrapper.CreationTime = nextLocal | mock +- CALL: PropertyDelegates_ShouldMirrorMockedIDirectoryInfo | wrapper.CreationTimeUtc = nextUtc | mock +- CALL: PropertyDelegates_ShouldMirrorMockedIDirectoryInfo | wrapper.LastAccessTime = nextAccessLocal | mock +- CALL: PropertyDelegates_ShouldMirrorMockedIDirectoryInfo | wrapper.LastAccessTimeUtc = nextAccessUtc | mock +- CALL: PropertyDelegates_ShouldMirrorMockedIDirectoryInfo | wrapper.LastWriteTime = nextWriteLocal | mock +- CALL: PropertyDelegates_ShouldMirrorMockedIDirectoryInfo | wrapper.LastWriteTimeUtc = nextWriteUtc | mock +- CALL: EnumerationAndArrayMethods_ShouldDelegateToWrappedIDirectoryInfo | wrapper.CreateSubdirectory("child") | mock +- CALL: EnumerationAndArrayMethods_ShouldDelegateToWrappedIDirectoryInfo | wrapper.CreateSubdirectory("child", security) | mock +- CALL: LifecycleAndAccessControlMethods_ShouldDelegateToWrappedIDirectoryInfo | wrapper.Create() | mock +- CALL: LifecycleAndAccessControlMethods_ShouldDelegateToWrappedIDirectoryInfo | wrapper.Create(directorySecurity) | mock +- CALL: LifecycleAndAccessControlMethods_ShouldDelegateToWrappedIDirectoryInfo | wrapper.Delete() | mock +- CALL: LifecycleAndAccessControlMethods_ShouldDelegateToWrappedIDirectoryInfo | wrapper.Delete(recursive: true) | mock +- CALL: LifecycleAndAccessControlMethods_ShouldDelegateToWrappedIDirectoryInfo | wrapper.MoveTo("moved") | mock +- CALL: LifecycleAndAccessControlMethods_ShouldDelegateToWrappedIDirectoryInfo | wrapper.Refresh() | mock +- CALL: LifecycleAndAccessControlMethods_ShouldDelegateToWrappedIDirectoryInfo | wrapper.SetAccessControl(directorySecurity) | mock + +- PFS-MUTATING-CALLS: 34 +- DIW-MUTATING-CALLS: 16 +- CLASS-COUNT owned output directory = 4 +- CLASS-COUNT its parent = 2 +- CLASS-COUNT existing owned entry = 2 +- CLASS-COUNT missing owned path = 26 +- CLASS-COUNT mock = 0 +- NON-INVENTORIED: seamAdapter.AppendText(), seamAdapter.Open(FileMode.Open), seamAdapter.Open(FileMode.Open, FileAccess.Read), seamAdapter.OpenWrite() (PhysicalFileInfoAdapter_PropertiesStreamsAndAccessors_MirrorFileInfo; routed through the adapter's internal delegate seam to test-owned sentinel streams, so no file-system call is issued) + +Acceptance for the inventory: + +1. PFS-MUTATING-CALLS: 34 with CLASS-COUNT owned output directory 4, its parent 2, existing owned entry 2, missing owned path 26, mock 0 - met. +2. every DIW CALL line has class mock and a method among the three Moq-based names, with DIW-MUTATING-CALLS 16 (at least 7) - met. +3. every class is one of the five tokens - met. +4. no CALL line names the repository root, a tracked repository path or the solution file - met. + +AC3 evidence also held: P2-T8 census (PFS `new DirectoryInfo(` 2 equal to `new DirectoryInfo(MissingOwnedPath(` 2; `new FileInfo(` 3 equal to `new FileInfo(MissingOwnedPath(` 2 plus `new FileInfo(typeof(` 1; `Exists.Should().BeFalse()` 6; `MissingOwnedPath(` 10; `__940_missing_` 1; `GetRepositoryRoot` 0; the solution-file literal 0; DIW `new DirectoryInfo(` 2 equal to `new DirectoryInfo(RootedFixturePath)` 2; `new FileInfo(` 1 equal to `new FileInfo(typeof(` 1), P2-T10 (`ADDED-ROOTWALK: 0`, `ADDED-TEMP: 0`) and P2-T5 (the four missing-path tests Passed). AC3: MET. diff --git a/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/qa-gates/p2-t15-ac4-form.2026-09-30T11-32.md b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/qa-gates/p2-t15-ac4-form.2026-09-30T11-32.md new file mode 100644 index 000000000..f8f5e2948 --- /dev/null +++ b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/qa-gates/p2-t15-ac4-form.2026-09-30T11-32.md @@ -0,0 +1,47 @@ +# AC4 Member Forms (P2-T15) + +Timestamp: 2026-09-30T11-32 +Task: P2-T15 +Command: Read tool over the two formatted Write Set files and test-run-final.md; no command executed +EXIT_CODE: 0 +Output Summary: fifteen FORM rows, each using one of the four form tokens; every named test reads Passed in FEATURE/evidence/regression-testing/test-run-final.md; the no-op members Create, Refresh and SetAccessControl each appear in a no-op-no-throw row; every member group keeps at least one P2-T8 census token at or above its bound. The per-file coverage evidence of D-8 (P2-T7 MEASUREMENT 3: the three final3 FILE lines NOT-LOWER=True on covered lines and covered branches) also holds. + +- FORM: PDA | getters Exists, Attributes, CreationTime, CreationTimeUtc, LastAccessTime, LastAccessTimeUtc, LastWriteTime, LastWriteTimeUtc, Extension, FullName, Name, Parent, Root; GetAccessControl(), GetAccessControl(sections); GetObjectData; ToString | PhysicalDirectoryInfoAdapter_AccessorsAndNoOpCreation_MirrorOwnedDirectory | owned read-only fixture + - EVIDENCE: RESULT PhysicalDirectoryInfoAdapter_AccessorsAndNoOpCreation_MirrorOwnedDirectory = Passed; P2-T8 PFS TOKEN .GetAccessControl( = 6, .GetObjectData( = 2, .MemberCount = 2, .ToString() = 4, .Root = 2 +- FORM: PDA | Create(), Create(security), CreateSubdirectory(name), CreateSubdirectory(name, security), Refresh(), SetAccessControl(security) | PhysicalDirectoryInfoAdapter_AccessorsAndNoOpCreation_MirrorOwnedDirectory | no-op-no-throw + - EVIDENCE: RESULT PhysicalDirectoryInfoAdapter_AccessorsAndNoOpCreation_MirrorOwnedDirectory = Passed; P2-T8 PFS TOKEN .Create( = 2, .CreateSubdirectory( = 2, .Refresh() = 4, .SetAccessControl( = 2 +- FORM: PDA | the 18 enumeration overloads (EnumerateDirectories, EnumerateFiles, EnumerateFileSystemInfos, GetDirectories, GetFiles, GetFileSystemInfos, three overloads each) | PhysicalDirectoryInfoAdapter_Enumeration_WrapsOwnedDirectoryEntries | owned read-only fixture + - EVIDENCE: RESULT PhysicalDirectoryInfoAdapter_Enumeration_WrapsOwnedDirectoryEntries = Passed; P2-T8 PFS TOKEN .EnumerateDirectories( = 3, .EnumerateFiles( = 3, .EnumerateFileSystemInfos( = 3, .GetDirectories( = 3, .GetFiles( = 3, .GetFileSystemInfos( = 3 +- FORM: PDA | setters Attributes, CreationTime, CreationTimeUtc, LastAccessTime, LastAccessTimeUtc, LastWriteTime, LastWriteTimeUtc | PhysicalDirectoryInfoAdapter_SettersOnMissingDirectory_ThrowWithoutCreatingEntries | non-existent-path outcome + - EVIDENCE: RESULT PhysicalDirectoryInfoAdapter_SettersOnMissingDirectory_ThrowWithoutCreatingEntries = Passed; P2-T8 PFS TOKEN Throw() = 20, Exists.Should().BeFalse() = 6, .CreationTimeUtc = 6 +- FORM: PDA | Delete(), Delete(true), MoveTo | PhysicalDirectoryInfoAdapter_MissingDirectoryAndUnsupportedInfo_BranchesBehaveAsExpected | non-existent-path outcome + - EVIDENCE: RESULT PhysicalDirectoryInfoAdapter_MissingDirectoryAndUnsupportedInfo_BranchesBehaveAsExpected = Passed; P2-T8 PFS TOKEN .Delete() = 2, .Delete(recursive: true) = 1, .MoveTo( = 2, Throw() = 3 +- FORM: PDA | WrapFileSystemInfo unsupported branch (the test-owned UnsupportedInfo) | PhysicalDirectoryInfoAdapter_MissingDirectoryAndUnsupportedInfo_BranchesBehaveAsExpected | owned read-only fixture + - EVIDENCE: RESULT PhysicalDirectoryInfoAdapter_MissingDirectoryAndUnsupportedInfo_BranchesBehaveAsExpected = Passed; P2-T8 PFS TOKEN [TestMethod] = 7 (the method is present); MissingOwnedPath( = 10 +- FORM: PFA | getters Exists, Extension, FullName, Name, Directory, DirectoryName, Length, IsReadOnly, Attributes, the six timestamps; GetAccessControl(), GetAccessControl(sections); GetObjectData; ToString; Open(mode, access, share), OpenRead(), OpenText() | PhysicalFileInfoAdapter_PropertiesStreamsAndAccessors_MirrorFileInfo | owned read-only fixture + - EVIDENCE: RESULT PhysicalFileInfoAdapter_PropertiesStreamsAndAccessors_MirrorFileInfo = Passed; P2-T8 PFS TOKEN .Length = 1, .IsReadOnly = 3, .OpenRead() = 1, .OpenText() = 1, .Open( = 3, .MemberCount = 2 +- FORM: PFA | AppendText(), Open(mode), Open(mode, access), OpenWrite() through the internal delegate seam with test-owned sentinel streams | PhysicalFileInfoAdapter_PropertiesStreamsAndAccessors_MirrorFileInfo | owned read-only fixture + - EVIDENCE: RESULT PhysicalFileInfoAdapter_PropertiesStreamsAndAccessors_MirrorFileInfo = Passed; P2-T8 PFS TOKEN .AppendText() = 1, .OpenWrite() = 1, .Open( = 3 +- FORM: PFA | Refresh(), SetAccessControl(security) | PhysicalFileInfoAdapter_PropertiesStreamsAndAccessors_MirrorFileInfo | no-op-no-throw + - EVIDENCE: RESULT PhysicalFileInfoAdapter_PropertiesStreamsAndAccessors_MirrorFileInfo = Passed; P2-T8 PFS TOKEN .Refresh() = 4, .SetAccessControl( = 2 +- FORM: PFA | setters IsReadOnly, Attributes, the six timestamps | PhysicalFileInfoAdapter_SettersOnMissingFile_ThrowWithoutCreatingFiles | non-existent-path outcome + - EVIDENCE: RESULT PhysicalFileInfoAdapter_SettersOnMissingFile_ThrowWithoutCreatingFiles = Passed; P2-T8 PFS TOKEN Throw() = 20, .IsReadOnly = 3, .Attributes = 6 +- FORM: PFA | Delete() (documented no-throw on a missing file), CopyTo(target), CopyTo(target, overwrite), MoveTo, Replace(target, backup), Replace(target, backup, ignoreMetadataErrors) | PhysicalFileInfoAdapter_MissingFileBranches_ThrowOrNoOpWithoutCreatingFiles | non-existent-path outcome + - EVIDENCE: RESULT PhysicalFileInfoAdapter_MissingFileBranches_ThrowOrNoOpWithoutCreatingFiles = Passed; P2-T8 PFS TOKEN .CopyTo( = 2, .Replace( = 2, .MoveTo( = 2, .Delete() = 2 +- FORM: DIWP | Exists, FullName, Name, Extension, Parent, Root (the rooted test-owned literal) | Properties_ShouldMirrorWrappedDirectoryInfo | owned read-only fixture + - EVIDENCE: RESULT Properties_ShouldMirrorWrappedDirectoryInfo = Passed; P2-T8 DIW TOKEN RootedFixturePath = 3, new DirectoryInfo(RootedFixturePath) = 2, .Parent = 6, .Root = 4 +- FORM: DIWP | ToString (the same rooted literal) | ToString_ShouldDelegateToWrappedDirectoryInfo | owned read-only fixture + - EVIDENCE: RESULT ToString_ShouldDelegateToWrappedDirectoryInfo = Passed; P2-T8 DIW TOKEN .ToString() = 4 +- FORM: DIWP | GetFiles(), GetDirectories(), EnumerateFileSystemInfos() (the owned directory and its parent) | GetDirectoriesAndGetFiles_ShouldReturnWrappedEntries, EnumerateFileSystemInfos_ShouldWrapDirectoriesAndFiles | owned read-only fixture + - EVIDENCE: RESULT GetDirectoriesAndGetFiles_ShouldReturnWrappedEntries = Passed; RESULT EnumerateFileSystemInfos_ShouldWrapDirectoriesAndFiles = Passed; P2-T8 DIW TOKEN .GetFiles( = 7, .GetDirectories( = 7, .EnumerateFileSystemInfos( = 8 +- FORM: DIWP | every member the three Moq-based tests exercise (unchanged by this item) | PropertyDelegates_ShouldMirrorMockedIDirectoryInfo, EnumerationAndArrayMethods_ShouldDelegateToWrappedIDirectoryInfo, LifecycleAndAccessControlMethods_ShouldDelegateToWrappedIDirectoryInfo | Moq + - EVIDENCE: RESULT PropertyDelegates_ShouldMirrorMockedIDirectoryInfo = Passed; RESULT EnumerationAndArrayMethods_ShouldDelegateToWrappedIDirectoryInfo = Passed; RESULT LifecycleAndAccessControlMethods_ShouldDelegateToWrappedIDirectoryInfo = Passed; P2-T8 DIW TOKEN using Moq; = 1, .Create( = 4, .Refresh() = 2, .SetAccessControl( = 2 + +Acceptance for the forms: + +1. exactly fifteen FORM lines - met. +2. every form is one of Moq, owned read-only fixture, non-existent-path outcome, no-op-no-throw - met. +3. every named test reads Passed in test-run-final.md - met. +4. Create, Refresh and SetAccessControl each appear in a no-op-no-throw row - met (PDA row 2 and PFA row 9). + +AC4: MET (census lower bounds held in P2-T8, including .MemberCount 2; P2-T7 MEASUREMENT 3 FILE lines NOT-LOWER=True for all three files; P2-T5 fifteen tests Passed). diff --git a/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/qa-gates/p2-t2-csharpier-check.2026-09-30T08-08.md b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/qa-gates/p2-t2-csharpier-check.2026-09-30T08-08.md new file mode 100644 index 000000000..55abd0260 --- /dev/null +++ b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/qa-gates/p2-t2-csharpier-check.2026-09-30T08-08.md @@ -0,0 +1,11 @@ +# Repository-Wide CSharpier Check (P2-T2) + +Timestamp: 2026-09-30T08-08 +Task: P2-T2 +ITERATION: 1 +Command: dotnet tool run csharpier check . +EXIT_CODE: 0 +Output Summary: formatting check clean; the tool printed no file list and its final line verbatim was `Checked 1625 files in 7661ms.` + +- CHECKED-FILES: 1625 +- CHECKED-DELTA: 0 (1625 minus the P0-T6 `CHECKED-FILES: 1625`; an observation, not a gate) diff --git a/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/qa-gates/p2-t20-closure.2026-09-30T11-33.md b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/qa-gates/p2-t20-closure.2026-09-30T11-33.md new file mode 100644 index 000000000..87047aa20 --- /dev/null +++ b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/qa-gates/p2-t20-closure.2026-09-30T11-33.md @@ -0,0 +1,44 @@ +# Final Commit and Closure (P2-T20) + +Timestamp: 2026-09-30T11-33 +Task: P2-T20 +Command: CMD-SWEEP; git diff --cached --name-only; git status --porcelain -- docs/features/potential; git add -- UtilitiesCS.Test docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940; hygiene guard payload (`& pwsh -NoProfile -File (Join-Path (Get-Location).Path "scripts\hygiene\Test-RepositoryHygiene.ps1") 2>&1 | Tee-Object -FilePath "coverage\logs\p2-t20.hygiene.log"`, then `HYGIENE_GUARD_EXIT:`); git commit -m "docs(940): evidence, acceptance check-off and plan state for the file-system wrapper test fix" -m "Co-Authored-By: Claude Opus 5.5 noreply@anthropic.com" -- UtilitiesCS.Test docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940; git rev-parse HEAD; git diff --name-only origin/main...HEAD -- UtilitiesCS UtilitiesCS.Test; git diff --exit-code origin/main...HEAD -- UtilitiesCS; git merge-base HEAD origin/main; then the two amend steps and the step-6 porcelain span recorded below +Output Summary: the step-1 sweep reported every count 0 over 51 files; nothing was pre-staged; the potential tree is clean; the hygiene guard reported no finding; the final commit succeeded; the committed source footprint is exactly the two Write Set test files and no production file changed; the merge base is the P2-T7 ANCHOR-SHA-2 value; all eight acceptance criteria are checked off. + +## Pre-commit observations + +- STEP-1-SWEEP: FILES 51; ACCOUNT-TOKEN-MATCHES 0; PROFILE-LEAF-MATCHES 0; MACHINE-TOKEN-MATCHES 0; WORKTREE-ROOT-MATCHES 0; USERS-PATH-MATCHES 0; HYGIENE-PROFILE-PATTERN-MATCHES 0; RAW-DOCUMENT-FILES 0 +- PRE-STAGED: NONE +- POTENTIAL-PORCELAIN: EMPTY +- HYGIENE_GUARD_EXIT: 0 +- HYGIENE Findings=0 +- HYGIENE-ITEM-FINDINGS: NONE +- HYGIENE-INHERITED-FINDINGS: NONE + +## Commit + +- COMMIT-1-EXIT: 0 +- HEAD-AFTER-COMMIT: 8fe58c339a2ef6b7f0ad43b3a4106949b7584855 +- Confirming footprint (`git diff --name-only origin/main...HEAD -- UtilitiesCS UtilitiesCS.Test`, verbatim; the step-2 `git add` span is its companion): + - UtilitiesCS.Test/HelperClasses/DirectoryInfoWrapper_Tests.cs + - UtilitiesCS.Test/HelperClasses/PhysicalFileSystemAdapters_Tests.cs +- COMMITTED-PRODUCTION-DIFF-EXIT: 0 +- MERGE-BASE-NOW: 66afa6372fd82fc1ffd7c81f85a1ad65eebc5817 (equals the P2-T7 `ANCHOR-SHA-2:` value) + +## Follow-up pointer + +- P1-T1 classification record for the orchestrator's follow-up decision: FEATURE/evidence/other/root-walk-site-classification.2026-09-30T07-27.md (the fourth site, SortEmail_Tests.cs `TrySaveAttachmentAsync_WhenSaveSucceeds_ReturnsTrueAndCallsSaveAsFile`, is classified `same defect class` and was not modified by this item). + +## Acceptance Criteria Status + +- Source: docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/issue.md +- Total AC items: 8 +- Checked off (delivered): 8 +- Remaining (unchecked): 0 +- Items remaining: none + +## Post-amend state + +EXIT_CODE: 0 (scoped to the step-6 `git status --porcelain -- UtilitiesCS UtilitiesCS.Test docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940` span) +- POST-AMEND-PORCELAIN: EMPTY +- HEAD-BEFORE-FINAL-AMEND: f2de58ea71aeb9b8d70abdc28e9b0f6cd4ff8ad0 (superseded by the step-8 amend, which adds this section) diff --git a/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/qa-gates/p2-t3-msbuild-analyzers.2026-09-30T08-09.md b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/qa-gates/p2-t3-msbuild-analyzers.2026-09-30T08-09.md new file mode 100644 index 000000000..0e2849d28 --- /dev/null +++ b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/qa-gates/p2-t3-msbuild-analyzers.2026-09-30T08-09.md @@ -0,0 +1,17 @@ +# Analyzer Rebuild Gate (P2-T3) + +Timestamp: 2026-09-30T08-09 +Task: P2-T3 +ITERATION: 1 +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; file logger coverage\logs\p2-t3.msbuild.log at normal verbosity, git-ignored; the console stream was discarded with Out-Null and every recorded value is read from the file log, as CMD-REBUILD defines) +EXIT_CODE: 0 +Output Summary: analyzer rebuild clean; CoreCompile ran for both the test project and its production project; zero warnings, zero errors, no Write Set diagnostic. +- MSBUILD_EXIT_CODE: 0 +- SKIP_CORECOMPILE_LINES: 0 +- UCS_TEST_CSC_OUT_LINES: 2 +- UCS_CSC_OUT_LINES: 2 +- ZERO_ERRORS_LINES: 1 +- WARNINGS: 0 +- WARNINGS-DELTA: 0 (0 minus `ANALYZE-BASELINE-WARNINGS: 0`; an observation) +- ERRORS: 0 +- WRITESET_DIAGNOSTIC_LINES: 0 diff --git a/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/qa-gates/p2-t4-msbuild-nullable.2026-09-30T08-10.md b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/qa-gates/p2-t4-msbuild-nullable.2026-09-30T08-10.md new file mode 100644 index 000000000..a8650bb93 --- /dev/null +++ b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/qa-gates/p2-t4-msbuild-nullable.2026-09-30T08-10.md @@ -0,0 +1,17 @@ +# Nullable / Type-Check Rebuild Gate (P2-T4) + +Timestamp: 2026-09-30T08-10 +Task: P2-T4 +ITERATION: 1 +Command: 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; file logger coverage\logs\p2-t4.msbuild.log at normal verbosity, git-ignored; console stream discarded with Out-Null, every value read from the file log) +EXIT_CODE: 0 +Output Summary: TreatWarningsAsErrors rebuild clean; CoreCompile ran for both projects; zero warnings and errors; no Write Set diagnostic. +- MSBUILD_EXIT_CODE: 0 +- SKIP_CORECOMPILE_LINES: 0 +- UCS_TEST_CSC_OUT_LINES: 2 +- UCS_CSC_OUT_LINES: 2 +- ZERO_ERRORS_LINES: 1 +- WARNINGS: 0 +- WARNINGS-DELTA: 0 (0 minus `NULLABLE-BASELINE-WARNINGS: 0`; an observation) +- ERRORS: 0 +- WRITESET_DIAGNOSTIC_LINES: 0 diff --git a/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/qa-gates/p2-t7-msbuild-analyzers.2026-09-30T11-19.md b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/qa-gates/p2-t7-msbuild-analyzers.2026-09-30T11-19.md new file mode 100644 index 000000000..1baf53fdb --- /dev/null +++ b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/qa-gates/p2-t7-msbuild-analyzers.2026-09-30T11-19.md @@ -0,0 +1,18 @@ +# Post-Merge Analyzer Rebuild Gate (P2-T7 step d, sub-record) + +Timestamp: 2026-09-30T11-19 +Task: P2-T7 (step d, TASKID p2-t7a) +ITERATION: 1 +POST-MERGE: YES +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; file logger coverage\logs\p2-t7a.msbuild.log at normal verbosity, git-ignored; the console stream was discarded with Out-Null and every recorded value is read from the file log, as CMD-REBUILD defines) +EXIT_CODE: 0 +Output Summary: post-merge analyzer rebuild on HEAD d3f01551991a93ce2038db79415540992dc8b5fe (which contains the orchestrator merge commit 40e587ce20bbd41cd6915707271faae937f1a8e2) is clean; CoreCompile ran for both the test project and its production project; zero warnings, zero errors, no Write Set diagnostic. Started after the foreign-process probe printed FOREIGN-TEST-PROCESSES: 0. +- MSBUILD_EXIT_CODE: 0 +- SKIP_CORECOMPILE_LINES: 0 +- UCS_TEST_CSC_OUT_LINES: 2 +- UCS_CSC_OUT_LINES: 2 +- ZERO_ERRORS_LINES: 1 +- WARNINGS: 0 +- WARNINGS-DELTA: 0 (0 minus `ANALYZE-BASELINE-WARNINGS: 0`; an observation) +- ERRORS: 0 +- WRITESET_DIAGNOSTIC_LINES: 0 diff --git a/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/qa-gates/p2-t7-msbuild-nullable.2026-09-30T11-20.md b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/qa-gates/p2-t7-msbuild-nullable.2026-09-30T11-20.md new file mode 100644 index 000000000..8422cac7c --- /dev/null +++ b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/qa-gates/p2-t7-msbuild-nullable.2026-09-30T11-20.md @@ -0,0 +1,18 @@ +# Post-Merge Nullable / Type-Check Rebuild Gate (P2-T7 step d, sub-record) + +Timestamp: 2026-09-30T11-20 +Task: P2-T7 (step d, TASKID p2-t7b) +ITERATION: 1 +POST-MERGE: YES +Command: 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; file logger coverage\logs\p2-t7b.msbuild.log at normal verbosity, git-ignored; console stream discarded with Out-Null, every value read from the file log) +EXIT_CODE: 0 +Output Summary: post-merge TreatWarningsAsErrors rebuild on HEAD d3f01551991a93ce2038db79415540992dc8b5fe is clean; CoreCompile ran for both projects; zero warnings and errors; no Write Set diagnostic. +- MSBUILD_EXIT_CODE: 0 +- SKIP_CORECOMPILE_LINES: 0 +- UCS_TEST_CSC_OUT_LINES: 2 +- UCS_CSC_OUT_LINES: 2 +- ZERO_ERRORS_LINES: 1 +- WARNINGS: 0 +- WARNINGS-DELTA: 0 (0 minus `NULLABLE-BASELINE-WARNINGS: 0`; an observation) +- ERRORS: 0 +- WRITESET_DIAGNOSTIC_LINES: 0 diff --git a/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/qa-gates/p2-t8-post-format-census.2026-09-30T11-25.md b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/qa-gates/p2-t8-post-format-census.2026-09-30T11-25.md new file mode 100644 index 000000000..88cac75da --- /dev/null +++ b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/qa-gates/p2-t8-post-format-census.2026-09-30T11-25.md @@ -0,0 +1,188 @@ +# Post-Format Census of the Two Write Set Files (P2-T8) + +Timestamp: 2026-09-30T11-25 +Task: P2-T8 +Command: CMD-CENSUS with PATH UtilitiesCS.Test\HelperClasses\PhysicalFileSystemAdapters_Tests.cs, then with PATH UtilitiesCS.Test\HelperClasses\DirectoryInfoWrapper_Tests.cs (one payload looping the Command Reference block over the two paths; the token array is the Command Reference array unchanged) +EXIT_CODE: 0 +Output Summary: both files are within the 500-line ceiling (PFS 446, DIW 394); every `post` value of the Token Census Expectations table holds under its gate column for both files; the `new DirectoryInfo(` and `new FileInfo(` equalities hold; THROW-SUM is 23; both SHA-256 values equal the after-format hashes of the single P2-T1 iteration (ITERATION 1, REWRITTEN 0), and therefore also equal FIX-HASH-PFS and FIX-HASH-DIW. +- CENSUS-TOKENS-APPENDED: NONE (P1-T7 recorded PINNED-COUNT: 0) +- THROW-SUM: 23 (PFS Throw() 20 plus Throw() 3) +- PFS-HASH-CHECK: C88A785C23D8DB2960E9FAA53DF9EF91F6F00359683485BEC7EDFC44F9A2F998 equals P2-T1 PFS-HASH-AFTER and FIX-HASH-PFS +- DIW-HASH-CHECK: 6650B33204BABCAAB7CD6E97C8B4BA7012ABB1F1320DB6AF7764ECD8714B7910 equals P2-T1 DIW-HASH-AFTER and FIX-HASH-DIW +- EQUALITY PFS new DirectoryInfo( 2 = new DirectoryInfo(MissingOwnedPath( 2 + new DirectoryInfo(RootedFixturePath) 0: holds +- EQUALITY PFS new FileInfo( 3 = new FileInfo(MissingOwnedPath( 2 + new FileInfo(typeof( 1: holds +- EQUALITY DIW new DirectoryInfo( 2 = new DirectoryInfo(RootedFixturePath) 2: holds +- EQUALITY DIW new FileInfo( 1 = new FileInfo(typeof( 1: holds +- FILE-SIZE: PFS 446, DIW 394 (both at most 500) + +## PFS: UtilitiesCS.Test/HelperClasses/PhysicalFileSystemAdapters_Tests.cs + +- TOKEN GetRepositoryRoot = 0 +- TOKEN GetSolutionFile = 0 +- TOKEN TaskMaster.sln = 0 +- TOKEN AppDomain = 0 +- TOKEN File.Exists( = 4 +- TOKEN catch (IOException) = 0 +- TOKEN catch = 0 +- TOKEN Assembly.Location = 4 +- TOKEN RootedFixturePath = 0 +- TOKEN OwnedAssemblyFile = 4 +- TOKEN OwnedAssemblyDirectory = 4 +- TOKEN MissingOwnedPath( = 10 +- TOKEN __940_missing_ = 1 +- TOKEN Exists.Should().BeFalse() = 6 +- TOKEN new DirectoryInfo( = 2 +- TOKEN new DirectoryInfo(RootedFixturePath) = 0 +- TOKEN new DirectoryInfo(MissingOwnedPath( = 2 +- TOKEN new FileInfo( = 3 +- TOKEN new FileInfo(MissingOwnedPath( = 2 +- TOKEN new FileInfo(typeof( = 1 +- TOKEN [TestMethod] = 7 +- TOKEN using System.Linq; = 1 +- TOKEN using Moq; = 0 +- TOKEN Thread.Sleep = 0 +- TOKEN Task.Delay = 0 +- TOKEN [Timeout = 0 +- TOKEN DoNotParallelize = 0 +- TOKEN Retry = 0 +- TOKEN Path.GetTemp = 0 +- TOKEN File.Create = 0 +- TOKEN File.WriteAll = 0 +- TOKEN Directory.CreateDirectory = 0 +- TOKEN FileShare.ReadWrite = 4 +- TOKEN FileShare.None = 0 +- TOKEN .Create() = 1 +- TOKEN .Create( = 2 +- TOKEN .CreateSubdirectory( = 2 +- TOKEN .EnumerateDirectories( = 3 +- TOKEN .EnumerateFiles( = 3 +- TOKEN .EnumerateFileSystemInfos( = 3 +- TOKEN .GetDirectories( = 3 +- TOKEN .GetFiles( = 3 +- TOKEN .GetFileSystemInfos( = 3 +- TOKEN .GetAccessControl( = 6 +- TOKEN .GetObjectData( = 2 +- TOKEN .Refresh() = 4 +- TOKEN .SetAccessControl( = 2 +- TOKEN .ToString() = 4 +- TOKEN .Delete() = 2 +- TOKEN .Delete(recursive: true) = 1 +- TOKEN .MoveTo( = 2 +- TOKEN .CopyTo( = 2 +- TOKEN .Replace( = 2 +- TOKEN .Open( = 3 +- TOKEN .OpenRead() = 1 +- TOKEN .OpenText() = 1 +- TOKEN .AppendText() = 1 +- TOKEN .OpenWrite() = 1 +- TOKEN .Parent = 3 +- TOKEN .Root = 2 +- TOKEN .Length = 1 +- TOKEN .IsReadOnly = 3 +- TOKEN .Attributes = 6 +- TOKEN .CreationTime = 12 +- TOKEN .CreationTimeUtc = 6 +- TOKEN .LastAccessTime = 12 +- TOKEN .LastAccessTimeUtc = 6 +- TOKEN .LastWriteTime = 12 +- TOKEN .LastWriteTimeUtc = 6 +- TOKEN .MemberCount = 2 +- TOKEN Throw() = 20 +- TOKEN Throw() = 3 +- TOKEN !_directoryInfo.Exists = 0 +- TOKEN Array.Empty() = 0 +- TOKEN Enumerable.Empty() = 0 +- TOKEN set { } = 0 +- TOKEN public void Delete() { } = 0 +- TOKEN _directoryInfo.FullName = 0 +- TOKEN public long Length => 0; = 0 +- TOKEN public void MoveTo(string destFileName) { } = 0 +- TOKEN return string.Empty; = 0 +- LINES = 446 +- SHA256 = C88A785C23D8DB2960E9FAA53DF9EF91F6F00359683485BEC7EDFC44F9A2F998 + +## DIW: UtilitiesCS.Test/HelperClasses/DirectoryInfoWrapper_Tests.cs + +- TOKEN GetRepositoryRoot = 0 +- TOKEN GetSolutionFile = 0 +- TOKEN TaskMaster.sln = 0 +- TOKEN AppDomain = 0 +- TOKEN File.Exists( = 0 +- TOKEN catch (IOException) = 0 +- TOKEN catch = 0 +- TOKEN Assembly.Location = 1 +- TOKEN RootedFixturePath = 3 +- TOKEN OwnedAssemblyFile = 4 +- TOKEN OwnedAssemblyDirectory = 3 +- TOKEN MissingOwnedPath( = 0 +- TOKEN __940_missing_ = 0 +- TOKEN Exists.Should().BeFalse() = 0 +- TOKEN new DirectoryInfo( = 2 +- TOKEN new DirectoryInfo(RootedFixturePath) = 2 +- TOKEN new DirectoryInfo(MissingOwnedPath( = 0 +- TOKEN new FileInfo( = 1 +- TOKEN new FileInfo(MissingOwnedPath( = 0 +- TOKEN new FileInfo(typeof( = 1 +- TOKEN [TestMethod] = 8 +- TOKEN using System.Linq; = 1 +- TOKEN using Moq; = 1 +- TOKEN Thread.Sleep = 0 +- TOKEN Task.Delay = 0 +- TOKEN [Timeout = 0 +- TOKEN DoNotParallelize = 0 +- TOKEN Retry = 0 +- TOKEN Path.GetTemp = 0 +- TOKEN File.Create = 0 +- TOKEN File.WriteAll = 0 +- TOKEN Directory.CreateDirectory = 0 +- TOKEN FileShare.ReadWrite = 0 +- TOKEN FileShare.None = 0 +- TOKEN .Create() = 2 +- TOKEN .Create( = 4 +- TOKEN .CreateSubdirectory( = 4 +- TOKEN .EnumerateDirectories( = 6 +- TOKEN .EnumerateFiles( = 6 +- TOKEN .EnumerateFileSystemInfos( = 8 +- TOKEN .GetDirectories( = 7 +- TOKEN .GetFiles( = 7 +- TOKEN .GetFileSystemInfos( = 6 +- TOKEN .GetAccessControl( = 4 +- TOKEN .GetObjectData( = 2 +- TOKEN .Refresh() = 2 +- TOKEN .SetAccessControl( = 2 +- TOKEN .ToString() = 4 +- TOKEN .Delete() = 2 +- TOKEN .Delete(recursive: true) = 1 +- TOKEN .MoveTo( = 2 +- TOKEN .CopyTo( = 0 +- TOKEN .Replace( = 0 +- TOKEN .Open( = 0 +- TOKEN .OpenRead() = 0 +- TOKEN .OpenText() = 0 +- TOKEN .AppendText() = 0 +- TOKEN .OpenWrite() = 0 +- TOKEN .Parent = 6 +- TOKEN .Root = 4 +- TOKEN .Length = 0 +- TOKEN .IsReadOnly = 0 +- TOKEN .Attributes = 4 +- TOKEN .CreationTime = 8 +- TOKEN .CreationTimeUtc = 4 +- TOKEN .LastAccessTime = 8 +- TOKEN .LastAccessTimeUtc = 4 +- TOKEN .LastWriteTime = 8 +- TOKEN .LastWriteTimeUtc = 4 +- TOKEN .MemberCount = 0 +- TOKEN Throw() = 0 +- TOKEN Throw() = 0 +- TOKEN !_directoryInfo.Exists = 0 +- TOKEN Array.Empty() = 0 +- TOKEN Enumerable.Empty() = 0 +- TOKEN set { } = 0 +- TOKEN public void Delete() { } = 0 +- TOKEN _directoryInfo.FullName = 0 +- TOKEN public long Length => 0; = 0 +- TOKEN public void MoveTo(string destFileName) { } = 0 +- TOKEN return string.Empty; = 0 +- LINES = 394 +- SHA256 = 6650B33204BABCAAB7CD6E97C8B4BA7012ABB1F1320DB6AF7764ECD8714B7910 diff --git a/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/qa-gates/toolchain-pass.md b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/qa-gates/toolchain-pass.md new file mode 100644 index 000000000..6289af273 --- /dev/null +++ b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/qa-gates/toolchain-pass.md @@ -0,0 +1,32 @@ +# Toolchain Loop Closure (P2-T9) + +Timestamp: 2026-09-30T11-26 +Task: P2-T9 +Command: reconciliation of P2-T1 through P2-T8 +EXIT_CODE: 0 +Output Summary: the Phase 2 loop ran once from P2-T1 (no restart); every step met its own declared expectation; the analyzer, nullable and coverage lines quote the P2-T7 post-merge records, which are the latest runs of those commands on the merged head d3f01551991a93ce2038db79415540992dc8b5fe. + +Toolchain lines of the final clean iteration, in CLAUDE.md order: + +1. `dotnet tool run csharpier format .` (P2-T1): exit 0 (declared expectation 0); `REWRITTEN: 0`; artifact FEATURE/evidence/qa-gates/p2-t1-csharpier-format.2026-09-30T08-08.md. +2. `dotnet tool run csharpier check .` (P2-T2): exit 0 (declared expectation 0); `Checked 1625 files in 7661ms.`; artifact FEATURE/evidence/qa-gates/p2-t2-csharpier-check.2026-09-30T08-08.md. +3. `msbuild TaskMaster.sln /t:Rebuild /m /p:Configuration=Debug "/p:Platform=Any CPU" /p:EnableNETAnalyzers=true /p:EnforceCodeStyleInBuild=true` (P2-T3 and its post-merge re-run; values quoted from the P2-T7 record): exit 0 (declared expectation 0); `SKIP_CORECOMPILE_LINES: 0`; ERRORS 0; artifact FEATURE/evidence/qa-gates/p2-t7-msbuild-analyzers.2026-09-30T11-19.md (pre-merge run FEATURE/evidence/qa-gates/p2-t3-msbuild-analyzers.2026-09-30T08-09.md, also exit 0). +4. `msbuild TaskMaster.sln /t:Rebuild /m /p:Configuration=Debug "/p:Platform=Any CPU" /p:TreatWarningsAsErrors=true` (P2-T4 and its post-merge re-run; values quoted from the P2-T7 record): exit 0 (declared expectation 0); `SKIP_CORECOMPILE_LINES: 0`; ERRORS 0; artifact FEATURE/evidence/qa-gates/p2-t7-msbuild-nullable.2026-09-30T11-20.md (pre-merge run FEATURE/evidence/qa-gates/p2-t4-msbuild-nullable.2026-09-30T08-10.md, also exit 0). +5. MSTest with coverage (P2-T6 MEASUREMENT 1 and the gating post-merge MEASUREMENT 3 of P2-T7; values quoted from MEASUREMENT 3): `COVERAGE-ROUTE: DIRECT`; collector exit 0 (declared expectation 0, no `ExpectedExitCode:` declared); 7327 of 7327 passed; `LINE-FLOOR: MET`, `BRANCH-FLOOR: MET`; artifact FEATURE/evidence/qa-gates/coverage-final.md, section `Post-merge measurement (MEASUREMENT: 3, STAGE final3)`. + +Scoped run: + +- Scoped vstest of the two rewritten classes (P2-T5): exit 0 (declared expectation 0); total 15, executed 15, failed 0; artifact FEATURE/evidence/regression-testing/test-run-final.md. + +- ITERATIONS: 1 + +- EXPECTATION-MET: P2-T1 YES +- EXPECTATION-MET: P2-T2 YES +- EXPECTATION-MET: P2-T3 YES +- EXPECTATION-MET: P2-T4 YES +- EXPECTATION-MET: P2-T5 YES +- EXPECTATION-MET: P2-T6 YES +- EXPECTATION-MET: P2-T7 YES +- EXPECTATION-MET: P2-T8 YES + +LOOP: CLEAN PASS diff --git a/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/regression-testing/fail-before-exception.2026-09-30T07-28.md b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/regression-testing/fail-before-exception.2026-09-30T07-28.md new file mode 100644 index 000000000..7968b27c8 --- /dev/null +++ b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/regression-testing/fail-before-exception.2026-09-30T07-28.md @@ -0,0 +1,19 @@ +# Fail-Before Exception Dossier (P1-T2) + +Timestamp: 2026-09-30T07-28 + +WhyFailingRunImpossible: The defect is decided by whether another process holds TaskMaster.sln with a share mode that excludes readers, or by whether the repository layout places a solution file above the test assembly. A committed test can arrange neither without starting an external process or relocating the assembly, and the unit-test policy prohibits both, so no committed test can deterministically reproduce the failure before the fix. + +## Alternative Proof + +- Static reproduction, as re-recorded by P0-T12 (FEATURE/evidence/baseline/p0-t12-pre-edit-census.2026-09-30T07-22.md): UtilitiesCS.Test/HelperClasses/PhysicalFileSystemAdapters_Tests.cs walks up from the test assembly to the directory holding TaskMaster.sln in `GetRepositoryRoot()` (lines 349 to 371) and returns the solution file itself from `GetSolutionFile()` (lines 373 to 377); the pre-edit tests then perform real mutating calls on those two entries (fact 2: the `Attributes` and six timestamp setters, `Create()`, `Create(security)`, `CreateSubdirectory` and `SetAccessControl` on the repository root; the six timestamp setters, `IsReadOnly` and `SetAccessControl` on TaskMaster.sln; and `CopyTo` and `Replace` naming the solution file as destination). UtilitiesCS.Test/HelperClasses/DirectoryInfoWrapper_Tests.cs walks up the same way in its `GetRepositoryRoot()` (lines 375 to 391) and asserts the presence of TaskMaster.sln in its enumeration assertions at lines 60 and 79. Each of these sites opens or modifies the solution file, so a concurrent holder of that file with an excluding share mode makes the test fail with an I/O exception unrelated to the unit under test. +- Recorded failure of the same defect class: the #906 failure observed during the #900 run, cited through the #931 dossier docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/regression-testing/fail-before-exception.2026-09-29T09-07.md. +- Deterministic observed-failing evidence: P1-T9 through P1-T30 later demonstrate deterministically that each rewritten test fails against a deliberately broken wrapper or adapter and passes once the break is reverted. This dossier states that plan; it records no result of those tasks. + +SearchScope: FEATURE/evidence/regression-testing/ +SearchPatterns: `fail-before-exception.*.md` +SearchResult: this file (fail-before-exception.2026-09-30T07-28.md) + +## Output Summary + +Fail-before run structurally impossible without an external process holding the solution file or a relocated assembly; alternative proof recorded (static reproduction from the P0-T12 pre-edit census, the #906 failure through the #931 dossier, and the eleven negative controls P1-T9 through P1-T30 as the deterministic observed-failing evidence). diff --git a/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/regression-testing/mutation-directory-creationtime-setter-noop.md b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/regression-testing/mutation-directory-creationtime-setter-noop.md new file mode 100644 index 000000000..37d688bcc --- /dev/null +++ b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/regression-testing/mutation-directory-creationtime-setter-noop.md @@ -0,0 +1,49 @@ +# Negative Control C3: Directory CreationTime Setter No-Op (P1-T13, P1-T14) + +Timestamp: 2026-09-30T07-42 +Task: P1-T13 [expect-fail] +Command: CMD-CENSUS with PATH = UtilitiesCS/HelperClasses/FileSystem/PhysicalDirectoryInfoAdapter.cs (the payload printed the non-zero TOKEN lines plus the transition token); msbuild UtilitiesCS.Test\UtilitiesCS.Test.csproj /t:Build /m /p:Configuration=Debug /p:Platform=AnyCPU (resolved through vswhere, plus /nodeReuse:false; TASKID p1-t13); vstest.console.exe UtilitiesCS.Test\bin\Debug\UtilitiesCS.Test.dll "/Settings:scripts\vscode\TaskMaster.cli.runsettings" /InIsolation "/TestCaseFilter:FullyQualifiedName~HelperClasses.PhysicalFileSystemAdapters_Tests.PhysicalDirectoryInfoAdapter_SettersOnMissingDirectory_ThrowWithoutCreatingEntries" "/ResultsDirectory:coverage\test-results\940\p1-t13" "/Logger:trx;LogFileName=p1-t13.trx" "/Blame:CollectHangDump;TestTimeout=4min;HangDumpType=None" (resolved through vswhere; NAMES-PFS) +EXIT_CODE: 1 (scoped to the mutated vstest run) +ExpectedExitCode: 1 +Output Summary: the mutation compiled and the predicted test failed on the first assertion (`setCreationTime`) with the predicted phrase `but no exception was thrown`; total 1, failed 1. + +## Mutated run + +- MUTATED-FILE: UtilitiesCS/HelperClasses/FileSystem/PhysicalDirectoryInfoAdapter.cs line 25 +- PRE-EDIT-LINE: `set => _directoryInfo.CreationTime = value;` +- MUTATED-LINE: `set { }` +- CENSUS-TRANSITION: TOKEN set { } = 1 +- Other non-zero census lines: TOKEN using System.Linq; = 1; TOKEN .Create() = 1; TOKEN .Create( = 2; TOKEN .CreateSubdirectory( = 2; TOKEN .EnumerateDirectories( = 3; TOKEN .EnumerateFiles( = 3; TOKEN .EnumerateFileSystemInfos( = 3; TOKEN .GetDirectories( = 3; TOKEN .GetFiles( = 3; TOKEN .GetFileSystemInfos( = 3; TOKEN .GetAccessControl( = 2; TOKEN .GetObjectData( = 1; TOKEN .Refresh() = 1; TOKEN .SetAccessControl( = 1; TOKEN .ToString() = 1; TOKEN .Delete() = 1; TOKEN .MoveTo( = 1; TOKEN .Parent = 2; TOKEN .Root = 1; TOKEN .Attributes = 2; TOKEN .CreationTime = 3; TOKEN .CreationTimeUtc = 2; TOKEN .LastAccessTime = 4; TOKEN .LastAccessTimeUtc = 2; TOKEN .LastWriteTime = 4; TOKEN .LastWriteTimeUtc = 2; TOKEN _directoryInfo.FullName = 1 +- LINES = 217 +- SHA256 = 20457DCEB02AF41C24DF0D1B047D79F0D2CD40F0C4A5250F8D2435833F610246 +- MSBUILD_EXIT_CODE: 0 +- CSC_OUT_LINES: 2 +- PROD_CSC_OUT_LINES: 2 +- ZERO_ERRORS_LINES: 1 +- DLL_ADVANCED: True +- RUNSETTINGS: scripts\vscode\TaskMaster.cli.runsettings with /InIsolation +- FILTER: FullyQualifiedName~HelperClasses.PhysicalFileSystemAdapters_Tests.PhysicalDirectoryInfoAdapter_SettersOnMissingDirectory_ThrowWithoutCreatingEntries +- RUNSETTINGS-HASH-NOW: 98EF03A8D3B0EBB2ED7A765E3B5E1B58E774D20202DF2F294C03A7260B9CEF57 (equals RUNSETTINGS-HASH) +- VSTEST_EXIT_CODE: 1 +- TRX_PRESENT: True +- SEQUENCE_FILES: 0 +- COUNTERS total=1 executed=1 passed=0 failed=1 +- RESULT PhysicalDirectoryInfoAdapter_SettersOnMissingDirectory_ThrowWithoutCreatingEntries = Failed +- MESSAGE PhysicalDirectoryInfoAdapter_SettersOnMissingDirectory_ThrowWithoutCreatingEntries :: Expected a to be thrown, but no exception was thrown. +- PREDICTED-PHRASE: `but no exception was thrown` (present) + +## Revert and confirming run + +- Task: P1-T14 +- Command: git checkout -- UtilitiesCS/HelperClasses/FileSystem/PhysicalDirectoryInfoAdapter.cs; git diff --exit-code HEAD -- UtilitiesCS/HelperClasses/FileSystem/PhysicalDirectoryInfoAdapter.cs; git status --porcelain -- UtilitiesCS/HelperClasses/FileSystem/PhysicalDirectoryInfoAdapter.cs; CMD-CENSUS (same PATH; non-zero TOKEN lines plus the transition token printed); CMD-BUILD with TASKID p1-t14; CMD-VSTEST with the C3 filter, NAMES-PFS and TASKID p1-t14 +- CHECKOUT-EXIT: 0 +- REVERT-DIFF-EXIT: 0 +- PORCELAIN: EMPTY +- CENSUS-TRANSITION: TOKEN set { } = 0 +- SHA256: FBAC7002BE4DF5979624F8AE649D52F1044BC40D54EBDA4618C6374EAB9CB242 (anchor PRE-EDIT-HASH-PDA: FBAC7002BE4DF5979624F8AE649D52F1044BC40D54EBDA4618C6374EAB9CB242; 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: 98EF03A8D3B0EBB2ED7A765E3B5E1B58E774D20202DF2F294C03A7260B9CEF57 +- TRX_PRESENT: True; SEQUENCE_FILES: 0 +- COUNTERS total=1 executed=1 passed=1 failed=0 +- RESULT PhysicalDirectoryInfoAdapter_SettersOnMissingDirectory_ThrowWithoutCreatingEntries = Passed diff --git a/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/regression-testing/mutation-directory-delete-noop.md b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/regression-testing/mutation-directory-delete-noop.md new file mode 100644 index 000000000..616b8c849 --- /dev/null +++ b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/regression-testing/mutation-directory-delete-noop.md @@ -0,0 +1,49 @@ +# Negative Control C4: Directory Delete No-Op (P1-T15, P1-T16) + +Timestamp: 2026-09-30T07-44 +Task: P1-T15 [expect-fail] +Command: CMD-CENSUS with PATH = UtilitiesCS/HelperClasses/FileSystem/PhysicalDirectoryInfoAdapter.cs (the payload printed the non-zero TOKEN lines plus the transition token); msbuild UtilitiesCS.Test\UtilitiesCS.Test.csproj /t:Build /m /p:Configuration=Debug /p:Platform=AnyCPU (resolved through vswhere, plus /nodeReuse:false; TASKID p1-t15); vstest.console.exe UtilitiesCS.Test\bin\Debug\UtilitiesCS.Test.dll "/Settings:scripts\vscode\TaskMaster.cli.runsettings" /InIsolation "/TestCaseFilter:FullyQualifiedName~HelperClasses.PhysicalFileSystemAdapters_Tests.PhysicalDirectoryInfoAdapter_MissingDirectoryAndUnsupportedInfo_BranchesBehaveAsExpected" "/ResultsDirectory:coverage\test-results\940\p1-t15" "/Logger:trx;LogFileName=p1-t15.trx" "/Blame:CollectHangDump;TestTimeout=4min;HangDumpType=None" (resolved through vswhere; NAMES-PFS) +EXIT_CODE: 1 (scoped to the mutated vstest run) +ExpectedExitCode: 1 +Output Summary: the mutation compiled and the predicted test failed on the first assertion (`delete`) with the predicted phrase `but no exception was thrown`; total 1, failed 1. + +## Mutated run + +- MUTATED-FILE: UtilitiesCS/HelperClasses/FileSystem/PhysicalDirectoryInfoAdapter.cs line 87 +- PRE-EDIT-LINE: `public void Delete() => _directoryInfo.Delete();` +- MUTATED-LINE: `public void Delete() { }` +- CENSUS-TRANSITION: TOKEN public void Delete() { } = 1 +- Other non-zero census lines: TOKEN using System.Linq; = 1; TOKEN .Create() = 1; TOKEN .Create( = 2; TOKEN .CreateSubdirectory( = 2; TOKEN .EnumerateDirectories( = 3; TOKEN .EnumerateFiles( = 3; TOKEN .EnumerateFileSystemInfos( = 3; TOKEN .GetDirectories( = 3; TOKEN .GetFiles( = 3; TOKEN .GetFileSystemInfos( = 3; TOKEN .GetAccessControl( = 2; TOKEN .GetObjectData( = 1; TOKEN .Refresh() = 1; TOKEN .SetAccessControl( = 1; TOKEN .ToString() = 1; TOKEN .MoveTo( = 1; TOKEN .Parent = 2; TOKEN .Root = 1; TOKEN .Attributes = 2; TOKEN .CreationTime = 4; TOKEN .CreationTimeUtc = 2; TOKEN .LastAccessTime = 4; TOKEN .LastAccessTimeUtc = 2; TOKEN .LastWriteTime = 4; TOKEN .LastWriteTimeUtc = 2; TOKEN _directoryInfo.FullName = 1 +- LINES = 217 +- SHA256 = D1F477B9F8BCB6237743879293811DE8EA0A8E0622E890946FD6A9961CE3329E +- MSBUILD_EXIT_CODE: 0 +- CSC_OUT_LINES: 2 +- PROD_CSC_OUT_LINES: 2 +- ZERO_ERRORS_LINES: 1 +- DLL_ADVANCED: True +- RUNSETTINGS: scripts\vscode\TaskMaster.cli.runsettings with /InIsolation +- FILTER: FullyQualifiedName~HelperClasses.PhysicalFileSystemAdapters_Tests.PhysicalDirectoryInfoAdapter_MissingDirectoryAndUnsupportedInfo_BranchesBehaveAsExpected +- RUNSETTINGS-HASH-NOW: 98EF03A8D3B0EBB2ED7A765E3B5E1B58E774D20202DF2F294C03A7260B9CEF57 (equals RUNSETTINGS-HASH) +- VSTEST_EXIT_CODE: 1 +- TRX_PRESENT: True +- SEQUENCE_FILES: 0 +- COUNTERS total=1 executed=1 passed=0 failed=1 +- RESULT PhysicalDirectoryInfoAdapter_MissingDirectoryAndUnsupportedInfo_BranchesBehaveAsExpected = Failed +- MESSAGE PhysicalDirectoryInfoAdapter_MissingDirectoryAndUnsupportedInfo_BranchesBehaveAsExpected :: Expected a to be thrown, but no exception was thrown. +- PREDICTED-PHRASE: `but no exception was thrown` (present) + +## Revert and confirming run + +- Task: P1-T16 +- Command: git checkout -- UtilitiesCS/HelperClasses/FileSystem/PhysicalDirectoryInfoAdapter.cs; git diff --exit-code HEAD -- UtilitiesCS/HelperClasses/FileSystem/PhysicalDirectoryInfoAdapter.cs; git status --porcelain -- UtilitiesCS/HelperClasses/FileSystem/PhysicalDirectoryInfoAdapter.cs; CMD-CENSUS (same PATH; non-zero TOKEN lines plus the transition token printed); CMD-BUILD with TASKID p1-t16; CMD-VSTEST with the C4 filter, NAMES-PFS and TASKID p1-t16 +- CHECKOUT-EXIT: 0 +- REVERT-DIFF-EXIT: 0 +- PORCELAIN: EMPTY +- CENSUS-TRANSITION: TOKEN public void Delete() { } = 0 +- SHA256: FBAC7002BE4DF5979624F8AE649D52F1044BC40D54EBDA4618C6374EAB9CB242 (anchor PRE-EDIT-HASH-PDA: FBAC7002BE4DF5979624F8AE649D52F1044BC40D54EBDA4618C6374EAB9CB242; 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: 98EF03A8D3B0EBB2ED7A765E3B5E1B58E774D20202DF2F294C03A7260B9CEF57 +- TRX_PRESENT: True; SEQUENCE_FILES: 0 +- COUNTERS total=1 executed=1 passed=1 failed=0 +- RESULT PhysicalDirectoryInfoAdapter_MissingDirectoryAndUnsupportedInfo_BranchesBehaveAsExpected = Passed diff --git a/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/regression-testing/mutation-directory-exists-inverted.md b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/regression-testing/mutation-directory-exists-inverted.md new file mode 100644 index 000000000..08ce5feac --- /dev/null +++ b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/regression-testing/mutation-directory-exists-inverted.md @@ -0,0 +1,49 @@ +# Negative Control C1: Directory Exists Inverted (P1-T9, P1-T10) + +Timestamp: 2026-09-30T07-37 +Task: P1-T9 [expect-fail] +Command: CMD-CENSUS with PATH = UtilitiesCS/HelperClasses/FileSystem/PhysicalDirectoryInfoAdapter.cs; msbuild UtilitiesCS.Test\UtilitiesCS.Test.csproj /t:Build /m /p:Configuration=Debug /p:Platform=AnyCPU (resolved through vswhere, plus /nodeReuse:false; TASKID p1-t9); vstest.console.exe UtilitiesCS.Test\bin\Debug\UtilitiesCS.Test.dll "/Settings:scripts\vscode\TaskMaster.cli.runsettings" /InIsolation "/TestCaseFilter:FullyQualifiedName~HelperClasses.PhysicalFileSystemAdapters_Tests.PhysicalDirectoryInfoAdapter_AccessorsAndNoOpCreation_MirrorOwnedDirectory" "/ResultsDirectory:coverage\test-results\940\p1-t9" "/Logger:trx;LogFileName=p1-t9.trx" "/Blame:CollectHangDump;TestTimeout=4min;HangDumpType=None" (resolved through vswhere; NAMES-PFS) +EXIT_CODE: 1 (scoped to the mutated vstest run) +ExpectedExitCode: 1 +Output Summary: the mutation compiled and the predicted test failed on the predicted assertion with the predicted phrase `but found False`; total 1, failed 1. + +## Mutated run + +- MUTATED-FILE: UtilitiesCS/HelperClasses/FileSystem/PhysicalDirectoryInfoAdapter.cs line 34 +- PRE-EDIT-LINE: `public bool Exists => _directoryInfo.Exists;` +- MUTATED-LINE: `public bool Exists => !_directoryInfo.Exists;` +- CENSUS-TRANSITION: TOKEN !_directoryInfo.Exists = 1 +- Other non-zero census lines: TOKEN using System.Linq; = 1; TOKEN .Create() = 1; TOKEN .Create( = 2; TOKEN .CreateSubdirectory( = 2; TOKEN .EnumerateDirectories( = 3; TOKEN .EnumerateFiles( = 3; TOKEN .EnumerateFileSystemInfos( = 3; TOKEN .GetDirectories( = 3; TOKEN .GetFiles( = 3; TOKEN .GetFileSystemInfos( = 3; TOKEN .GetAccessControl( = 2; TOKEN .GetObjectData( = 1; TOKEN .Refresh() = 1; TOKEN .SetAccessControl( = 1; TOKEN .ToString() = 1; TOKEN .Delete() = 1; TOKEN .MoveTo( = 1; TOKEN .Parent = 2; TOKEN .Root = 1; TOKEN .Attributes = 2; TOKEN .CreationTime = 4; TOKEN .CreationTimeUtc = 2; TOKEN .LastAccessTime = 4; TOKEN .LastAccessTimeUtc = 2; TOKEN .LastWriteTime = 4; TOKEN .LastWriteTimeUtc = 2; TOKEN _directoryInfo.FullName = 1 +- LINES = 217 +- SHA256 = 260648C3FBE87C0E5723B709306B3CD94154D5E1962B3C8E8716AD3E1486608F +- MSBUILD_EXIT_CODE: 0 +- CSC_OUT_LINES: 2 +- PROD_CSC_OUT_LINES: 2 +- ZERO_ERRORS_LINES: 1 +- DLL_ADVANCED: True +- RUNSETTINGS: scripts\vscode\TaskMaster.cli.runsettings with /InIsolation +- FILTER: FullyQualifiedName~HelperClasses.PhysicalFileSystemAdapters_Tests.PhysicalDirectoryInfoAdapter_AccessorsAndNoOpCreation_MirrorOwnedDirectory +- RUNSETTINGS-HASH-NOW: 98EF03A8D3B0EBB2ED7A765E3B5E1B58E774D20202DF2F294C03A7260B9CEF57 (equals RUNSETTINGS-HASH) +- VSTEST_EXIT_CODE: 1 +- TRX_PRESENT: True +- SEQUENCE_FILES: 0 +- COUNTERS total=1 executed=1 passed=0 failed=1 +- RESULT PhysicalDirectoryInfoAdapter_AccessorsAndNoOpCreation_MirrorOwnedDirectory = Failed +- MESSAGE PhysicalDirectoryInfoAdapter_AccessorsAndNoOpCreation_MirrorOwnedDirectory :: Expected adapter.Exists to be True, but found False. +- PREDICTED-PHRASE: `but found False` (present) + +## Revert and confirming run + +- Task: P1-T10 +- Command: git checkout -- UtilitiesCS/HelperClasses/FileSystem/PhysicalDirectoryInfoAdapter.cs; git diff --exit-code HEAD -- UtilitiesCS/HelperClasses/FileSystem/PhysicalDirectoryInfoAdapter.cs; git status --porcelain -- UtilitiesCS/HelperClasses/FileSystem/PhysicalDirectoryInfoAdapter.cs; CMD-CENSUS (same PATH; the payload printed the non-zero TOKEN lines plus the transition token); CMD-BUILD with TASKID p1-t10; CMD-VSTEST with the C1 filter, NAMES-PFS and TASKID p1-t10 +- CHECKOUT-EXIT: 0 +- REVERT-DIFF-EXIT: 0 +- PORCELAIN: EMPTY +- CENSUS-TRANSITION: TOKEN !_directoryInfo.Exists = 0 +- SHA256: FBAC7002BE4DF5979624F8AE649D52F1044BC40D54EBDA4618C6374EAB9CB242 (anchor PRE-EDIT-HASH-PDA: FBAC7002BE4DF5979624F8AE649D52F1044BC40D54EBDA4618C6374EAB9CB242; 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: 98EF03A8D3B0EBB2ED7A765E3B5E1B58E774D20202DF2F294C03A7260B9CEF57 +- TRX_PRESENT: True; SEQUENCE_FILES: 0 +- COUNTERS total=1 executed=1 passed=1 failed=0 +- RESULT PhysicalDirectoryInfoAdapter_AccessorsAndNoOpCreation_MirrorOwnedDirectory = Passed diff --git a/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/regression-testing/mutation-directory-getfiles-empty.md b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/regression-testing/mutation-directory-getfiles-empty.md new file mode 100644 index 000000000..4b5e43d2f --- /dev/null +++ b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/regression-testing/mutation-directory-getfiles-empty.md @@ -0,0 +1,49 @@ +# Negative Control C2: Directory GetFiles Returns Empty (P1-T11, P1-T12) + +Timestamp: 2026-09-30T07-39 +Task: P1-T11 [expect-fail] +Command: CMD-CENSUS with PATH = UtilitiesCS/HelperClasses/FileSystem/PhysicalDirectoryInfoAdapter.cs (the payload printed the non-zero TOKEN lines plus the transition token); msbuild UtilitiesCS.Test\UtilitiesCS.Test.csproj /t:Build /m /p:Configuration=Debug /p:Platform=AnyCPU (resolved through vswhere, plus /nodeReuse:false; TASKID p1-t11); vstest.console.exe UtilitiesCS.Test\bin\Debug\UtilitiesCS.Test.dll "/Settings:scripts\vscode\TaskMaster.cli.runsettings" /InIsolation "/TestCaseFilter:FullyQualifiedName~HelperClasses.PhysicalFileSystemAdapters_Tests.PhysicalDirectoryInfoAdapter_Enumeration_WrapsOwnedDirectoryEntries" "/ResultsDirectory:coverage\test-results\940\p1-t11" "/Logger:trx;LogFileName=p1-t11.trx" "/Blame:CollectHangDump;TestTimeout=4min;HangDumpType=None" (resolved through vswhere; NAMES-PFS) +EXIT_CODE: 1 (scoped to the mutated vstest run) +ExpectedExitCode: 1 +Output Summary: the mutation compiled and the predicted test failed on the first assertion over `files` with the predicted phrase `to contain "UtilitiesCS.Test.dll"`; total 1, failed 1. + +## Mutated run + +- MUTATED-FILE: UtilitiesCS/HelperClasses/FileSystem/PhysicalDirectoryInfoAdapter.cs lines 160 to 161 +- PRE-EDIT-LINES: `public IFileInfo[] GetFiles() =>` / `_directoryInfo.GetFiles().Select(file => new FileInfoWrapper(file)).ToArray();` +- MUTATED-LINE: `public IFileInfo[] GetFiles() => Array.Empty();` +- CENSUS-TRANSITION: TOKEN Array.Empty() = 1 +- Other non-zero census lines: TOKEN using System.Linq; = 1; TOKEN .Create() = 1; TOKEN .Create( = 2; TOKEN .CreateSubdirectory( = 2; TOKEN .EnumerateDirectories( = 3; TOKEN .EnumerateFiles( = 3; TOKEN .EnumerateFileSystemInfos( = 3; TOKEN .GetDirectories( = 3; TOKEN .GetFiles( = 2; TOKEN .GetFileSystemInfos( = 3; TOKEN .GetAccessControl( = 2; TOKEN .GetObjectData( = 1; TOKEN .Refresh() = 1; TOKEN .SetAccessControl( = 1; TOKEN .ToString() = 1; TOKEN .Delete() = 1; TOKEN .MoveTo( = 1; TOKEN .Parent = 2; TOKEN .Root = 1; TOKEN .Attributes = 2; TOKEN .CreationTime = 4; TOKEN .CreationTimeUtc = 2; TOKEN .LastAccessTime = 4; TOKEN .LastAccessTimeUtc = 2; TOKEN .LastWriteTime = 4; TOKEN .LastWriteTimeUtc = 2; TOKEN _directoryInfo.FullName = 1 +- LINES = 216 +- SHA256 = 115AF5E375CC95772134C9EF714EE3BF46727BB47BB9DF9D6FE57B5D605272C0 +- MSBUILD_EXIT_CODE: 0 +- CSC_OUT_LINES: 2 +- PROD_CSC_OUT_LINES: 2 +- ZERO_ERRORS_LINES: 1 +- DLL_ADVANCED: True +- RUNSETTINGS: scripts\vscode\TaskMaster.cli.runsettings with /InIsolation +- FILTER: FullyQualifiedName~HelperClasses.PhysicalFileSystemAdapters_Tests.PhysicalDirectoryInfoAdapter_Enumeration_WrapsOwnedDirectoryEntries +- RUNSETTINGS-HASH-NOW: 98EF03A8D3B0EBB2ED7A765E3B5E1B58E774D20202DF2F294C03A7260B9CEF57 (equals RUNSETTINGS-HASH) +- VSTEST_EXIT_CODE: 1 +- TRX_PRESENT: True +- SEQUENCE_FILES: 0 +- COUNTERS total=1 executed=1 passed=0 failed=1 +- RESULT PhysicalDirectoryInfoAdapter_Enumeration_WrapsOwnedDirectoryEntries = Failed +- MESSAGE PhysicalDirectoryInfoAdapter_Enumeration_WrapsOwnedDirectoryEntries :: Expected files.Select(item => item.Name) {empty} to contain "UtilitiesCS.Test.dll". +- PREDICTED-PHRASE: `to contain "UtilitiesCS.Test.dll"` (present) + +## Revert and confirming run + +- Task: P1-T12 +- Command: git checkout -- UtilitiesCS/HelperClasses/FileSystem/PhysicalDirectoryInfoAdapter.cs; git diff --exit-code HEAD -- UtilitiesCS/HelperClasses/FileSystem/PhysicalDirectoryInfoAdapter.cs; git status --porcelain -- UtilitiesCS/HelperClasses/FileSystem/PhysicalDirectoryInfoAdapter.cs; CMD-CENSUS (same PATH; non-zero TOKEN lines plus the transition token printed); CMD-BUILD with TASKID p1-t12; CMD-VSTEST with the C2 filter, NAMES-PFS and TASKID p1-t12 +- CHECKOUT-EXIT: 0 +- REVERT-DIFF-EXIT: 0 +- PORCELAIN: EMPTY +- CENSUS-TRANSITION: TOKEN Array.Empty() = 0 +- SHA256: FBAC7002BE4DF5979624F8AE649D52F1044BC40D54EBDA4618C6374EAB9CB242 (anchor PRE-EDIT-HASH-PDA: FBAC7002BE4DF5979624F8AE649D52F1044BC40D54EBDA4618C6374EAB9CB242; 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: 98EF03A8D3B0EBB2ED7A765E3B5E1B58E774D20202DF2F294C03A7260B9CEF57 +- TRX_PRESENT: True; SEQUENCE_FILES: 0 +- COUNTERS total=1 executed=1 passed=1 failed=0 +- RESULT PhysicalDirectoryInfoAdapter_Enumeration_WrapsOwnedDirectoryEntries = Passed diff --git a/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/regression-testing/mutation-directory-name-fullname.md b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/regression-testing/mutation-directory-name-fullname.md new file mode 100644 index 000000000..7783b1793 --- /dev/null +++ b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/regression-testing/mutation-directory-name-fullname.md @@ -0,0 +1,58 @@ +# Negative Control C8: Directory Name Returns FullName (P1-T23, P1-T24) + +Timestamp: 2026-09-30T07-55 +Task: P1-T23 [expect-fail] +Command: CMD-CENSUS with PATH = UtilitiesCS/HelperClasses/FileSystem/PhysicalDirectoryInfoAdapter.cs (the payload printed the non-zero TOKEN lines plus the transition token); msbuild UtilitiesCS.Test\UtilitiesCS.Test.csproj /t:Build /m /p:Configuration=Debug /p:Platform=AnyCPU (resolved through vswhere, plus /nodeReuse:false; TASKID p1-t23); vstest.console.exe UtilitiesCS.Test\bin\Debug\UtilitiesCS.Test.dll "/Settings:scripts\vscode\TaskMaster.cli.runsettings" /InIsolation "/TestCaseFilter:FullyQualifiedName~HelperClasses.DirectoryInfoWrapper_Tests.Properties_ShouldMirrorWrappedDirectoryInfo" "/ResultsDirectory:coverage\test-results\940\p1-t23" "/Logger:trx;LogFileName=p1-t23.trx" "/Blame:CollectHangDump;TestTimeout=4min;HangDumpType=None" (resolved through vswhere; NAMES-DIW) +EXIT_CODE: 1 (scoped to the mutated vstest run) +ExpectedExitCode: 1 +Output Summary: the mutation compiled and the predicted test failed on the `wrapper.Name` assertion with a message containing the predicted phrase `"fixture"`; total 1, failed 1. + +## Mutated run + +- MUTATED-FILE: UtilitiesCS/HelperClasses/FileSystem/PhysicalDirectoryInfoAdapter.cs line 64 +- PRE-EDIT-LINE: `public string Name => _directoryInfo.Name;` +- MUTATED-LINE: `public string Name => _directoryInfo.FullName;` +- CENSUS-TRANSITION: TOKEN _directoryInfo.FullName = 2 +- Other non-zero census lines: TOKEN using System.Linq; = 1; TOKEN .Create() = 1; TOKEN .Create( = 2; TOKEN .CreateSubdirectory( = 2; TOKEN .EnumerateDirectories( = 3; TOKEN .EnumerateFiles( = 3; TOKEN .EnumerateFileSystemInfos( = 3; TOKEN .GetDirectories( = 3; TOKEN .GetFiles( = 3; TOKEN .GetFileSystemInfos( = 3; TOKEN .GetAccessControl( = 2; TOKEN .GetObjectData( = 1; TOKEN .Refresh() = 1; TOKEN .SetAccessControl( = 1; TOKEN .ToString() = 1; TOKEN .Delete() = 1; TOKEN .MoveTo( = 1; TOKEN .Parent = 2; TOKEN .Root = 1; TOKEN .Attributes = 2; TOKEN .CreationTime = 4; TOKEN .CreationTimeUtc = 2; TOKEN .LastAccessTime = 4; TOKEN .LastAccessTimeUtc = 2; TOKEN .LastWriteTime = 4; TOKEN .LastWriteTimeUtc = 2 +- LINES = 217 +- SHA256 = 09348328DED34860ED76CF270BBC8728F94D5E69770BABFE0F75BDCA3B08FA08 +- MSBUILD_EXIT_CODE: 0 +- CSC_OUT_LINES: 2 +- PROD_CSC_OUT_LINES: 2 +- ZERO_ERRORS_LINES: 1 +- DLL_ADVANCED: True +- RUNSETTINGS: scripts\vscode\TaskMaster.cli.runsettings with /InIsolation +- FILTER: FullyQualifiedName~HelperClasses.DirectoryInfoWrapper_Tests.Properties_ShouldMirrorWrappedDirectoryInfo +- RUNSETTINGS-HASH-NOW: 98EF03A8D3B0EBB2ED7A765E3B5E1B58E774D20202DF2F294C03A7260B9CEF57 (equals RUNSETTINGS-HASH) +- VSTEST_EXIT_CODE: 1 +- TRX_PRESENT: True +- SEQUENCE_FILES: 0 +- COUNTERS total=1 executed=1 passed=0 failed=1 +- RESULT Properties_ShouldMirrorWrappedDirectoryInfo = Failed +- MESSAGE (verbatim; the rooted literal is the test's fixture constant, not a host path): + +``` +MESSAGE Properties_ShouldMirrorWrappedDirectoryInfo :: Expected wrapper.Name to be a match with the expectation, but it differs at index 0: + (actual) + "C:\Repo\fixture" + "fixture" + (expected) +``` + +- PREDICTED-PHRASE: `"fixture"` (present) + +## Revert and confirming run + +- Task: P1-T24 +- Command: git checkout -- UtilitiesCS/HelperClasses/FileSystem/PhysicalDirectoryInfoAdapter.cs; git diff --exit-code HEAD -- UtilitiesCS/HelperClasses/FileSystem/PhysicalDirectoryInfoAdapter.cs; git status --porcelain -- UtilitiesCS/HelperClasses/FileSystem/PhysicalDirectoryInfoAdapter.cs; CMD-CENSUS (same PATH; non-zero TOKEN lines plus the transition token printed); CMD-BUILD with TASKID p1-t24; CMD-VSTEST with the C8 filter, NAMES-DIW and TASKID p1-t24 +- CHECKOUT-EXIT: 0 +- REVERT-DIFF-EXIT: 0 +- PORCELAIN: EMPTY +- CENSUS-TRANSITION: TOKEN _directoryInfo.FullName = 1 +- SHA256: FBAC7002BE4DF5979624F8AE649D52F1044BC40D54EBDA4618C6374EAB9CB242 (anchor PRE-EDIT-HASH-PDA: FBAC7002BE4DF5979624F8AE649D52F1044BC40D54EBDA4618C6374EAB9CB242; 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: 98EF03A8D3B0EBB2ED7A765E3B5E1B58E774D20202DF2F294C03A7260B9CEF57 +- TRX_PRESENT: True; SEQUENCE_FILES: 0 +- COUNTERS total=1 executed=1 passed=1 failed=0 +- RESULT Properties_ShouldMirrorWrappedDirectoryInfo = Passed diff --git a/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/regression-testing/mutation-file-isreadonly-setter-noop.md b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/regression-testing/mutation-file-isreadonly-setter-noop.md new file mode 100644 index 000000000..4b2f2292a --- /dev/null +++ b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/regression-testing/mutation-file-isreadonly-setter-noop.md @@ -0,0 +1,49 @@ +# Negative Control C6: File IsReadOnly Setter No-Op (P1-T19, P1-T20) + +Timestamp: 2026-09-30T07-50 +Task: P1-T19 [expect-fail] +Command: CMD-CENSUS with PATH = UtilitiesCS/HelperClasses/FileSystem/PhysicalFileInfoAdapter.cs (the payload printed the non-zero TOKEN lines plus the transition token); msbuild UtilitiesCS.Test\UtilitiesCS.Test.csproj /t:Build /m /p:Configuration=Debug /p:Platform=AnyCPU (resolved through vswhere, plus /nodeReuse:false; TASKID p1-t19); vstest.console.exe UtilitiesCS.Test\bin\Debug\UtilitiesCS.Test.dll "/Settings:scripts\vscode\TaskMaster.cli.runsettings" /InIsolation "/TestCaseFilter:FullyQualifiedName~HelperClasses.PhysicalFileSystemAdapters_Tests.PhysicalFileInfoAdapter_SettersOnMissingFile_ThrowWithoutCreatingFiles" "/ResultsDirectory:coverage\test-results\940\p1-t19" "/Logger:trx;LogFileName=p1-t19.trx" "/Blame:CollectHangDump;TestTimeout=4min;HangDumpType=None" (resolved through vswhere; NAMES-PFS) +EXIT_CODE: 1 (scoped to the mutated vstest run) +ExpectedExitCode: 1 +Output Summary: the mutation compiled and the predicted test failed on the first assertion (`setIsReadOnly`) with the predicted phrase `but no exception was thrown`; total 1, failed 1. + +## Mutated run + +- MUTATED-FILE: UtilitiesCS/HelperClasses/FileSystem/PhysicalFileInfoAdapter.cs line 113 +- PRE-EDIT-LINE: `set => _fileInfo.IsReadOnly = value;` +- MUTATED-LINE: `set { }` +- CENSUS-TRANSITION: TOKEN set { } = 1 +- Other non-zero census lines: TOKEN FileShare.None = 1; TOKEN .Create() = 1; TOKEN .Create( = 1; TOKEN .GetAccessControl( = 2; TOKEN .GetObjectData( = 1; TOKEN .Refresh() = 1; TOKEN .SetAccessControl( = 1; TOKEN .ToString() = 1; TOKEN .Delete() = 1; TOKEN .MoveTo( = 1; TOKEN .CopyTo( = 2; TOKEN .Replace( = 2; TOKEN .Open( = 1; TOKEN .OpenRead() = 1; TOKEN .OpenText() = 1; TOKEN .Length = 1; TOKEN .IsReadOnly = 1; TOKEN .Attributes = 2; TOKEN .CreationTime = 4; TOKEN .CreationTimeUtc = 2; TOKEN .LastAccessTime = 4; TOKEN .LastAccessTimeUtc = 2; TOKEN .LastWriteTime = 4; TOKEN .LastWriteTimeUtc = 2 +- LINES = 183 +- SHA256 = A7FFADE58D2A86306678B63BECC8AEFAB4CF35FC2444FF45DA7E0D0F1692E1F3 +- MSBUILD_EXIT_CODE: 0 +- CSC_OUT_LINES: 2 +- PROD_CSC_OUT_LINES: 2 +- ZERO_ERRORS_LINES: 1 +- DLL_ADVANCED: True +- RUNSETTINGS: scripts\vscode\TaskMaster.cli.runsettings with /InIsolation +- FILTER: FullyQualifiedName~HelperClasses.PhysicalFileSystemAdapters_Tests.PhysicalFileInfoAdapter_SettersOnMissingFile_ThrowWithoutCreatingFiles +- RUNSETTINGS-HASH-NOW: 98EF03A8D3B0EBB2ED7A765E3B5E1B58E774D20202DF2F294C03A7260B9CEF57 (equals RUNSETTINGS-HASH) +- VSTEST_EXIT_CODE: 1 +- TRX_PRESENT: True +- SEQUENCE_FILES: 0 +- COUNTERS total=1 executed=1 passed=0 failed=1 +- RESULT PhysicalFileInfoAdapter_SettersOnMissingFile_ThrowWithoutCreatingFiles = Failed +- MESSAGE PhysicalFileInfoAdapter_SettersOnMissingFile_ThrowWithoutCreatingFiles :: Expected a to be thrown, but no exception was thrown. +- PREDICTED-PHRASE: `but no exception was thrown` (present) + +## Revert and confirming run + +- Task: P1-T20 +- Command: git checkout -- UtilitiesCS/HelperClasses/FileSystem/PhysicalFileInfoAdapter.cs; git diff --exit-code HEAD -- UtilitiesCS/HelperClasses/FileSystem/PhysicalFileInfoAdapter.cs; git status --porcelain -- UtilitiesCS/HelperClasses/FileSystem/PhysicalFileInfoAdapter.cs; CMD-CENSUS (same PATH; non-zero TOKEN lines plus the transition token printed); CMD-BUILD with TASKID p1-t20; CMD-VSTEST with the C6 filter, NAMES-PFS and TASKID p1-t20 +- CHECKOUT-EXIT: 0 +- REVERT-DIFF-EXIT: 0 +- PORCELAIN: EMPTY +- CENSUS-TRANSITION: TOKEN set { } = 0 +- SHA256: 0337E5C1FF7A2E3FF5E5D67D383E68583838AFB5E5BA999A30502CF24333D948 (anchor PRE-EDIT-HASH-PFA: 0337E5C1FF7A2E3FF5E5D67D383E68583838AFB5E5BA999A30502CF24333D948; 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: 98EF03A8D3B0EBB2ED7A765E3B5E1B58E774D20202DF2F294C03A7260B9CEF57 +- TRX_PRESENT: True; SEQUENCE_FILES: 0 +- COUNTERS total=1 executed=1 passed=1 failed=0 +- RESULT PhysicalFileInfoAdapter_SettersOnMissingFile_ThrowWithoutCreatingFiles = Passed diff --git a/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/regression-testing/mutation-file-length-zero.md b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/regression-testing/mutation-file-length-zero.md new file mode 100644 index 000000000..cbfc09120 --- /dev/null +++ b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/regression-testing/mutation-file-length-zero.md @@ -0,0 +1,49 @@ +# Negative Control C5: File Length Returns Zero (P1-T17, P1-T18) + +Timestamp: 2026-09-30T07-47 +Task: P1-T17 [expect-fail] +Command: CMD-CENSUS with PATH = UtilitiesCS/HelperClasses/FileSystem/PhysicalFileInfoAdapter.cs (the payload printed the non-zero TOKEN lines plus the transition token); msbuild UtilitiesCS.Test\UtilitiesCS.Test.csproj /t:Build /m /p:Configuration=Debug /p:Platform=AnyCPU (resolved through vswhere, plus /nodeReuse:false; TASKID p1-t17); vstest.console.exe UtilitiesCS.Test\bin\Debug\UtilitiesCS.Test.dll "/Settings:scripts\vscode\TaskMaster.cli.runsettings" /InIsolation "/TestCaseFilter:FullyQualifiedName~HelperClasses.PhysicalFileSystemAdapters_Tests.PhysicalFileInfoAdapter_PropertiesStreamsAndAccessors_MirrorFileInfo" "/ResultsDirectory:coverage\test-results\940\p1-t17" "/Logger:trx;LogFileName=p1-t17.trx" "/Blame:CollectHangDump;TestTimeout=4min;HangDumpType=None" (resolved through vswhere; NAMES-PFS) +EXIT_CODE: 1 (scoped to the mutated vstest run) +ExpectedExitCode: 1 +Output Summary: the mutation compiled and the predicted test failed on the `adapter.Length` assertion with the predicted phrase `to be greater than`; the message does not contain `because GetObjectData adds values`; total 1, failed 1. + +## Mutated run + +- MUTATED-FILE: UtilitiesCS/HelperClasses/FileSystem/PhysicalFileInfoAdapter.cs line 116 +- PRE-EDIT-LINE: `public long Length => _fileInfo.Length;` +- MUTATED-LINE: `public long Length => 0;` +- CENSUS-TRANSITION: TOKEN public long Length => 0; = 1 +- Other non-zero census lines: TOKEN FileShare.None = 1; TOKEN .Create() = 1; TOKEN .Create( = 1; TOKEN .GetAccessControl( = 2; TOKEN .GetObjectData( = 1; TOKEN .Refresh() = 1; TOKEN .SetAccessControl( = 1; TOKEN .ToString() = 1; TOKEN .Delete() = 1; TOKEN .MoveTo( = 1; TOKEN .CopyTo( = 2; TOKEN .Replace( = 2; TOKEN .Open( = 1; TOKEN .OpenRead() = 1; TOKEN .OpenText() = 1; TOKEN .IsReadOnly = 2; TOKEN .Attributes = 2; TOKEN .CreationTime = 4; TOKEN .CreationTimeUtc = 2; TOKEN .LastAccessTime = 4; TOKEN .LastAccessTimeUtc = 2; TOKEN .LastWriteTime = 4; TOKEN .LastWriteTimeUtc = 2 +- LINES = 183 +- SHA256 = 617AB3B49C61E5B46DCEA4FA0FD43E5A7084C10135255D5C650C7D6B0B8DF379 +- MSBUILD_EXIT_CODE: 0 +- CSC_OUT_LINES: 2 +- PROD_CSC_OUT_LINES: 2 +- ZERO_ERRORS_LINES: 1 +- DLL_ADVANCED: True +- RUNSETTINGS: scripts\vscode\TaskMaster.cli.runsettings with /InIsolation +- FILTER: FullyQualifiedName~HelperClasses.PhysicalFileSystemAdapters_Tests.PhysicalFileInfoAdapter_PropertiesStreamsAndAccessors_MirrorFileInfo +- RUNSETTINGS-HASH-NOW: 98EF03A8D3B0EBB2ED7A765E3B5E1B58E774D20202DF2F294C03A7260B9CEF57 (equals RUNSETTINGS-HASH) +- VSTEST_EXIT_CODE: 1 +- TRX_PRESENT: True +- SEQUENCE_FILES: 0 +- COUNTERS total=1 executed=1 passed=0 failed=1 +- RESULT PhysicalFileInfoAdapter_PropertiesStreamsAndAccessors_MirrorFileInfo = Failed +- MESSAGE PhysicalFileInfoAdapter_PropertiesStreamsAndAccessors_MirrorFileInfo :: Expected adapter.Length to be greater than 0L, but found 0L. +- PREDICTED-PHRASE: `to be greater than` (present; `because GetObjectData adds values` absent) + +## Revert and confirming run + +- Task: P1-T18 +- Command: git checkout -- UtilitiesCS/HelperClasses/FileSystem/PhysicalFileInfoAdapter.cs; git diff --exit-code HEAD -- UtilitiesCS/HelperClasses/FileSystem/PhysicalFileInfoAdapter.cs; git status --porcelain -- UtilitiesCS/HelperClasses/FileSystem/PhysicalFileInfoAdapter.cs; CMD-CENSUS (same PATH; non-zero TOKEN lines plus the transition token printed); CMD-BUILD with TASKID p1-t18; CMD-VSTEST with the C5 filter, NAMES-PFS and TASKID p1-t18 +- CHECKOUT-EXIT: 0 +- REVERT-DIFF-EXIT: 0 +- PORCELAIN: EMPTY +- CENSUS-TRANSITION: TOKEN public long Length => 0; = 0 +- SHA256: 0337E5C1FF7A2E3FF5E5D67D383E68583838AFB5E5BA999A30502CF24333D948 (anchor PRE-EDIT-HASH-PFA: 0337E5C1FF7A2E3FF5E5D67D383E68583838AFB5E5BA999A30502CF24333D948; 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: 98EF03A8D3B0EBB2ED7A765E3B5E1B58E774D20202DF2F294C03A7260B9CEF57 +- TRX_PRESENT: True; SEQUENCE_FILES: 0 +- COUNTERS total=1 executed=1 passed=1 failed=0 +- RESULT PhysicalFileInfoAdapter_PropertiesStreamsAndAccessors_MirrorFileInfo = Passed diff --git a/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/regression-testing/mutation-file-moveto-noop.md b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/regression-testing/mutation-file-moveto-noop.md new file mode 100644 index 000000000..8f8dc040a --- /dev/null +++ b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/regression-testing/mutation-file-moveto-noop.md @@ -0,0 +1,49 @@ +# Negative Control C7: File MoveTo No-Op (P1-T21, P1-T22) + +Timestamp: 2026-09-30T07-52 +Task: P1-T21 [expect-fail] +Command: CMD-CENSUS with PATH = UtilitiesCS/HelperClasses/FileSystem/PhysicalFileInfoAdapter.cs (the payload printed the non-zero TOKEN lines plus the transition token); msbuild UtilitiesCS.Test\UtilitiesCS.Test.csproj /t:Build /m /p:Configuration=Debug /p:Platform=AnyCPU (resolved through vswhere, plus /nodeReuse:false; TASKID p1-t21); vstest.console.exe UtilitiesCS.Test\bin\Debug\UtilitiesCS.Test.dll "/Settings:scripts\vscode\TaskMaster.cli.runsettings" /InIsolation "/TestCaseFilter:FullyQualifiedName~HelperClasses.PhysicalFileSystemAdapters_Tests.PhysicalFileInfoAdapter_MissingFileBranches_ThrowOrNoOpWithoutCreatingFiles" "/ResultsDirectory:coverage\test-results\940\p1-t21" "/Logger:trx;LogFileName=p1-t21.trx" "/Blame:CollectHangDump;TestTimeout=4min;HangDumpType=None" (resolved through vswhere; NAMES-PFS) +EXIT_CODE: 1 (scoped to the mutated vstest run) +ExpectedExitCode: 1 +Output Summary: the mutation compiled and the predicted test failed with a `Throw()` message (the `move` assertion is the only one whose member the mutation changed; `delete`, `copy` and `copyOverwrite` before it exercise unmutated members) with the predicted phrase `but no exception was thrown`; total 1, failed 1. + +## Mutated run + +- MUTATED-FILE: UtilitiesCS/HelperClasses/FileSystem/PhysicalFileInfoAdapter.cs line 144 +- PRE-EDIT-LINE: `public void MoveTo(string destFileName) => _fileInfo.MoveTo(destFileName);` +- MUTATED-LINE: `public void MoveTo(string destFileName) { }` +- CENSUS-TRANSITION: TOKEN public void MoveTo(string destFileName) { } = 1 +- Other non-zero census lines: TOKEN FileShare.None = 1; TOKEN .Create() = 1; TOKEN .Create( = 1; TOKEN .GetAccessControl( = 2; TOKEN .GetObjectData( = 1; TOKEN .Refresh() = 1; TOKEN .SetAccessControl( = 1; TOKEN .ToString() = 1; TOKEN .Delete() = 1; TOKEN .CopyTo( = 2; TOKEN .Replace( = 2; TOKEN .Open( = 1; TOKEN .OpenRead() = 1; TOKEN .OpenText() = 1; TOKEN .Length = 1; TOKEN .IsReadOnly = 2; TOKEN .Attributes = 2; TOKEN .CreationTime = 4; TOKEN .CreationTimeUtc = 2; TOKEN .LastAccessTime = 4; TOKEN .LastAccessTimeUtc = 2; TOKEN .LastWriteTime = 4; TOKEN .LastWriteTimeUtc = 2 +- LINES = 183 +- SHA256 = B8C13303614EF62FC24DAE084828491AC779B6FC9760396ECDE4CC9818F5E2E7 +- MSBUILD_EXIT_CODE: 0 +- CSC_OUT_LINES: 2 +- PROD_CSC_OUT_LINES: 2 +- ZERO_ERRORS_LINES: 1 +- DLL_ADVANCED: True +- RUNSETTINGS: scripts\vscode\TaskMaster.cli.runsettings with /InIsolation +- FILTER: FullyQualifiedName~HelperClasses.PhysicalFileSystemAdapters_Tests.PhysicalFileInfoAdapter_MissingFileBranches_ThrowOrNoOpWithoutCreatingFiles +- RUNSETTINGS-HASH-NOW: 98EF03A8D3B0EBB2ED7A765E3B5E1B58E774D20202DF2F294C03A7260B9CEF57 (equals RUNSETTINGS-HASH) +- VSTEST_EXIT_CODE: 1 +- TRX_PRESENT: True +- SEQUENCE_FILES: 0 +- COUNTERS total=1 executed=1 passed=0 failed=1 +- RESULT PhysicalFileInfoAdapter_MissingFileBranches_ThrowOrNoOpWithoutCreatingFiles = Failed +- MESSAGE PhysicalFileInfoAdapter_MissingFileBranches_ThrowOrNoOpWithoutCreatingFiles :: Expected a to be thrown, but no exception was thrown. +- PREDICTED-PHRASE: `but no exception was thrown` (present) + +## Revert and confirming run + +- Task: P1-T22 +- Command: git checkout -- UtilitiesCS/HelperClasses/FileSystem/PhysicalFileInfoAdapter.cs; git diff --exit-code HEAD -- UtilitiesCS/HelperClasses/FileSystem/PhysicalFileInfoAdapter.cs; git status --porcelain -- UtilitiesCS/HelperClasses/FileSystem/PhysicalFileInfoAdapter.cs; CMD-CENSUS (same PATH; non-zero TOKEN lines plus the transition token printed); CMD-BUILD with TASKID p1-t22; CMD-VSTEST with the C7 filter, NAMES-PFS and TASKID p1-t22 +- CHECKOUT-EXIT: 0 +- REVERT-DIFF-EXIT: 0 +- PORCELAIN: EMPTY +- CENSUS-TRANSITION: TOKEN public void MoveTo(string destFileName) { } = 0 +- SHA256: 0337E5C1FF7A2E3FF5E5D67D383E68583838AFB5E5BA999A30502CF24333D948 (anchor PRE-EDIT-HASH-PFA: 0337E5C1FF7A2E3FF5E5D67D383E68583838AFB5E5BA999A30502CF24333D948; 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: 98EF03A8D3B0EBB2ED7A765E3B5E1B58E774D20202DF2F294C03A7260B9CEF57 +- TRX_PRESENT: True; SEQUENCE_FILES: 0 +- COUNTERS total=1 executed=1 passed=1 failed=0 +- RESULT PhysicalFileInfoAdapter_MissingFileBranches_ThrowOrNoOpWithoutCreatingFiles = Passed diff --git a/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/regression-testing/mutation-wrapper-enumeratefilesysteminfos-empty.md b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/regression-testing/mutation-wrapper-enumeratefilesysteminfos-empty.md new file mode 100644 index 000000000..4608fc568 --- /dev/null +++ b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/regression-testing/mutation-wrapper-enumeratefilesysteminfos-empty.md @@ -0,0 +1,49 @@ +# Negative Control C10: Wrapper EnumerateFileSystemInfos Returns Empty (P1-T27, P1-T28) + +Timestamp: 2026-09-30T08-00 +Task: P1-T27 [expect-fail] +Command: CMD-CENSUS with PATH = UtilitiesCS/HelperClasses/FileSystem/DirectoryInfoWrapper.cs (the payload printed the non-zero TOKEN lines plus the transition token); msbuild UtilitiesCS.Test\UtilitiesCS.Test.csproj /t:Build /m /p:Configuration=Debug /p:Platform=AnyCPU (resolved through vswhere, plus /nodeReuse:false; TASKID p1-t27); vstest.console.exe UtilitiesCS.Test\bin\Debug\UtilitiesCS.Test.dll "/Settings:scripts\vscode\TaskMaster.cli.runsettings" /InIsolation "/TestCaseFilter:FullyQualifiedName~HelperClasses.DirectoryInfoWrapper_Tests.EnumerateFileSystemInfos_ShouldWrapDirectoriesAndFiles" "/ResultsDirectory:coverage\test-results\940\p1-t27" "/Logger:trx;LogFileName=p1-t27.trx" "/Blame:CollectHangDump;TestTimeout=4min;HangDumpType=None" (resolved through vswhere; NAMES-DIW) +EXIT_CODE: 1 (scoped to the mutated vstest run) +ExpectedExitCode: 1 +Output Summary: the mutation compiled and the predicted test failed on the first assertion over `fileSystemInfos` with the predicted phrase `to contain "UtilitiesCS.Test.dll"`; total 1, failed 1. + +## Mutated run + +- MUTATED-FILE: UtilitiesCS/HelperClasses/FileSystem/DirectoryInfoWrapper.cs line 145 +- PRE-EDIT-LINE: `return _directoryInfo.EnumerateFileSystemInfos();` +- MUTATED-LINE: `return Enumerable.Empty();` +- CENSUS-TRANSITION: TOKEN Enumerable.Empty() = 1 +- Other non-zero census lines: TOKEN using System.Linq; = 1; TOKEN .Create() = 1; TOKEN .Create( = 2; TOKEN .CreateSubdirectory( = 2; TOKEN .EnumerateDirectories( = 3; TOKEN .EnumerateFiles( = 3; TOKEN .EnumerateFileSystemInfos( = 2; TOKEN .GetDirectories( = 3; TOKEN .GetFiles( = 3; TOKEN .GetFileSystemInfos( = 3; TOKEN .GetAccessControl( = 2; TOKEN .GetObjectData( = 1; TOKEN .Refresh() = 1; TOKEN .SetAccessControl( = 1; TOKEN .ToString() = 1; TOKEN .Delete() = 1; TOKEN .MoveTo( = 1; TOKEN .Parent = 1; TOKEN .Root = 1; TOKEN .Attributes = 2; TOKEN .CreationTime = 4; TOKEN .CreationTimeUtc = 2; TOKEN .LastAccessTime = 4; TOKEN .LastAccessTimeUtc = 2; TOKEN .LastWriteTime = 4; TOKEN .LastWriteTimeUtc = 2; TOKEN _directoryInfo.FullName = 1 +- LINES = 246 +- SHA256 = 376B759C1FC41D4EE6373EBA8026AB8E75520419D590F9CFC389F80A41514FF2 +- MSBUILD_EXIT_CODE: 0 +- CSC_OUT_LINES: 2 +- PROD_CSC_OUT_LINES: 2 +- ZERO_ERRORS_LINES: 1 +- DLL_ADVANCED: True +- RUNSETTINGS: scripts\vscode\TaskMaster.cli.runsettings with /InIsolation +- FILTER: FullyQualifiedName~HelperClasses.DirectoryInfoWrapper_Tests.EnumerateFileSystemInfos_ShouldWrapDirectoriesAndFiles +- RUNSETTINGS-HASH-NOW: 98EF03A8D3B0EBB2ED7A765E3B5E1B58E774D20202DF2F294C03A7260B9CEF57 (equals RUNSETTINGS-HASH) +- VSTEST_EXIT_CODE: 1 +- TRX_PRESENT: True +- SEQUENCE_FILES: 0 +- COUNTERS total=1 executed=1 passed=0 failed=1 +- RESULT EnumerateFileSystemInfos_ShouldWrapDirectoriesAndFiles = Failed +- MESSAGE EnumerateFileSystemInfos_ShouldWrapDirectoriesAndFiles :: Expected fileSystemInfos.OfType().Select(item => item.Name) {empty} to contain "UtilitiesCS.Test.dll". +- PREDICTED-PHRASE: `to contain "UtilitiesCS.Test.dll"` (present) + +## Revert and confirming run + +- Task: P1-T28 +- Command: git checkout -- UtilitiesCS/HelperClasses/FileSystem/DirectoryInfoWrapper.cs; git diff --exit-code HEAD -- UtilitiesCS/HelperClasses/FileSystem/DirectoryInfoWrapper.cs; git status --porcelain -- UtilitiesCS/HelperClasses/FileSystem/DirectoryInfoWrapper.cs; CMD-CENSUS (same PATH; non-zero TOKEN lines plus the transition token printed); CMD-BUILD with TASKID p1-t28; CMD-VSTEST with the C10 filter, NAMES-DIW and TASKID p1-t28 +- CHECKOUT-EXIT: 0 +- REVERT-DIFF-EXIT: 0 +- PORCELAIN: EMPTY +- CENSUS-TRANSITION: TOKEN Enumerable.Empty() = 0 +- SHA256: F77616271ABB36C9133ADE496550703B391B077AB68C8F5DC652AB73FD177DC3 (anchor PRE-EDIT-HASH-DIWP: F77616271ABB36C9133ADE496550703B391B077AB68C8F5DC652AB73FD177DC3; 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: 98EF03A8D3B0EBB2ED7A765E3B5E1B58E774D20202DF2F294C03A7260B9CEF57 +- TRX_PRESENT: True; SEQUENCE_FILES: 0 +- COUNTERS total=1 executed=1 passed=1 failed=0 +- RESULT EnumerateFileSystemInfos_ShouldWrapDirectoriesAndFiles = Passed diff --git a/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/regression-testing/mutation-wrapper-getfiles-empty.md b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/regression-testing/mutation-wrapper-getfiles-empty.md new file mode 100644 index 000000000..f0a092af5 --- /dev/null +++ b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/regression-testing/mutation-wrapper-getfiles-empty.md @@ -0,0 +1,49 @@ +# Negative Control C9: Wrapper GetFiles Returns Empty (P1-T25, P1-T26) + +Timestamp: 2026-09-30T07-57 +Task: P1-T25 [expect-fail] +Command: CMD-CENSUS with PATH = UtilitiesCS/HelperClasses/FileSystem/DirectoryInfoWrapper.cs (the payload printed the non-zero TOKEN lines plus the transition token); msbuild UtilitiesCS.Test\UtilitiesCS.Test.csproj /t:Build /m /p:Configuration=Debug /p:Platform=AnyCPU (resolved through vswhere, plus /nodeReuse:false; TASKID p1-t25); vstest.console.exe UtilitiesCS.Test\bin\Debug\UtilitiesCS.Test.dll "/Settings:scripts\vscode\TaskMaster.cli.runsettings" /InIsolation "/TestCaseFilter:FullyQualifiedName~HelperClasses.DirectoryInfoWrapper_Tests.GetDirectoriesAndGetFiles_ShouldReturnWrappedEntries" "/ResultsDirectory:coverage\test-results\940\p1-t25" "/Logger:trx;LogFileName=p1-t25.trx" "/Blame:CollectHangDump;TestTimeout=4min;HangDumpType=None" (resolved through vswhere; NAMES-DIW) +EXIT_CODE: 1 (scoped to the mutated vstest run) +ExpectedExitCode: 1 +Output Summary: the mutation compiled and the predicted test failed on the first assertion (`files.Should().NotBeEmpty()`) with the predicted phrase `not to be empty`; total 1, failed 1. + +## Mutated run + +- MUTATED-FILE: UtilitiesCS/HelperClasses/FileSystem/DirectoryInfoWrapper.cs line 188 +- PRE-EDIT-LINE: `return _directoryInfo.GetFiles();` +- MUTATED-LINE: `return Array.Empty();` +- CENSUS-TRANSITION: TOKEN Array.Empty() = 1 +- Other non-zero census lines: TOKEN using System.Linq; = 1; TOKEN .Create() = 1; TOKEN .Create( = 2; TOKEN .CreateSubdirectory( = 2; TOKEN .EnumerateDirectories( = 3; TOKEN .EnumerateFiles( = 3; TOKEN .EnumerateFileSystemInfos( = 3; TOKEN .GetDirectories( = 3; TOKEN .GetFiles( = 2; TOKEN .GetFileSystemInfos( = 3; TOKEN .GetAccessControl( = 2; TOKEN .GetObjectData( = 1; TOKEN .Refresh() = 1; TOKEN .SetAccessControl( = 1; TOKEN .ToString() = 1; TOKEN .Delete() = 1; TOKEN .MoveTo( = 1; TOKEN .Parent = 1; TOKEN .Root = 1; TOKEN .Attributes = 2; TOKEN .CreationTime = 4; TOKEN .CreationTimeUtc = 2; TOKEN .LastAccessTime = 4; TOKEN .LastAccessTimeUtc = 2; TOKEN .LastWriteTime = 4; TOKEN .LastWriteTimeUtc = 2; TOKEN _directoryInfo.FullName = 1 +- LINES = 246 +- SHA256 = C0BCD143862607319B10DCF00E006264170BD3212D123C9E7F65D9800C4BDE51 +- MSBUILD_EXIT_CODE: 0 +- CSC_OUT_LINES: 2 +- PROD_CSC_OUT_LINES: 2 +- ZERO_ERRORS_LINES: 1 +- DLL_ADVANCED: True +- RUNSETTINGS: scripts\vscode\TaskMaster.cli.runsettings with /InIsolation +- FILTER: FullyQualifiedName~HelperClasses.DirectoryInfoWrapper_Tests.GetDirectoriesAndGetFiles_ShouldReturnWrappedEntries +- RUNSETTINGS-HASH-NOW: 98EF03A8D3B0EBB2ED7A765E3B5E1B58E774D20202DF2F294C03A7260B9CEF57 (equals RUNSETTINGS-HASH) +- VSTEST_EXIT_CODE: 1 +- TRX_PRESENT: True +- SEQUENCE_FILES: 0 +- COUNTERS total=1 executed=1 passed=0 failed=1 +- RESULT GetDirectoriesAndGetFiles_ShouldReturnWrappedEntries = Failed +- MESSAGE GetDirectoriesAndGetFiles_ShouldReturnWrappedEntries :: Expected files not to be empty. +- PREDICTED-PHRASE: `not to be empty` (present) + +## Revert and confirming run + +- Task: P1-T26 +- Command: git checkout -- UtilitiesCS/HelperClasses/FileSystem/DirectoryInfoWrapper.cs; git diff --exit-code HEAD -- UtilitiesCS/HelperClasses/FileSystem/DirectoryInfoWrapper.cs; git status --porcelain -- UtilitiesCS/HelperClasses/FileSystem/DirectoryInfoWrapper.cs; CMD-CENSUS (same PATH; non-zero TOKEN lines plus the transition token printed); CMD-BUILD with TASKID p1-t26; CMD-VSTEST with the C9 filter, NAMES-DIW and TASKID p1-t26 +- CHECKOUT-EXIT: 0 +- REVERT-DIFF-EXIT: 0 +- PORCELAIN: EMPTY +- CENSUS-TRANSITION: TOKEN Array.Empty() = 0 +- SHA256: F77616271ABB36C9133ADE496550703B391B077AB68C8F5DC652AB73FD177DC3 (anchor PRE-EDIT-HASH-DIWP: F77616271ABB36C9133ADE496550703B391B077AB68C8F5DC652AB73FD177DC3; 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: 98EF03A8D3B0EBB2ED7A765E3B5E1B58E774D20202DF2F294C03A7260B9CEF57 +- TRX_PRESENT: True; SEQUENCE_FILES: 0 +- COUNTERS total=1 executed=1 passed=1 failed=0 +- RESULT GetDirectoriesAndGetFiles_ShouldReturnWrappedEntries = Passed diff --git a/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/regression-testing/mutation-wrapper-tostring-empty.md b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/regression-testing/mutation-wrapper-tostring-empty.md new file mode 100644 index 000000000..21c473f7d --- /dev/null +++ b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/regression-testing/mutation-wrapper-tostring-empty.md @@ -0,0 +1,49 @@ +# Negative Control C11: Wrapper ToString Returns Empty (P1-T29, P1-T30) + +Timestamp: 2026-09-30T08-03 +Task: P1-T29 [expect-fail] +Command: CMD-CENSUS with PATH = UtilitiesCS/HelperClasses/FileSystem/DirectoryInfoWrapper.cs (the payload printed the non-zero TOKEN lines plus the transition token); msbuild UtilitiesCS.Test\UtilitiesCS.Test.csproj /t:Build /m /p:Configuration=Debug /p:Platform=AnyCPU (resolved through vswhere, plus /nodeReuse:false; TASKID p1-t29); vstest.console.exe UtilitiesCS.Test\bin\Debug\UtilitiesCS.Test.dll "/Settings:scripts\vscode\TaskMaster.cli.runsettings" /InIsolation "/TestCaseFilter:FullyQualifiedName~HelperClasses.DirectoryInfoWrapper_Tests.ToString_ShouldDelegateToWrappedDirectoryInfo" "/ResultsDirectory:coverage\test-results\940\p1-t29" "/Logger:trx;LogFileName=p1-t29.trx" "/Blame:CollectHangDump;TestTimeout=4min;HangDumpType=None" (resolved through vswhere; NAMES-DIW) +EXIT_CODE: 1 (scoped to the mutated vstest run) +ExpectedExitCode: 1 +Output Summary: the mutation compiled and the predicted test failed on its only assertion with a message containing the predicted phrase `"C:\Repo\fixture"`; total 1, failed 1. + +## Mutated run + +- MUTATED-FILE: UtilitiesCS/HelperClasses/FileSystem/DirectoryInfoWrapper.cs line 228 +- PRE-EDIT-LINE: `return _directoryInfo.ToString();` +- MUTATED-LINE: `return string.Empty;` +- CENSUS-TRANSITION: TOKEN return string.Empty; = 1 +- Other non-zero census lines: TOKEN using System.Linq; = 1; TOKEN .Create() = 1; TOKEN .Create( = 2; TOKEN .CreateSubdirectory( = 2; TOKEN .EnumerateDirectories( = 3; TOKEN .EnumerateFiles( = 3; TOKEN .EnumerateFileSystemInfos( = 3; TOKEN .GetDirectories( = 3; TOKEN .GetFiles( = 3; TOKEN .GetFileSystemInfos( = 3; TOKEN .GetAccessControl( = 2; TOKEN .GetObjectData( = 1; TOKEN .Refresh() = 1; TOKEN .SetAccessControl( = 1; TOKEN .Delete() = 1; TOKEN .MoveTo( = 1; TOKEN .Parent = 1; TOKEN .Root = 1; TOKEN .Attributes = 2; TOKEN .CreationTime = 4; TOKEN .CreationTimeUtc = 2; TOKEN .LastAccessTime = 4; TOKEN .LastAccessTimeUtc = 2; TOKEN .LastWriteTime = 4; TOKEN .LastWriteTimeUtc = 2; TOKEN _directoryInfo.FullName = 1 +- LINES = 246 +- SHA256 = A76D1936F8537422C548492A19E3F3BEBB2817B8103DEF99A75B479B74E93F32 +- MSBUILD_EXIT_CODE: 0 +- CSC_OUT_LINES: 2 +- PROD_CSC_OUT_LINES: 2 +- ZERO_ERRORS_LINES: 1 +- DLL_ADVANCED: True +- RUNSETTINGS: scripts\vscode\TaskMaster.cli.runsettings with /InIsolation +- FILTER: FullyQualifiedName~HelperClasses.DirectoryInfoWrapper_Tests.ToString_ShouldDelegateToWrappedDirectoryInfo +- RUNSETTINGS-HASH-NOW: 98EF03A8D3B0EBB2ED7A765E3B5E1B58E774D20202DF2F294C03A7260B9CEF57 (equals RUNSETTINGS-HASH) +- VSTEST_EXIT_CODE: 1 +- TRX_PRESENT: True +- SEQUENCE_FILES: 0 +- COUNTERS total=1 executed=1 passed=0 failed=1 +- RESULT ToString_ShouldDelegateToWrappedDirectoryInfo = Failed +- MESSAGE ToString_ShouldDelegateToWrappedDirectoryInfo :: Expected result to be "C:\Repo\fixture" with a length of 15, but "" has a length of 0, differs near "" (index 0). +- PREDICTED-PHRASE: `"C:\Repo\fixture"` (present; the rooted literal is the test's fixture constant, not a host path) + +## Revert and confirming run + +- Task: P1-T30 +- Command: git checkout -- UtilitiesCS/HelperClasses/FileSystem/DirectoryInfoWrapper.cs; git diff --exit-code HEAD -- UtilitiesCS/HelperClasses/FileSystem/DirectoryInfoWrapper.cs; git status --porcelain -- UtilitiesCS/HelperClasses/FileSystem/DirectoryInfoWrapper.cs; CMD-CENSUS (same PATH; non-zero TOKEN lines plus the transition token printed); CMD-BUILD with TASKID p1-t30; CMD-VSTEST with the C11 filter, NAMES-DIW and TASKID p1-t30 +- CHECKOUT-EXIT: 0 +- REVERT-DIFF-EXIT: 0 +- PORCELAIN: EMPTY +- CENSUS-TRANSITION: TOKEN return string.Empty; = 0 +- SHA256: F77616271ABB36C9133ADE496550703B391B077AB68C8F5DC652AB73FD177DC3 (anchor PRE-EDIT-HASH-DIWP: F77616271ABB36C9133ADE496550703B391B077AB68C8F5DC652AB73FD177DC3; 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: 98EF03A8D3B0EBB2ED7A765E3B5E1B58E774D20202DF2F294C03A7260B9CEF57 +- TRX_PRESENT: True; SEQUENCE_FILES: 0 +- COUNTERS total=1 executed=1 passed=1 failed=0 +- RESULT ToString_ShouldDelegateToWrappedDirectoryInfo = Passed diff --git a/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/regression-testing/p1-t3-pfs-census.2026-09-30T07-30.md b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/regression-testing/p1-t3-pfs-census.2026-09-30T07-30.md new file mode 100644 index 000000000..f06339d98 --- /dev/null +++ b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/regression-testing/p1-t3-pfs-census.2026-09-30T07-30.md @@ -0,0 +1,98 @@ +# PFS Rewrite Census (P1-T3) + +Timestamp: 2026-09-30T07-30 +Task: P1-T3 +Command: Write of Target Source A to UtilitiesCS.Test/HelperClasses/PhysicalFileSystemAdapters_Tests.cs (Read first, then Write); CMD-CENSUS with PATH = UtilitiesCS.Test/HelperClasses/PhysicalFileSystemAdapters_Tests.cs; git diff --numstat HEAD -- UtilitiesCS.Test/HelperClasses/PhysicalFileSystemAdapters_Tests.cs; git status --porcelain -- UtilitiesCS.Test/HelperClasses/PhysicalFileSystemAdapters_Tests.cs +EXIT_CODE: 0 (scoped to the CMD-CENSUS payload; the numstat and porcelain spans also exited 0) +Output Summary: all seven acceptance clauses met. Root-walk and catch tokens 0; `[TestMethod]` 7; `using System.Linq;` 1; `__940_missing_` 1; `MissingOwnedPath(` 10; `Exists.Should().BeFalse()` 6; `Assembly.Location` 4; `FileShare.ReadWrite` 4; `new DirectoryInfo(` 2 = 2 + 0; `new FileInfo(` 3 = 2 + 1; `Throw()` 20, `Throw()` 3, `.MemberCount` 2; every at-least member token meets its bound; the nine determinism tokens 0; LINES 410; porcelain ` M`. The Write tool wrote LF line endings (git warned that LF will be replaced by CRLF); P1-T8 restores the checkout form. + +## Census + +TOKEN GetRepositoryRoot = 0 +TOKEN GetSolutionFile = 0 +TOKEN TaskMaster.sln = 0 +TOKEN AppDomain = 0 +TOKEN File.Exists( = 4 +TOKEN catch (IOException) = 0 +TOKEN catch = 0 +TOKEN Assembly.Location = 4 +TOKEN RootedFixturePath = 0 +TOKEN OwnedAssemblyFile = 4 +TOKEN OwnedAssemblyDirectory = 4 +TOKEN MissingOwnedPath( = 10 +TOKEN __940_missing_ = 1 +TOKEN Exists.Should().BeFalse() = 6 +TOKEN new DirectoryInfo( = 2 +TOKEN new DirectoryInfo(RootedFixturePath) = 0 +TOKEN new DirectoryInfo(MissingOwnedPath( = 2 +TOKEN new FileInfo( = 3 +TOKEN new FileInfo(MissingOwnedPath( = 2 +TOKEN new FileInfo(typeof( = 1 +TOKEN [TestMethod] = 7 +TOKEN using System.Linq; = 1 +TOKEN using Moq; = 0 +TOKEN Thread.Sleep = 0 +TOKEN Task.Delay = 0 +TOKEN [Timeout = 0 +TOKEN DoNotParallelize = 0 +TOKEN Retry = 0 +TOKEN Path.GetTemp = 0 +TOKEN File.Create = 0 +TOKEN File.WriteAll = 0 +TOKEN Directory.CreateDirectory = 0 +TOKEN FileShare.ReadWrite = 4 +TOKEN FileShare.None = 0 +TOKEN .Create() = 1 +TOKEN .Create( = 2 +TOKEN .CreateSubdirectory( = 2 +TOKEN .EnumerateDirectories( = 3 +TOKEN .EnumerateFiles( = 3 +TOKEN .EnumerateFileSystemInfos( = 3 +TOKEN .GetDirectories( = 3 +TOKEN .GetFiles( = 3 +TOKEN .GetFileSystemInfos( = 3 +TOKEN .GetAccessControl( = 6 +TOKEN .GetObjectData( = 2 +TOKEN .Refresh() = 4 +TOKEN .SetAccessControl( = 2 +TOKEN .ToString() = 4 +TOKEN .Delete() = 2 +TOKEN .Delete(recursive: true) = 1 +TOKEN .MoveTo( = 2 +TOKEN .CopyTo( = 2 +TOKEN .Replace( = 2 +TOKEN .Open( = 3 +TOKEN .OpenRead() = 1 +TOKEN .OpenText() = 1 +TOKEN .AppendText() = 1 +TOKEN .OpenWrite() = 1 +TOKEN .Parent = 3 +TOKEN .Root = 2 +TOKEN .Length = 1 +TOKEN .IsReadOnly = 3 +TOKEN .Attributes = 6 +TOKEN .CreationTime = 12 +TOKEN .CreationTimeUtc = 6 +TOKEN .LastAccessTime = 12 +TOKEN .LastAccessTimeUtc = 6 +TOKEN .LastWriteTime = 12 +TOKEN .LastWriteTimeUtc = 6 +TOKEN .MemberCount = 2 +TOKEN Throw() = 20 +TOKEN Throw() = 3 +TOKEN !_directoryInfo.Exists = 0 +TOKEN Array.Empty() = 0 +TOKEN Enumerable.Empty() = 0 +TOKEN set { } = 0 +TOKEN public void Delete() { } = 0 +TOKEN _directoryInfo.FullName = 0 +TOKEN public long Length => 0; = 0 +TOKEN public void MoveTo(string destFileName) { } = 0 +TOKEN return string.Empty; = 0 +LINES = 410 +SHA256 = 198188E24AD412CBE09ED3DCC419BA566EB20ADC7D3EFC6611BC81840F3FA2CC + +## Git spans + +- NUMSTAT: `215 193 UtilitiesCS.Test/HelperClasses/PhysicalFileSystemAdapters_Tests.cs` +- PORCELAIN: ` M UtilitiesCS.Test/HelperClasses/PhysicalFileSystemAdapters_Tests.cs` diff --git a/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/regression-testing/p1-t31-post-control-clean-tree.2026-09-30T08-04.md b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/regression-testing/p1-t31-post-control-clean-tree.2026-09-30T08-04.md new file mode 100644 index 000000000..da4815b48 --- /dev/null +++ b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/regression-testing/p1-t31-post-control-clean-tree.2026-09-30T08-04.md @@ -0,0 +1,23 @@ +# Post-Control Clean Tree (P1-T31) + +Timestamp: 2026-09-30T08-04 +Task: P1-T31 +Command: git diff --exit-code HEAD -- UtilitiesCS UtilitiesCS.Test; git diff --exit-code ANCHOR-SHA -- UtilitiesCS (ANCHOR-SHA substituted with 231e1c0b55105aeb626bf5a6e8d0266a567cacad, the P0-T3 `ANCHOR-SHA:` value); git status --porcelain -- UtilitiesCS UtilitiesCS.Test; git rev-parse origin/main; Get-FileHash -Algorithm SHA256 of the two Write Set files and the three production files +EXIT_CODE: 0 (scoped to `git diff --exit-code HEAD -- UtilitiesCS UtilitiesCS.Test`) +PRODUCTION-DIFF-EXIT: 0 +ORIGIN-MAIN-NOW: 231e1c0b55105aeb626bf5a6e8d0266a567cacad (equal to ANCHOR-SHA; not gated) +Output Summary: after the eleven controls, UtilitiesCS and UtilitiesCS.Test equal HEAD, the UtilitiesCS working tree is byte-identical to the ANCHOR-SHA tree, the scoped porcelain is empty, and all five hashes equal their anchors. No temporary production edit and no control residue survives into Phase 2 (AC6 mechanical proof). + +- HEAD-DIFF-EXIT: 0 (no output) +- PRODUCTION-DIFF-EXIT: 0 (no output) +- PORCELAIN: EMPTY + +## Hashes beside anchors + +| File | SHA-256 now | Anchor | Equal | +| --- | --- | --- | --- | +| UtilitiesCS.Test/HelperClasses/PhysicalFileSystemAdapters_Tests.cs | C88A785C23D8DB2960E9FAA53DF9EF91F6F00359683485BEC7EDFC44F9A2F998 | FIX-HASH-PFS: C88A785C23D8DB2960E9FAA53DF9EF91F6F00359683485BEC7EDFC44F9A2F998 | yes | +| UtilitiesCS.Test/HelperClasses/DirectoryInfoWrapper_Tests.cs | 6650B33204BABCAAB7CD6E97C8B4BA7012ABB1F1320DB6AF7764ECD8714B7910 | FIX-HASH-DIW: 6650B33204BABCAAB7CD6E97C8B4BA7012ABB1F1320DB6AF7764ECD8714B7910 | yes | +| UtilitiesCS/HelperClasses/FileSystem/PhysicalDirectoryInfoAdapter.cs | FBAC7002BE4DF5979624F8AE649D52F1044BC40D54EBDA4618C6374EAB9CB242 | PRE-EDIT-HASH-PDA: FBAC7002BE4DF5979624F8AE649D52F1044BC40D54EBDA4618C6374EAB9CB242 | yes | +| UtilitiesCS/HelperClasses/FileSystem/PhysicalFileInfoAdapter.cs | 0337E5C1FF7A2E3FF5E5D67D383E68583838AFB5E5BA999A30502CF24333D948 | PRE-EDIT-HASH-PFA: 0337E5C1FF7A2E3FF5E5D67D383E68583838AFB5E5BA999A30502CF24333D948 | yes | +| UtilitiesCS/HelperClasses/FileSystem/DirectoryInfoWrapper.cs | F77616271ABB36C9133ADE496550703B391B077AB68C8F5DC652AB73FD177DC3 | PRE-EDIT-HASH-DIWP: F77616271ABB36C9133ADE496550703B391B077AB68C8F5DC652AB73FD177DC3 | yes | diff --git a/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/regression-testing/p1-t4-diw-census.2026-09-30T07-31.md b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/regression-testing/p1-t4-diw-census.2026-09-30T07-31.md new file mode 100644 index 000000000..940c2acd0 --- /dev/null +++ b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/regression-testing/p1-t4-diw-census.2026-09-30T07-31.md @@ -0,0 +1,98 @@ +# DIW Edit Census (P1-T4) + +Timestamp: 2026-09-30T07-31 +Task: P1-T4 +Command: six Target Source B edits to UtilitiesCS.Test/HelperClasses/DirectoryInfoWrapper_Tests.cs with the Edit tool, bottom-up (1 delete the helper 374 to 391; 2 line 86; 3 lines 64 to 80; 4 lines 43 to 61; 5 line 30; 6 insert the fixture block after line 15); CMD-CENSUS with PATH = UtilitiesCS.Test/HelperClasses/DirectoryInfoWrapper_Tests.cs; git diff --numstat HEAD -- UtilitiesCS.Test/HelperClasses/DirectoryInfoWrapper_Tests.cs; git status --porcelain -- UtilitiesCS.Test/HelperClasses/DirectoryInfoWrapper_Tests.cs +EXIT_CODE: 0 (scoped to the CMD-CENSUS payload; the numstat and porcelain spans also exited 0) +Output Summary: all six acceptance clauses met. `GetRepositoryRoot`, `TaskMaster.sln`, `AppDomain`, `File.Exists(`, `catch`, `GetSolutionFile`, `MissingOwnedPath(` 0; `[TestMethod]` 8; `RootedFixturePath` 3; `Assembly.Location` 1; `OwnedAssemblyFile` 4; `OwnedAssemblyDirectory` 3; `using Moq;` 1; `using System.Linq;` 1; `new DirectoryInfo(` 2 = `new DirectoryInfo(RootedFixturePath)` 2; `new FileInfo(` 1 = `new FileInfo(typeof(` 1; `.GetFiles(` 7, `.GetDirectories(` 7, `.EnumerateFileSystemInfos(` 8, `.ToString()` 4; nine determinism tokens 0; numstat 32 added, 31 deleted; LINES 394; porcelain ` M`. + +## Census + +TOKEN GetRepositoryRoot = 0 +TOKEN GetSolutionFile = 0 +TOKEN TaskMaster.sln = 0 +TOKEN AppDomain = 0 +TOKEN File.Exists( = 0 +TOKEN catch (IOException) = 0 +TOKEN catch = 0 +TOKEN Assembly.Location = 1 +TOKEN RootedFixturePath = 3 +TOKEN OwnedAssemblyFile = 4 +TOKEN OwnedAssemblyDirectory = 3 +TOKEN MissingOwnedPath( = 0 +TOKEN __940_missing_ = 0 +TOKEN Exists.Should().BeFalse() = 0 +TOKEN new DirectoryInfo( = 2 +TOKEN new DirectoryInfo(RootedFixturePath) = 2 +TOKEN new DirectoryInfo(MissingOwnedPath( = 0 +TOKEN new FileInfo( = 1 +TOKEN new FileInfo(MissingOwnedPath( = 0 +TOKEN new FileInfo(typeof( = 1 +TOKEN [TestMethod] = 8 +TOKEN using System.Linq; = 1 +TOKEN using Moq; = 1 +TOKEN Thread.Sleep = 0 +TOKEN Task.Delay = 0 +TOKEN [Timeout = 0 +TOKEN DoNotParallelize = 0 +TOKEN Retry = 0 +TOKEN Path.GetTemp = 0 +TOKEN File.Create = 0 +TOKEN File.WriteAll = 0 +TOKEN Directory.CreateDirectory = 0 +TOKEN FileShare.ReadWrite = 0 +TOKEN FileShare.None = 0 +TOKEN .Create() = 2 +TOKEN .Create( = 4 +TOKEN .CreateSubdirectory( = 4 +TOKEN .EnumerateDirectories( = 6 +TOKEN .EnumerateFiles( = 6 +TOKEN .EnumerateFileSystemInfos( = 8 +TOKEN .GetDirectories( = 7 +TOKEN .GetFiles( = 7 +TOKEN .GetFileSystemInfos( = 6 +TOKEN .GetAccessControl( = 4 +TOKEN .GetObjectData( = 2 +TOKEN .Refresh() = 2 +TOKEN .SetAccessControl( = 2 +TOKEN .ToString() = 4 +TOKEN .Delete() = 2 +TOKEN .Delete(recursive: true) = 1 +TOKEN .MoveTo( = 2 +TOKEN .CopyTo( = 0 +TOKEN .Replace( = 0 +TOKEN .Open( = 0 +TOKEN .OpenRead() = 0 +TOKEN .OpenText() = 0 +TOKEN .AppendText() = 0 +TOKEN .OpenWrite() = 0 +TOKEN .Parent = 6 +TOKEN .Root = 4 +TOKEN .Length = 0 +TOKEN .IsReadOnly = 0 +TOKEN .Attributes = 4 +TOKEN .CreationTime = 8 +TOKEN .CreationTimeUtc = 4 +TOKEN .LastAccessTime = 8 +TOKEN .LastAccessTimeUtc = 4 +TOKEN .LastWriteTime = 8 +TOKEN .LastWriteTimeUtc = 4 +TOKEN .MemberCount = 0 +TOKEN Throw() = 0 +TOKEN Throw() = 0 +TOKEN !_directoryInfo.Exists = 0 +TOKEN Array.Empty() = 0 +TOKEN Enumerable.Empty() = 0 +TOKEN set { } = 0 +TOKEN public void Delete() { } = 0 +TOKEN _directoryInfo.FullName = 0 +TOKEN public long Length => 0; = 0 +TOKEN public void MoveTo(string destFileName) { } = 0 +TOKEN return string.Empty; = 0 +LINES = 394 +SHA256 = 6650B33204BABCAAB7CD6E97C8B4BA7012ABB1F1320DB6AF7764ECD8714B7910 + +## Git spans + +- NUMSTAT: `32 31 UtilitiesCS.Test/HelperClasses/DirectoryInfoWrapper_Tests.cs` +- PORCELAIN: ` M UtilitiesCS.Test/HelperClasses/DirectoryInfoWrapper_Tests.cs` diff --git a/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/regression-testing/p1-t5-csharpier-scoped.2026-09-30T07-32.md b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/regression-testing/p1-t5-csharpier-scoped.2026-09-30T07-32.md new file mode 100644 index 000000000..4664dae4c --- /dev/null +++ b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/regression-testing/p1-t5-csharpier-scoped.2026-09-30T07-32.md @@ -0,0 +1,191 @@ +# Scoped CSharpier Format and Check (P1-T5) + +Timestamp: 2026-09-30T07-32 +Task: P1-T5 +ITERATION: 1 +Command: Get-FileHash -Algorithm SHA256 of the two Write Set files; dotnet tool run csharpier format UtilitiesCS.Test/HelperClasses/PhysicalFileSystemAdapters_Tests.cs UtilitiesCS.Test/HelperClasses/DirectoryInfoWrapper_Tests.cs; Get-FileHash again; dotnet tool run csharpier check UtilitiesCS.Test/HelperClasses/PhysicalFileSystemAdapters_Tests.cs UtilitiesCS.Test/HelperClasses/DirectoryInfoWrapper_Tests.cs; CMD-CENSUS on each file +EXIT_CODE: 0 (scoped to the `check`) +Output Summary: format exit 0 and check exit 0; one file rewritten (PFS; DIW unchanged); every post value of the Token Census Expectations table holds for both files; THROW-SUM 23; LINES 446 (PFS) and 394 (DIW), both at most 500. The hashes, format and check ran in one payload; the CMD-CENSUS of both files ran in the payload issued immediately after it, with no file write in between (the census hashes equal the after-format hashes). + +- PFS-HASH-BEFORE: 198188E24AD412CBE09ED3DCC419BA566EB20ADC7D3EFC6611BC81840F3FA2CC +- PFS-HASH-AFTER: C88A785C23D8DB2960E9FAA53DF9EF91F6F00359683485BEC7EDFC44F9A2F998 +- DIW-HASH-BEFORE: 6650B33204BABCAAB7CD6E97C8B4BA7012ABB1F1320DB6AF7764ECD8714B7910 +- DIW-HASH-AFTER: 6650B33204BABCAAB7CD6E97C8B4BA7012ABB1F1320DB6AF7764ECD8714B7910 +- FORMAT_EXIT_CODE: 0 (console: `Formatted 2 files in 2605ms.`, a processed-file count) +- REWRITTEN: 1 +- CHECK-SUMMARY-LINE: `Checked 2 files in 1166ms.` +- CHECK_EXIT_CODE: 0 +- CENSUS-TOKENS-APPENDED: NONE +- THROW-SUM: 23 (Throw() 20 + Throw() 3) + +## Census: UtilitiesCS.Test/HelperClasses/PhysicalFileSystemAdapters_Tests.cs + +TOKEN GetRepositoryRoot = 0 +TOKEN GetSolutionFile = 0 +TOKEN TaskMaster.sln = 0 +TOKEN AppDomain = 0 +TOKEN File.Exists( = 4 +TOKEN catch (IOException) = 0 +TOKEN catch = 0 +TOKEN Assembly.Location = 4 +TOKEN RootedFixturePath = 0 +TOKEN OwnedAssemblyFile = 4 +TOKEN OwnedAssemblyDirectory = 4 +TOKEN MissingOwnedPath( = 10 +TOKEN __940_missing_ = 1 +TOKEN Exists.Should().BeFalse() = 6 +TOKEN new DirectoryInfo( = 2 +TOKEN new DirectoryInfo(RootedFixturePath) = 0 +TOKEN new DirectoryInfo(MissingOwnedPath( = 2 +TOKEN new FileInfo( = 3 +TOKEN new FileInfo(MissingOwnedPath( = 2 +TOKEN new FileInfo(typeof( = 1 +TOKEN [TestMethod] = 7 +TOKEN using System.Linq; = 1 +TOKEN using Moq; = 0 +TOKEN Thread.Sleep = 0 +TOKEN Task.Delay = 0 +TOKEN [Timeout = 0 +TOKEN DoNotParallelize = 0 +TOKEN Retry = 0 +TOKEN Path.GetTemp = 0 +TOKEN File.Create = 0 +TOKEN File.WriteAll = 0 +TOKEN Directory.CreateDirectory = 0 +TOKEN FileShare.ReadWrite = 4 +TOKEN FileShare.None = 0 +TOKEN .Create() = 1 +TOKEN .Create( = 2 +TOKEN .CreateSubdirectory( = 2 +TOKEN .EnumerateDirectories( = 3 +TOKEN .EnumerateFiles( = 3 +TOKEN .EnumerateFileSystemInfos( = 3 +TOKEN .GetDirectories( = 3 +TOKEN .GetFiles( = 3 +TOKEN .GetFileSystemInfos( = 3 +TOKEN .GetAccessControl( = 6 +TOKEN .GetObjectData( = 2 +TOKEN .Refresh() = 4 +TOKEN .SetAccessControl( = 2 +TOKEN .ToString() = 4 +TOKEN .Delete() = 2 +TOKEN .Delete(recursive: true) = 1 +TOKEN .MoveTo( = 2 +TOKEN .CopyTo( = 2 +TOKEN .Replace( = 2 +TOKEN .Open( = 3 +TOKEN .OpenRead() = 1 +TOKEN .OpenText() = 1 +TOKEN .AppendText() = 1 +TOKEN .OpenWrite() = 1 +TOKEN .Parent = 3 +TOKEN .Root = 2 +TOKEN .Length = 1 +TOKEN .IsReadOnly = 3 +TOKEN .Attributes = 6 +TOKEN .CreationTime = 12 +TOKEN .CreationTimeUtc = 6 +TOKEN .LastAccessTime = 12 +TOKEN .LastAccessTimeUtc = 6 +TOKEN .LastWriteTime = 12 +TOKEN .LastWriteTimeUtc = 6 +TOKEN .MemberCount = 2 +TOKEN Throw() = 20 +TOKEN Throw() = 3 +TOKEN !_directoryInfo.Exists = 0 +TOKEN Array.Empty() = 0 +TOKEN Enumerable.Empty() = 0 +TOKEN set { } = 0 +TOKEN public void Delete() { } = 0 +TOKEN _directoryInfo.FullName = 0 +TOKEN public long Length => 0; = 0 +TOKEN public void MoveTo(string destFileName) { } = 0 +TOKEN return string.Empty; = 0 +LINES = 446 +SHA256 = C88A785C23D8DB2960E9FAA53DF9EF91F6F00359683485BEC7EDFC44F9A2F998 + +## Census: UtilitiesCS.Test/HelperClasses/DirectoryInfoWrapper_Tests.cs + +TOKEN GetRepositoryRoot = 0 +TOKEN GetSolutionFile = 0 +TOKEN TaskMaster.sln = 0 +TOKEN AppDomain = 0 +TOKEN File.Exists( = 0 +TOKEN catch (IOException) = 0 +TOKEN catch = 0 +TOKEN Assembly.Location = 1 +TOKEN RootedFixturePath = 3 +TOKEN OwnedAssemblyFile = 4 +TOKEN OwnedAssemblyDirectory = 3 +TOKEN MissingOwnedPath( = 0 +TOKEN __940_missing_ = 0 +TOKEN Exists.Should().BeFalse() = 0 +TOKEN new DirectoryInfo( = 2 +TOKEN new DirectoryInfo(RootedFixturePath) = 2 +TOKEN new DirectoryInfo(MissingOwnedPath( = 0 +TOKEN new FileInfo( = 1 +TOKEN new FileInfo(MissingOwnedPath( = 0 +TOKEN new FileInfo(typeof( = 1 +TOKEN [TestMethod] = 8 +TOKEN using System.Linq; = 1 +TOKEN using Moq; = 1 +TOKEN Thread.Sleep = 0 +TOKEN Task.Delay = 0 +TOKEN [Timeout = 0 +TOKEN DoNotParallelize = 0 +TOKEN Retry = 0 +TOKEN Path.GetTemp = 0 +TOKEN File.Create = 0 +TOKEN File.WriteAll = 0 +TOKEN Directory.CreateDirectory = 0 +TOKEN FileShare.ReadWrite = 0 +TOKEN FileShare.None = 0 +TOKEN .Create() = 2 +TOKEN .Create( = 4 +TOKEN .CreateSubdirectory( = 4 +TOKEN .EnumerateDirectories( = 6 +TOKEN .EnumerateFiles( = 6 +TOKEN .EnumerateFileSystemInfos( = 8 +TOKEN .GetDirectories( = 7 +TOKEN .GetFiles( = 7 +TOKEN .GetFileSystemInfos( = 6 +TOKEN .GetAccessControl( = 4 +TOKEN .GetObjectData( = 2 +TOKEN .Refresh() = 2 +TOKEN .SetAccessControl( = 2 +TOKEN .ToString() = 4 +TOKEN .Delete() = 2 +TOKEN .Delete(recursive: true) = 1 +TOKEN .MoveTo( = 2 +TOKEN .CopyTo( = 0 +TOKEN .Replace( = 0 +TOKEN .Open( = 0 +TOKEN .OpenRead() = 0 +TOKEN .OpenText() = 0 +TOKEN .AppendText() = 0 +TOKEN .OpenWrite() = 0 +TOKEN .Parent = 6 +TOKEN .Root = 4 +TOKEN .Length = 0 +TOKEN .IsReadOnly = 0 +TOKEN .Attributes = 4 +TOKEN .CreationTime = 8 +TOKEN .CreationTimeUtc = 4 +TOKEN .LastAccessTime = 8 +TOKEN .LastAccessTimeUtc = 4 +TOKEN .LastWriteTime = 8 +TOKEN .LastWriteTimeUtc = 4 +TOKEN .MemberCount = 0 +TOKEN Throw() = 0 +TOKEN Throw() = 0 +TOKEN !_directoryInfo.Exists = 0 +TOKEN Array.Empty() = 0 +TOKEN Enumerable.Empty() = 0 +TOKEN set { } = 0 +TOKEN public void Delete() { } = 0 +TOKEN _directoryInfo.FullName = 0 +TOKEN public long Length => 0; = 0 +TOKEN public void MoveTo(string destFileName) { } = 0 +TOKEN return string.Empty; = 0 +LINES = 394 +SHA256 = 6650B33204BABCAAB7CD6E97C8B4BA7012ABB1F1320DB6AF7764ECD8714B7910 diff --git a/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/regression-testing/p1-t6-build-after-fix.2026-09-30T07-33.md b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/regression-testing/p1-t6-build-after-fix.2026-09-30T07-33.md new file mode 100644 index 000000000..2915aa401 --- /dev/null +++ b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/regression-testing/p1-t6-build-after-fix.2026-09-30T07-33.md @@ -0,0 +1,15 @@ +# Build After Fix (P1-T6) + +Timestamp: 2026-09-30T07-33 +Task: P1-T6 +ITERATION: 1 +Command: msbuild UtilitiesCS.Test\UtilitiesCS.Test.csproj /t:Build /m /p:Configuration=Debug /p:Platform=AnyCPU (resolved through vswhere, plus /nodeReuse:false; CMD-BUILD with TASKID p1-t6, file log coverage\logs\p1-t6.msbuild.log) +EXIT_CODE: 0 +Output Summary: build succeeded; the edited test sources were compiled into the assembly the next task loads. + +- MSBUILD_EXIT_CODE: 0 +- CSC_OUT_LINES: 2 +- PROD_CSC_OUT_LINES: 0 (no production file changed, so UtilitiesCS was up to date) +- ZERO_ERRORS_LINES: 1 +- DLL_ADVANCED: True +- Note: the msbuild console stream was piped to Out-Null in the payload to keep the tool transcript short; the file logger, the exit code and every counter above are unaffected. diff --git a/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/regression-testing/p1-t7-exception-type-observation.2026-09-30T07-34.md b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/regression-testing/p1-t7-exception-type-observation.2026-09-30T07-34.md new file mode 100644 index 000000000..8aef5f968 --- /dev/null +++ b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/regression-testing/p1-t7-exception-type-observation.2026-09-30T07-34.md @@ -0,0 +1,32 @@ +# Exception-Type Observation (P1-T7) + +Timestamp: 2026-09-30T07-34 +Task: P1-T7 +ITERATION: 1 +Command: vstest.console.exe UtilitiesCS.Test\bin\Debug\UtilitiesCS.Test.dll "/Settings:scripts\vscode\TaskMaster.cli.runsettings" /InIsolation "/TestCaseFilter:FullyQualifiedName~HelperClasses.PhysicalFileSystemAdapters_Tests|FullyQualifiedName~HelperClasses.DirectoryInfoWrapper_Tests" "/ResultsDirectory:coverage\test-results\940\p1-t7" "/Logger:trx;LogFileName=p1-t7.trx" "/Blame:CollectHangDump;TestTimeout=4min;HangDumpType=None" (resolved through vswhere; CMD-VSTEST with FILTER-BOTH and NAMES-BOTH) +EXIT_CODE: 0 +Output Summary: branch (a); all fifteen tests of the two rewritten classes passed on the first run, so every predicted exception type was observed as written and no assertion was pinned. + +- RUNSETTINGS-HASH-NOW: 98EF03A8D3B0EBB2ED7A765E3B5E1B58E774D20202DF2F294C03A7260B9CEF57 (equals RUNSETTINGS-HASH) +- TRX_PRESENT: True +- SEQUENCE_FILES: 0 +- COUNTERS total=15 executed=15 passed=15 failed=0 +- RESULT_COUNT: 15 +- RESULT EnumerateFileSystemInfos_ShouldWrapDirectoriesAndFiles = Passed +- RESULT PhysicalDirectoryInfoAdapter_AccessorsAndNoOpCreation_MirrorOwnedDirectory = Passed +- RESULT PhysicalDirectoryInfoAdapter_Enumeration_WrapsOwnedDirectoryEntries = Passed +- RESULT PropertyDelegates_ShouldMirrorMockedIDirectoryInfo = Passed +- RESULT GetDirectoriesAndGetFiles_ShouldReturnWrappedEntries = Passed +- RESULT Constructor_WhenDirectoryInfoIsNull_ThrowsArgumentNullException = Passed +- RESULT Properties_ShouldMirrorWrappedDirectoryInfo = Passed +- RESULT EnumerationAndArrayMethods_ShouldDelegateToWrappedIDirectoryInfo = Passed +- RESULT PhysicalFileInfoAdapter_PropertiesStreamsAndAccessors_MirrorFileInfo = Passed +- RESULT PhysicalDirectoryInfoAdapter_MissingDirectoryAndUnsupportedInfo_BranchesBehaveAsExpected = Passed +- RESULT PhysicalFileInfoAdapter_SettersOnMissingFile_ThrowWithoutCreatingFiles = Passed +- RESULT ToString_ShouldDelegateToWrappedDirectoryInfo = Passed +- RESULT PhysicalFileInfoAdapter_MissingFileBranches_ThrowOrNoOpWithoutCreatingFiles = Passed +- RESULT LifecycleAndAccessControlMethods_ShouldDelegateToWrappedIDirectoryInfo = Passed +- RESULT PhysicalDirectoryInfoAdapter_SettersOnMissingDirectory_ThrowWithoutCreatingEntries = Passed +- MESSAGE lines: none (no failed test) +- EXCEPTION-TYPES: AS PREDICTED +- PINNED-COUNT: 0 diff --git a/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/regression-testing/p1-t8-fix-commit.2026-09-30T07-35.md b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/regression-testing/p1-t8-fix-commit.2026-09-30T07-35.md new file mode 100644 index 000000000..cc7d83764 --- /dev/null +++ b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/regression-testing/p1-t8-fix-commit.2026-09-30T07-35.md @@ -0,0 +1,39 @@ +# Fix Commit (P1-T8) + +Timestamp: 2026-09-30T07-35 +Task: P1-T8 +Command: git diff --cached --name-only; git status --porcelain -- docs/features/potential; git add -- UtilitiesCS.Test/HelperClasses/PhysicalFileSystemAdapters_Tests.cs UtilitiesCS.Test/HelperClasses/DirectoryInfoWrapper_Tests.cs docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940; git commit -m "test(940): verify file-system wrapper delegation through owned fixtures instead of the repository solution file" -m "Co-Authored-By: Claude Opus 5.5 noreply@anthropic.com" -- UtilitiesCS.Test/HelperClasses/PhysicalFileSystemAdapters_Tests.cs UtilitiesCS.Test/HelperClasses/DirectoryInfoWrapper_Tests.cs docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940; git ls-files --eol -- UtilitiesCS.Test/HelperClasses/PhysicalFileSystemAdapters_Tests.cs UtilitiesCS.Test/HelperClasses/DirectoryInfoWrapper_Tests.cs (twice); git diff --exit-code HEAD -- UtilitiesCS.Test; git show --name-only --format= HEAD; git rev-parse HEAD; Get-FileHash -Algorithm SHA256 of the two code files +EXIT_CODE: 0 (scoped to `git diff --exit-code HEAD -- UtilitiesCS.Test`, run after the commit and after the line-ending step) +Output Summary: fix committed; both test files already carried the checkout line endings (w/crlf), so no restore was needed; the committed test project equals the working tree; the commit lists exactly the two code paths plus paths under the feature folder; both FIX-HASH anchors equal the P1-T5 ITERATION 1 after-format hashes. This artifact and the P1-T8 check-off are written after the commit and are not part of it (fail-closed carve-out). + +- PRE-STAGED: NONE +- POTENTIAL-PORCELAIN: EMPTY +- COMMIT-EXIT: 0 +- COMMIT-OUTPUT-HEAD-LINE: `[bug/filesystem-wrapper-tests-open-repository-solution-file-940 bb9a3d64d] test(940): verify file-system wrapper delegation through owned fixtures instead of the repository solution file` (`11 files changed, 808 insertions(+), 225 deletions(-)`) +- EOL-BEFORE: + - `i/lf w/crlf attr/text=auto UtilitiesCS.Test/HelperClasses/DirectoryInfoWrapper_Tests.cs` + - `i/lf w/crlf attr/text=auto UtilitiesCS.Test/HelperClasses/PhysicalFileSystemAdapters_Tests.cs` +- EOL-AFTER: + - `i/lf w/crlf attr/text=auto UtilitiesCS.Test/HelperClasses/DirectoryInfoWrapper_Tests.cs` + - `i/lf w/crlf attr/text=auto UtilitiesCS.Test/HelperClasses/PhysicalFileSystemAdapters_Tests.cs` +- EOL-RESTORED: NONE +- DIFF-EXIT-CODE-HEAD-UTILITIESCS-TEST: 0 (no output) +- FIX-HEAD: bb9a3d64da4a90ce6a8815fb5b86d18703c5d04a +- FIX-HASH-PFS: C88A785C23D8DB2960E9FAA53DF9EF91F6F00359683485BEC7EDFC44F9A2F998 (equals P1-T5 PFS-HASH-AFTER) +- FIX-HASH-DIW: 6650B33204BABCAAB7CD6E97C8B4BA7012ABB1F1320DB6AF7764ECD8714B7910 (equals P1-T5 DIW-HASH-AFTER) + +## git show path list (verbatim) + +``` +UtilitiesCS.Test/HelperClasses/DirectoryInfoWrapper_Tests.cs +UtilitiesCS.Test/HelperClasses/PhysicalFileSystemAdapters_Tests.cs +docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/baseline/p0-t13-phase0-commit.2026-09-30T07-24.md +docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/other/root-walk-site-classification.2026-09-30T07-27.md +docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/regression-testing/fail-before-exception.2026-09-30T07-28.md +docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/regression-testing/p1-t3-pfs-census.2026-09-30T07-30.md +docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/regression-testing/p1-t4-diw-census.2026-09-30T07-31.md +docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/regression-testing/p1-t5-csharpier-scoped.2026-09-30T07-32.md +docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/regression-testing/p1-t6-build-after-fix.2026-09-30T07-33.md +docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/regression-testing/p1-t7-exception-type-observation.2026-09-30T07-34.md +docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/plan.2026-09-29T23-02.md +``` diff --git a/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/regression-testing/test-run-final.md b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/regression-testing/test-run-final.md new file mode 100644 index 000000000..0ae236158 --- /dev/null +++ b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/regression-testing/test-run-final.md @@ -0,0 +1,30 @@ +# Final Scoped Run of the Two Rewritten Classes (P2-T5) + +Timestamp: 2026-09-30T08-10 +Task: P2-T5 +ITERATION: 1 +Command: vstest.console.exe UtilitiesCS.Test\bin\Debug\UtilitiesCS.Test.dll "/Settings:scripts\vscode\TaskMaster.cli.runsettings" /InIsolation "/TestCaseFilter:FullyQualifiedName~HelperClasses.PhysicalFileSystemAdapters_Tests|FullyQualifiedName~HelperClasses.DirectoryInfoWrapper_Tests" "/ResultsDirectory:coverage\test-results\940\p2-t5" "/Logger:trx;LogFileName=p2-t5.trx" "/Blame:CollectHangDump;TestTimeout=4min;HangDumpType=None" (resolved through vswhere; CMD-VSTEST with FILTER-BOTH and NAMES-BOTH; parallel regime of the CLI runsettings, Workers 0, Scope ClassLevel) +EXIT_CODE: 0 +Output Summary: all fifteen tests of the two rewritten classes passed under the parallel regime with the isolation switch; no hang document; console printed `Test Run Successful.`, `Total tests: 15`, `Passed: 15` and no `Failed:` or `Skipped:` line. + +- RUNSETTINGS-HASH-NOW: 98EF03A8D3B0EBB2ED7A765E3B5E1B58E774D20202DF2F294C03A7260B9CEF57 (equals P0-T4 `RUNSETTINGS-HASH:`) +- TRX_PRESENT: True +- COUNTERS total=15 executed=15 passed=15 failed=0 +- RESULT_COUNT: 15 +- SEQUENCE_FILES: 0 +- RESULT ToString_ShouldDelegateToWrappedDirectoryInfo = Passed +- RESULT GetDirectoriesAndGetFiles_ShouldReturnWrappedEntries = Passed +- RESULT PhysicalDirectoryInfoAdapter_MissingDirectoryAndUnsupportedInfo_BranchesBehaveAsExpected = Passed +- RESULT PropertyDelegates_ShouldMirrorMockedIDirectoryInfo = Passed +- RESULT PhysicalDirectoryInfoAdapter_SettersOnMissingDirectory_ThrowWithoutCreatingEntries = Passed +- RESULT PhysicalFileInfoAdapter_SettersOnMissingFile_ThrowWithoutCreatingFiles = Passed +- RESULT LifecycleAndAccessControlMethods_ShouldDelegateToWrappedIDirectoryInfo = Passed +- RESULT PhysicalDirectoryInfoAdapter_Enumeration_WrapsOwnedDirectoryEntries = Passed +- RESULT Properties_ShouldMirrorWrappedDirectoryInfo = Passed +- RESULT EnumerateFileSystemInfos_ShouldWrapDirectoriesAndFiles = Passed +- RESULT EnumerationAndArrayMethods_ShouldDelegateToWrappedIDirectoryInfo = Passed +- RESULT Constructor_WhenDirectoryInfoIsNull_ThrowsArgumentNullException = Passed +- RESULT PhysicalFileInfoAdapter_MissingFileBranches_ThrowOrNoOpWithoutCreatingFiles = Passed +- RESULT PhysicalDirectoryInfoAdapter_AccessorsAndNoOpCreation_MirrorOwnedDirectory = Passed +- RESULT PhysicalFileInfoAdapter_PropertiesStreamsAndAccessors_MirrorFileInfo = Passed +- MESSAGE lines: none (no failed test) diff --git a/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/feature-audit.2026-09-30T12-00.md b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/feature-audit.2026-09-30T12-00.md new file mode 100644 index 000000000..78fe03ce3 --- /dev/null +++ b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/feature-audit.2026-09-30T12-00.md @@ -0,0 +1,63 @@ +# Feature Audit: filesystem-wrapper-tests-open-repository-solution-file (Issue #940) + +- Review timestamp label: `2026-09-30T12-00` +- Work mode: `minor-audit` (issue.md line 12). AC source: the `## Acceptance Criteria` section of `issue.md` only (AC1 to AC8, lines 64 to 71). AC8 is evaluated as amended on 2026-09-30 by the coordinator ruling recorded in the dated note at issue.md line 73 and in the plan's Revision Log entry A; the amendment is treated as authorized per the caller's instruction. +- Blocking findings: **0**. AC status: 8 of 8 PASS. + +## Scope and Baseline + +- Branch head: `bd71b8160e281054657280af8a7c54eeffe5c563` (worktree loose ref). Base: `origin/main` at `66afa6372fd82fc1ffd7c81f85a1ad65eebc5817`, merged into the branch at `40e587ce20bbd41cd6915707271faae937f1a8e2` (reflog entry `merge origin/main: Merge made by the 'ort' strategy`, epoch 1790779114). The merge changed no path under `UtilitiesCS/` or `UtilitiesCS.Test/` (P2-T7 `MERGE-UCS-DIFF-EXIT: 0`). +- Diff against the base (executor's verbatim `git diff --name-only origin/main...HEAD`, 46 paths): `UtilitiesCS.Test/HelperClasses/DirectoryInfoWrapper_Tests.cs`, `UtilitiesCS.Test/HelperClasses/PhysicalFileSystemAdapters_Tests.cs`, 43 paths under the feature folder, and the inherited promotion record `docs/features/potential/promoted/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file.md`. No production, project, runsettings, coverage-config or workflow path. +- Baseline (Phase 0, pre-edit tree at `231e1c0b55105aeb626bf5a6e8d0266a567cacad`, then the same source trees at the merged base): scoped run 12/12 passed; full suite 7323/7323 passed; first-party coverage 85.32% lines (56084/65736), 79.73% branches (13597/17054); per-file PDA 81/91 lines and 36/42 branches, PFA 69/75 and 6/12, DIW 123/123 and 3/4; pre-edit census PFS `GetRepositoryRoot` 6, `TaskMaster.sln` 13, `catch (IOException)` 2, DIW `GetRepositoryRoot` 5, `TaskMaster.sln` 3. +- Post-change (merged head, MEASUREMENT 3): scoped run 15/15 passed; full suite 7327/7327 passed; first-party 85.33% lines (56092/65736), 79.73% branches (13597/17054); per-file PDA 91/91 and 36/42, PFA 71/75 and 6/12, DIW 123/123 and 3/4 (reviewer-verified against the `` nodes of `coverage/baseline-940.cobertura.xml` and `coverage/final3-940.cobertura.xml`). +- Reviewer tooling: Read, Grep, Glob only (no shell, per caller instruction). PR context artifacts are absent from the item worktree; scope was taken from the executor's three-dot listings, which agree with the caller's branch facts and the worktree reflog. + +## Acceptance Criteria Inventory + +| ID | Criterion (abridged; full text in issue.md lines 64 to 71) | State on disk | +| --- | --- | --- | +| AC1 | No test in either file locates the repository root or references `TaskMaster.sln`; both `GetRepositoryRoot` helpers and `GetSolutionFile` removed | `[x]` | +| AC2 | No test in either file catches `IOException` or any broader type | `[x]` | +| AC3 | No mutating call at the repository root or a tracked entry; each mutating member exercised only against a Moq mock, an asserted-absent owned path, or an owned entry on which it is a no-op by construction | `[x]` | +| AC4 | Every previously exercised member still exercised, delegation asserted against Moq, an owned read-only fixture or a documented non-existent-path outcome; `Create`, `Refresh`, `SetAccessControl` no-ops complete without exception; coverage of the changed wrapper and adapter lines does not regress | `[x]` | +| AC5 | No temporary file or directory; no `DoNotParallelize`, worker-count or scope change, retry or sleep | `[x]` | +| AC6 | Negative-control run shows each rewritten test fails when its delegation is deliberately broken; control reverted before the final QC pass | `[x]` | +| AC7 | The three additional root-walk sites classified in an evidence artifact; none modified | `[x]` | +| AC8 (amended 2026-09-30) | C# toolchain passes in order with no new failures relative to baseline; covered lines and branches not lower than baseline in each of the three changed wrapper and adapter files; first-party line >= 80% and branch >= 75% | `[x]` | + +## Acceptance Criteria Evaluation + +| ID | Verdict | Evidence and reviewer verification | +| --- | --- | --- | +| AC1 | PASS | Read both files in full: no `GetRepositoryRoot`, `GetSolutionFile`, `TaskMaster.sln` or `AppDomain` token; DIW fixture is `RootedFixturePath` plus the owned image; PFS fixtures are the owned image, its directory and prefixed missing paths. P2-T8 census: all four tokens 0 in both files (baseline 6/3/13/2 and 5/0/3/1). P2-T10 `ADDED-ROOTWALK: 0`. | +| AC2 | PASS | `catch` token 0 in both files (baseline PFS 2); read confirms no try/catch; P2-T10 `ADDED-CATCH: 0`. Exception outcomes are asserted with `Should().Throw()`. | +| AC3 | PASS | `p2-t14-ac3-mutating-call-inventory.2026-09-30T11-31.md`: 34 PFS call sites (owned output directory 4, its parent 2, existing owned entry 2, missing owned path 26, mock 0) and 16 DIW call sites (all mock, all inside the three Moq tests); no call names a repository path. Reviewer re-derived the PFS classes from the file: lines 48 to 58 (owned directory and parent no-ops), 200 to 206 and 216 (missing directory), 236 to 238 (missing directory), 332 to 333 (owned image no-ops), 379 to 386 and 397 (missing file), 420 to 426 (missing file with destinations asserted absent at 414 to 417). Note CR-1 (code review): `SetAccessControl` is an idempotent DACL write admitted by the criterion text. | +| AC4 | PASS | `p2-t15-ac4-form.2026-09-30T11-32.md`: fifteen FORM rows over the four admitted forms, each backed by a `RESULT ... = Passed` line of `test-run-final.md`; `Create`, `Refresh`, `SetAccessControl` each in a `no-op-no-throw` row. Member census: every member token at or above its pre-edit count (for example `.Refresh()` 2 -> 4, `.IsReadOnly` 2 -> 3, `.MemberCount` 0 -> 2, `.Open(` 3 -> 3, `.CopyTo(` 2 -> 2, DIW `.GetFiles(` 7 -> 7). Coverage of the three carried files: PDA 81 -> 91 lines / 36 -> 36 branches, PFA 69 -> 71 / 6 -> 6, DIW 123 -> 123 / 3 -> 3 (reviewer-verified in the Cobertura class nodes). | +| AC5 | PASS | Read: no `Path.GetTemp`, `File.Create`, `File.WriteAll`, `Directory.CreateDirectory`, `DoNotParallelize`, `[Timeout`, `Thread.Sleep`, `Task.Delay`, retry. P2-T10: the ten prohibition counts 0 over 276 added lines; `OUT-OF-SET-DIFF-EXIT: 0` (neither runsettings file, `coverage.config` nor the project file changed); `RUNSETTINGS-HASH-NOW` equals the P0-T4 hash. Scoped runs under Workers 0 / ClassLevel passed twice (P1-T7, P2-T5). | +| AC6 | PASS | Eleven records `evidence/regression-testing/mutation-*.md` (C1 to C11), one per rewritten test (7 PFS tests, 4 retargeted DIW tests; the constructor test and the three Moq tests were not rewritten, as the P1-T4 edit spans show). Each: production mutation compiled (`PROD_CSC_OUT_LINES: 2`), the predicted test failed with the predicted phrase (all eleven `MESSAGE` lines read), revert with `REVERT-DIFF-EXIT: 0`, hash equal to the `PRE-EDIT-HASH-` anchor, confirming pass. P1-T31: `PRODUCTION-DIFF-EXIT: 0`, porcelain empty, five hashes equal anchors before Phase 2. P2-T10 re-confirms the three production hashes at closure. | +| AC7 | PASS | `evidence/other/root-walk-site-classification.2026-09-30T07-27.md`: four `SITE:` sections covering `RibbonControllerTests.cs` (legitimate repository-file read), `FSharpCoreHintPathAlignmentTests.cs` (legitimate repository-file read), `SortEmail_Tests.cs` string-only consumers (layout dependence without file-system I/O) and the `TrySaveAttachmentAsync` site (same defect class; follow-up F-3); every section `MODIFIED: NONE`. P2-T10 `OUT-OF-SET-DIFF-EXIT: 0` with a pathspec naming the three files; `UCS-TEST-CHANGED` excludes `SortEmail_Tests.cs`. | +| AC8 (amended) | PASS | Toolchain in order: `dotnet tool run csharpier check .` exit 0 (P2-T2); analyzer rebuild exit 0 (post-merge P2-T7a, `SKIP_CORECOMPILE_LINES: 0`); TreatWarningsAsErrors rebuild exit 0 (post-merge P2-T7b); MSTest with coverage MEASUREMENT 3 7327/7327, `NEWLY-FAILING: NONE`. Per-file rule: the three `final3` FILE lines read `NOT-LOWER=True` on lines and branches, and the reviewer confirmed the same figures from the Cobertura class nodes. Floors: 85.33% lines (>= 80) and 79.73% branches (>= 75). The comparator's in-memory negative control reads `NOT-LOWER=False` on all six lowered variants with the document hash unchanged. Caveat (non-blocking, G-1 / CR-7): the format check was not re-run post-merge; the two changed files are hash-identical to the checked state and CI's format check runs on the PR head. | + +Assumptions recorded: + +- The AC8 amendment is accepted as authorized on the caller's instruction; the reviewer verified that the amendment text in issue.md line 71 and line 73 matches the plan's Revision Log entry A and that `coverage-final.md` preserves the superseded MEASUREMENT 1 and 2 records with their `AC8: NOT MET` outcome, so the history of the change is auditable. +- The merged-tree format state is delegated to CI; no local evidence exists for the merged files that arrived from origin/main. + +## Acceptance Criteria Check-off + +All eight AC items were already checked `[x]` in `issue.md` by the executor (P2-T12 to P2-T19). Every item evaluates PASS above, so no item is unchecked by this review and no new check-off is required. `issue.md` was not modified by the reviewer. + +### Acceptance Criteria Status + +- Source: `docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/issue.md` +- Total AC items: 8 +- Checked off (delivered): 8 +- Remaining (unchecked): 0 +- Items remaining: none + +## Summary + +- Verdict: **PASS**. Blocking findings: **0**. All eight acceptance criteria are supported by evidence that the reviewer re-verified against the source files, the committed evidence artifacts, the on-disk Cobertura documents and the worktree's git files. +- Non-blocking items carried to the orchestrator: CR-1 to CR-7 (code review), G-1 to G-3 (policy audit), follow-ups F-1 to F-3 (seam `SetAccessControl`; constructor null-guard branch tests for `PhysicalFileInfoAdapter`; promote the `SortEmail_Tests.cs` `TrySaveAttachmentAsync` root-walk site). +- PR-time gates: CI format check on the merged head; PR context regeneration by pr-author; `validate_evidence_locations.py`. +- No remediation-inputs artifact is produced. diff --git a/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/issue.md b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/issue.md new file mode 100644 index 000000000..ecd8f10cf --- /dev/null +++ b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/issue.md @@ -0,0 +1,78 @@ +# filesystem-wrapper-tests-open-repository-solution-file (Issue #940) + +- Date captured: 2026-09-29 +- Author: Dan Moisan +- Status: Promoted -> docs/features/active/filesystem-wrapper-tests-open-repository-solution-file/ (Issue #940) + +> Automation note: Keep the section headings below unchanged; the promotion tooling maps each of them into the GitHub bug issue template. + +- Issue: #940 +- Issue URL: https://github.com/drmoisan/TaskMaster/issues/940 +- Last Updated: 2026-09-30 +- Work Mode: minor-audit + +## Summary + +Several UtilitiesCS.Test file-system wrapper tests locate the repository root at run time and open or enumerate the real `TaskMaster.sln`. This is the same defect class that #931 (PR #939) fixed in `FileInfoWrapper_Tests`: the outcome of a unit test depends on repository layout and on whether another process holds the solution file open. + +## Environment + +- OS/version: Windows 11 (local) and windows-latest (CI) +- Python version: n/a (C# / MSTest, .NET Framework 4.8) +- Command/flags used: `Invoke-MSTestWithCoverage.ps1` (parallel run, Workers=0, Scope=ClassLevel) +- Data source or fixture: the repository's own `TaskMaster.sln` + +## Steps to Reproduce + +1. Hold `TaskMaster.sln` open with a share mode that excludes readers (for example a resident MSBuild node or an IDE). +2. Run `UtilitiesCS.Test` with the standard coverage route. +3. Alternatively, run the test assembly from a location whose ancestors contain no `TaskMaster.sln`. + +## Expected Behavior + +The wrapper tests verify wrapper delegation through test-owned fixtures or mocks, independent of repository layout and of other processes. + +## Actual Behavior + +- `UtilitiesCS.Test/HelperClasses/PhysicalFileSystemAdapters_Tests.cs` walks up to the repository root (line ~361), builds a `FileInfo` over `TaskMaster.sln` (line ~376), asserts it is enumerated (lines 110-129), and swallows `IOException` in places, which hides the contention instead of removing it. +- `UtilitiesCS.Test/HelperClasses/DirectoryInfoWrapper_Tests.cs` asserts that `TaskMaster.sln` is enumerated from the repository root (lines 60 and 79, root walk at line ~381). + +## Logs / Screenshots + +- [ ] Attached minimal logs or screenshot +- Snippet: see the PR #939 body, section Follow-ups, items 1 and 2. + +## Impact / Severity + +- [ ] Blocker +- [ ] High +- [x] Medium +- [ ] Low + +## Suspected Cause / Notes + +A unit test depends on environment state it does not control, which is the root cause recorded for #931. Other root-walk sites to triage under the same fix: `TaskMaster.Test/Ribbon/RibbonControllerTests.cs:429`, `TaskMaster.Test/Bootstrap/FSharpCoreHintPathAlignmentTests.cs:27`, and `UtilitiesCS.Test/EmailIntelligence/SortEmail_Tests.cs:405`. Some of these may legitimately read repository files, for example build-configuration alignment tests. Classify each one before changing it. Source: `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`. + +## Proposed Fix / Validation Ideas + +- [ ] Reuse the #931 pattern: exercise delegation through the internal `IFileInfo`/`IDirectoryInfo` seams with Moq, or through a test-owned stream opened with `FileShare.ReadWrite`. Use no temporary files. +- [ ] Remove the `IOException` swallowing. +- [ ] Negative control: show that each rewritten test fails against a deliberately broken wrapper. + +## Acceptance Criteria + +- [x] AC1: No test method in `UtilitiesCS.Test/HelperClasses/PhysicalFileSystemAdapters_Tests.cs` or `UtilitiesCS.Test/HelperClasses/DirectoryInfoWrapper_Tests.cs` locates the repository root or references `TaskMaster.sln`; both `GetRepositoryRoot` helpers and the `GetSolutionFile` helper are removed. +- [x] AC2: No test in either file catches `IOException` (or any broader exception type) to tolerate file-system contention. +- [x] AC3: No test in either file directs a mutating call (timestamp, attribute, read-only flag, access-control, create, copy, replace, move, or delete) at the repository root or at any tracked repository file or directory; each mutating member is exercised only against a Moq mock, a path under the test assembly's own output directory that is asserted not to exist before the call, or an existing entry of that output directory, the output directory itself, or its parent directory, on which the call is a no-op by construction. +- [x] AC4: Each wrapper or adapter member previously exercised by a rewritten test is still exercised, and its delegation is asserted against a Moq mock, a test-owned read-only fixture, or a documented non-existent-path outcome; a member whose effect on an existing test-owned entry is a no-op by construction (`Create`, `Refresh`, and `SetAccessControl` with an unmodified security object) is instead exercised against that entry and must complete without an exception; so that coverage of the changed wrapper and adapter lines does not regress. +- [x] AC5: No temporary file or directory is created by any test in either file, and no test introduces `DoNotParallelize`, a worker-count or scope change, a retry, or a sleep. +- [x] AC6: A negative-control run shows that each rewritten test fails when the wrapper or adapter delegation it verifies is deliberately broken, and the control change is reverted before the final QC pass. +- [x] AC7: The three additional root-walk sites named under Suspected Cause (`RibbonControllerTests.cs`, `FSharpCoreHintPathAlignmentTests.cs`, `SortEmail_Tests.cs`) are each classified in an evidence artifact as either a legitimate repository-file read or an instance of this defect class; none is modified by this change. +- [x] AC8: The C# toolchain passes in order (CSharpier check, analyzer rebuild, TreatWarningsAsErrors rebuild, MSTest with coverage) with no new failures relative to the baseline, with covered lines and covered branches not lower than the baseline in each of the three changed wrapper and adapter files (PhysicalDirectoryInfoAdapter.cs, PhysicalFileInfoAdapter.cs, DirectoryInfoWrapper.cs), and first-party line coverage at least 80 percent and branch coverage at least 75 percent. + +Note (2026-09-30): AC8 amended per coordinator ruling (option 1) during P2-T7: the package-level and repository-level not-lower comparison was replaced by the per-file no-regression rule for the three changed files, lines and branches, after two identical measurements showed run-to-run variance in files this item does not touch. + +## Next Step + +- [x] Promote to GitHub issue (bug-report template) +- [ ] Move to active fix folder / branch diff --git a/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/plan.2026-09-29T23-02.md b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/plan.2026-09-29T23-02.md new file mode 100644 index 000000000..d72e5a526 --- /dev/null +++ b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/plan.2026-09-29T23-02.md @@ -0,0 +1,1303 @@ +# 2026-09-29-filesystem-wrapper-tests-open-repository-solution-file (Plan) + +- **Issue:** #940 +- **Parent (optional):** none (sibling of #931 / PR #939; parallel run bugs-2026-09-28) +- **Owner:** drmoisan +- **Last Updated:** 2026-09-30 (date-only: the planning session has no shell and cannot read a clock) +- **Status:** Ready for preflight, revision round 3 (execution ran P0-T1 through P2-T6 with every box checked; P2-T7 stopped with `AC8: NOT MET` under the former package-level comparison; the coordinator AC8 ruling of 2026-09-30, option 1, is applied to D-7, D-8, the Command Reference, P2-T7, P2-T9, P2-T10, P2-T15, P2-T19, P2-T20 and the AC8 mapping, and the orchestrator's post-P2-T6 merge of origin/main is re-anchored, both recorded in the Revision Log; the ten round-1 and the eight round-2 preflight defects remain applied; planner internal review passed; executor validation-only preflight pending) +- **Version:** 1.3 +- **Work Mode:** minor-audit (persisted marker `- Work Mode: minor-audit` at line 12 of FEATURE/issue.md; the `## Acceptance Criteria` section of FEATURE/issue.md at line 62 is the sole acceptance-criteria source, AC1 through AC8 at lines 64 to 71; no spec.md, user-story.md or research.md is required, and none exists except the research record named below, which is supporting material and not a requirements source) +- **Requirements source:** `docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/issue.md`, section `## Acceptance Criteria` +- **Supporting research:** `docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/research/2026-09-29T21-10-filesystem-wrapper-tests-open-repository-solution-file-research.md` (sections 1, 2, 4.3, 4.4, 5, 6 and 9 are consumed; every citation it carries was re-derived against the assigned worktree during authoring, see the Verified Repository Facts) +- **Branch:** bug/filesystem-wrapper-tests-open-repository-solution-file-940 (cut from origin/main) +- **Base:** origin/main. Every diff and footprint gate in this plan is anchored to the ref `origin/main`, never to a SHA pinned by this document. P0-T3 fetches once, records the merge base and, when origin/main has advanced past it, reconciles by exactly one deterministic rule (a fast-forward when the branch carries no commit of its own; a merge commit of origin/main when every path the branch's own commits touch lies under docs/features/ or .claude/agent-memory/, which is the state the orchestrator's preparation commit leaves; otherwise `ORIGIN MAIN ADVANCED`, stop; `git rebase` and `git reset` are never run), so that after P0-T3 the merge base equals the tip of origin/main and two-dot worktree gates use the recorded ANCHOR-SHA and three-dot committed gates use origin/main...HEAD, so both compare against the tree P0-T3 reconciled to throughout the run. Post-merge amendment (2026-09-30, Revision Log round 3 entry B): after P2-T6 the orchestrator merged origin/main into the branch (merge commit 40e587ce20bbd41cd6915707271faae937f1a8e2; first parent 97261cb4a1934af9887c1278adcd4b35f6d575bd, the P2-T6 head; second parent 66afa6372fd82fc1ffd7c81f85a1ad65eebc5817, the merged origin/main tip), so the merge base of HEAD and origin/main is now that second parent (P2-T7 records it as `ANCHOR-SHA-2:` and P2-T7, P2-T10 and P2-T20 re-read it as `MERGE-BASE-NOW:`) and stays there for the rest of the run because no task merges again. Three-dot origin/main...HEAD spans therefore list only this branch's own changes and cannot list a path main gained through the merge, and every remaining unexecuted two-dot worktree span (P2-T10 and `CMD-ADDED-LINES`) substitutes ANCHOR-SHA-2; P2-T7 proves by `git diff --exit-code ANCHOR-SHA ANCHOR-SHA-2 -- UtilitiesCS UtilitiesCS.Test` that the two anchors agree on both source trees, which is what keeps the `PRE-EDIT-HASH-` anchors, the Token Census Expectations and the Target Source C citations valid after the merge. The checked tasks P1-T31 and P2-T1 ran before the merge against ANCHOR-SHA and are not re-run. +- **Execution session requirement:** the executor runs later, non-isolated, from the item worktree, with `pwsh` available (an isolated agent is refused `pwsh` in every form). 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` 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 20). The checkpoint present in the worktree at authoring time is the preparation checkpoint (`route_id` `preparation`, `preparation_mode` `true`); the execution orchestrator re-seeds it before the executor starts, so this plan gates only on the readiness fields the hook reads (a present `route_id` or `path_selected`, whatever its value; `lifecycle_ready`; `issue-num`; `feature-folder`) and never on the `route_id` value, on `preparation_mode`, on `next_step` or on any other field of the preparation checkpoint. P0-T3 records the checkpoint fields read-only and stops with `PRE-IMPLEMENTATION GATE NOT SEEDED` when the readiness fields are absent; a PreToolUse refusal at any later `.cs` edit or non-exempt git span is `PRE-IMPLEMENTATION GATE BLOCKED`, reported verbatim, and stops the run. The executor never creates, edits or works around the checkpoint. +- **Task Count:** 64 (Phase 0: 13, Phase 1: 31, Phase 2: 20) + +**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 from the delegation; encoded below) + +- Write Set: only `UtilitiesCS.Test/HelperClasses/PhysicalFileSystemAdapters_Tests.cs` and `UtilitiesCS.Test/HelperClasses/DirectoryInfoWrapper_Tests.cs` plus evidence and the two feature documents under FEATURE/. No `.csproj` edit. No production file is changed in the final diff: the three production files the negative controls mutate temporarily (Target Source C) are reverted by `git checkout` and proven byte-identical to origin/main as fetched by P0-T3 (ANCHOR-SHA for the two-dot form of P1-T31; ANCHOR-SHA-2, the merged tip, for the two-dot forms of P2-T10 after the post-P2-T6 merge) by an anchored `git diff --exit-code -- ` span plus SHA-256 equality with the Phase 0 hash (P1-T31, P2-T10). +- The three other root-walk sites (TaskMaster.Test/Ribbon/RibbonControllerTests.cs, TaskMaster.Test/Bootstrap/FSharpCoreHintPathAlignmentTests.cs and UtilitiesCS.Test/EmailIntelligence/SortEmail_Tests.cs, including its `TrySaveAttachmentAsync` site) are classified in one evidence artifact (P1-T1) and are never modified; the executor creates no potential entry and no issue. +- Every diff or footprint gate anchors to `origin/main` as fetched by P0-T3 (ANCHOR-SHA for two-dot forms; after the orchestrator's post-P2-T6 merge of origin/main, ANCHOR-SHA-2, the merged tip and current merge base, for the two-dot forms of P2-T10 and `CMD-ADDED-LINES`, Refs); every name-listing diff is paired with a `git status --porcelain` span in the same task (G8b); every `git diff` carries a ref operand (G8). +- C# gates use the CLAUDE.md commands in their exact form (Command Reference); scoped runs use direct `vstest.console.exe` with an asserted expected `total`, because vstest exits 0 on a zero-match filter. Write-mode commands record a success-case observation beyond the exit code (G7). Coverage figures are read from the runner's printed `First-party coverage:` line, from the JaCoCo package projection and, for the per-file rule of D-7, from the `hits` and `condition-coverage` attributes of the post-processed Cobertura documents. +- Evidence paths resolve under FEATURE/evidence/baseline/, FEATURE/evidence/regression-testing/, FEATURE/evidence/qa-gates/ and FEATURE/evidence/other/ only, each command-step artifact carrying `Timestamp:`, `Command:`, `EXIT_CODE:` and `Output Summary:`. Committed test evidence is the JaCoCo package projection, the one-line first-party summary and TRX-derived summaries only; no raw Cobertura document and no TRX is added to git in any form. No committed text carries an absolute host path, an account name or a host name; the placeholders ``, ``, `` and `` are used instead, and P2-T11 sweeps the feature folder including this plan before the final commit; P2-T20 additionally runs the repository hygiene guard scripts/hygiene/Test-RepositoryHygiene.ps1 (the script .github/workflows/_hygiene.yml runs in CI, fact 16) over the staged tree before committing, so an artifact CI would reject is caught before it is committed. +- Prohibitions carried into task text: no temporary file or directory; no `DoNotParallelize`; no change to `Workers` or `Scope`; no retry; no sleep; no `catch` of `IOException` or of any broader exception type; a mismatched exception type is fixed by pinning the observed type in the one assertion that observed it (P1-T7), never by widening a `Throw` generic argument to a base type and never by catching. +- Coverage (coordinator ruling of 2026-09-30, option 1): for each of the three adapter and wrapper files the covered-line count and the covered-branch count of the gating measurement are not lower than this item's own Phase 0 baseline (D-7, D-8; the gating measurement is the post-merge MEASUREMENT 3 of P2-T7, and no re-measurement follows a lower reading), and the repository first-party line rate is at least 80 percent with branch rate at least 75 percent (CLAUDE.md UT2 floors, which rank above the 85 percent figure in .claude/rules/general-unit-test.md under the policy compliance order; the 85 percent figure is recorded as an observation). The UtilitiesCS package rates and the repository first-party rates are recorded against the baseline in a variance table as observations, never as gates. +- The 500-line ceiling is gated on both test files after the repository-wide format (P2-T8), and the determinism census tokens of research section 9 are named-count gates in P0-T12, P1-T3, P1-T4, P1-T5, P2-T8 and P2-T10. + +## Execution Conventions + +- `FEATURE` denotes `docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940`. `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. Every `git` command in this plan is written in its canonical form; the executor issues it as `git -C WORKTREE ` per its Bash discipline, and `Command:` fields record the canonical form. +- **Evidence paths.** Every artifact is written under FEATURE/evidence/baseline/, FEATURE/evidence/regression-testing/, FEATURE/evidence/qa-gates/ or FEATURE/evidence/other/. Nothing is written to artifacts/baseline/, artifacts/baselines/, artifacts/qa/, artifacts/qa-gates/, artifacts/evidence/, artifacts/coverage/, artifacts/regression-testing/ or artifacts/post-change/. The orchestrator-owned checkpoint under artifacts/orchestration/ is read by P0-T3 and never written. +- **Artifact filenames.** Fixed names without a timestamp suffix: phase0-instructions-read.md, test-run-baseline.md and coverage-baseline.md under baseline/; fail-before-exception..md (its `` equals its own `Timestamp:` field), test-run-final.md and the eleven `mutation-.md` records of the Control Table under regression-testing/; root-walk-site-classification..md under other/; coverage-final.md and toolchain-pass.md under qa-gates/. 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 P1-T5 through P1-T7 pinning iteration, and the Phase 2 restart from P2-T1) writes one new artifact per iteration carrying an `ITERATION:` line (1 for the first run); every later comparison reads the artifact with the highest `ITERATION:` value. +- **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")`, by absolute script path resolved at run time; `pwsh -WorkingDirectory` is never used because it does not resolve the `-File` path. A 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. +- **Long-running payloads.** The repository-wide coverage payloads (`CMD-COVERAGE-RUNNER` and `CMD-COVERAGE-DIRECT`, run by P0-T11, P2-T6 and P2-T7) can run longer than the ten-minute limit of a foreground tool call. The executor starts each of them as a background invocation of the same `pwsh -NoProfile -Command ''` form, whose captured console output is the payload's own stdout (the `Tee-Object` log under coverage\logs\ holds only the child's transcript, not the payload's `Write-Output` lines), and polls that captured output with read-only tool calls until the payload's final `TRX_PRESENT:` line appears; every `_LINE`, `_EXIT_CODE:`, `_PRESENT:`, `ASSEMBLY` and `SEQUENCE_FILES:` value is transcribed from that captured output. No sleep statement is added to any payload; the polling cadence is the executor's, not the plan's. The 120-minute stall bound of the RUNNER route (Stall handling) is measured from the start of the background invocation, and a payload still running at that bound is `COVERAGE RUN STALLED`. +- **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 91 and following). 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 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 (#931 D-16; a resident node-reuse worker was the process that held the solution file in the #900 run). The switch changes no diagnostic, target or property; the `Command:` field records the CLAUDE.md-canonical command and the note `plus /nodeReuse:false`. The restore script (P0-T5) receives the same instruction through the environment variable MSBUILDDISABLENODEREUSE set to 1 in its payload. +- **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\940\\ (git-ignored twice, by the `*.trx` pattern at .gitignore line 146 and by the `coverage/*` pattern at line 150; coverage/.gitkeep is tracked, so the coverage directory always exists and only its subdirectories can be absent) 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 scripts/vscode/TaskMaster.cli.runsettings (Workers 0, Scope ClassLevel, lines 4 through 7; no data collector; the same file the coverage route passes to its inner vstest) with the isolation switch; its SHA-256 is recorded once by P0-T4 as `RUNSETTINGS-HASH:` and re-read by every run as `RUNSETTINGS-HASH-NOW:`. TaskMaster.runsettings (30 lines, the same Workers and Scope plus a Code Coverage collector) is read by nothing in this plan and never written. +- **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, and they include occurrences inside comments and string literals because the block reads the raw file text; every `pre` value of the Token Census Expectations table was derived the same way (the PFS `AppDomain`, `Assembly.Location`, `FileShare.None` and `FileShare.ReadWrite` values each include comment occurrences, fact 2). `Select-String` is never used for a count because it is case-insensitive by default and counts lines. `LINES` is `@(Get-Content -LiteralPath $path).Count`. +- **Refs.** P0-T3 records `BASE-SHA:` (HEAD before any task edits a file), `ORIGIN-MAIN-SHA:` (`git rev-parse origin/main` after `git fetch origin main`) and `MERGE-BASE:` (`git merge-base HEAD origin/main`), and requires `ORIGIN-MAIN-SHA:` equal to `MERGE-BASE:`. P1-T8 records `FIX-HEAD:`, `FIX-HASH-PFS:` and `FIX-HASH-DIW:`, each taken after that task's line-ending restore (fact 22), so that the bytes a later `git checkout` of the path writes under `core.autocrlf=true` equal the anchored bytes. P0-T4 records `PRE-EDIT-HASH-PDA:`, `PRE-EDIT-HASH-PFA:` and `PRE-EDIT-HASH-DIWP:` for the three production files; those are the revert anchors of the production-side controls because no task ever commits a production edit. Command spans write the bare tokens; the executor substitutes the recorded values, because no shell variable survives a task boundary. ANCHOR-SHA is the final `ORIGIN-MAIN-SHA:` value P0-T3 recorded (after reconciliation), which P0-T3 also writes as its own `ANCHOR-SHA:` line; every two-dot worktree span writes the bare token ANCHOR-SHA and the executor substitutes that value, because refs/remotes/origin/main is shared by every worktree and a sibling session's fetch can advance it during the run; three-dot origin/main...HEAD spans are unchanged. ANCHOR-SHA-2 is the value P2-T7 records as `ANCHOR-SHA-2:` (`git rev-parse 40e587ce20bbd41cd6915707271faae937f1a8e2^2`, the second parent of the orchestrator's post-P2-T6 merge commit, which is the merged origin/main tip and the current merge base of HEAD and origin/main); every two-dot worktree span of P2-T10 and the `CMD-ADDED-LINES` block write the bare token ANCHOR-SHA-2 and the executor substitutes that value. P2-T7 gates `MERGE-UCS-DIFF-EXIT: 0` (the ANCHOR-SHA and ANCHOR-SHA-2 trees are identical under UtilitiesCS/ and UtilitiesCS.Test/), which is the condition under which the `PRE-EDIT-HASH-` and `FIX-HASH-` anchors taken against ANCHOR-SHA remain the anchors of the post-merge gates. +- **Inherited paths (rule, not list).** Clause A: every path already changed relative to origin/main before the first task ran, captured mechanically by P0-T3 as `INHERITED-CLAUSE-A:` (the union of `git diff --name-only origin/main...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. +- **Commits.** This plan commits three times (P0-T13, P1-T8 and P2-T20, the last followed by two amends), each as `git add -- ` followed by a separate `git commit` invocation, one command segment per invocation, never chained. Every `git commit` invocation in this plan, including the two `--amend` invocations of P2-T20, names its pathspecs after `--`, so it commits the working-tree state of exactly those paths and a path another session staged in the shared index (a sibling item's promotion record, an agent-memory file) is neither swept into the commit nor unstaged by it; the promoted record docs/features/potential/promoted/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file.md is committed by the orchestrator's preparation commit before execution starts, and every committing task records `git status --porcelain -- docs/features/potential` as `POTENTIAL-PORCELAIN:` and requires `EMPTY`, and records `git diff --cached --name-only` taken before its own `git add` as `PRE-STAGED:` (verbatim or `NONE`) without acting on it. 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). 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). P1-T8 and P2-T20 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; P2-T10 records the anchored committed diff of those two trees as `COMMITTED-CLAUDE-OR-CONFIG:` and requires `NONE`. Every `git` pathspec uses forward slashes; no `git` argument contains a backslash. +- **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`; `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 four-class exclusion appended, because the runner hard-codes its filter at scripts/vscode/Invoke-MSTestWithCoverage.ps1 line 91 and offers no extension point). Every direct vstest run and every DIRECT-route run carries the hang-dump blame argument so a stalled test is named in a `Sequence_*.xml` document rather than left silent. The RUNNER route passes no blame argument (the runner's argument list is fixed), so P0-T11, P2-T6 and P2-T7 bound it by wall clock instead: a RUNNER run still in progress after 120 minutes is `COVERAGE RUN STALLED`, stop and report. P2-T7 additionally runs `CMD-FOREIGN-PROCESS-PROBE` before its build and coverage commands and starts none of them while a vstest.console, testhost or dotnet-coverage process whose command line is not rooted in this worktree is running (a sibling item's test run holds build outputs and would race the collector); the probe is re-invoked at the executor's cadence with no sleep in the payload, bounded at 120 minutes (`FOREIGN TEST RUN STALLED`). +- **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 (`total` 1); 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 anchor (`FIX-HASH-PFS:` or `FIX-HASH-DIW:` for a test file, taken by P1-T8 after the line-ending restore; the P0-T4 `PRE-EDIT-HASH-` value for a production file, which is never edited between P0-T4 and the control and is outside the scoped P1-T5 format); rebuild; run the same filter again and record the passing run. The fix commit P1-T8 precedes every control so that `git checkout` restores exactly the committed fixed state. The prediction rule is D-6: any other failing assertion, or a passing control, is `MUTATION PREDICTION MISMATCH`, stop and report the message text; never adjust the test. +- **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 files (two; the only files the final diff changes outside FEATURE/): + +- `UtilitiesCS.Test/HelperClasses/PhysicalFileSystemAdapters_Tests.cs` (modify: rewritten to Target Source A; seven test methods, no root walk, no `catch`) +- `UtilitiesCS.Test/HelperClasses/DirectoryInfoWrapper_Tests.cs` (modify: rewritten to Target Source B; eight test methods, four retargeted, no root walk) + +Evidence directories (four; created by this run where absent): FEATURE/evidence/baseline/, FEATURE/evidence/regression-testing/, FEATURE/evidence/qa-gates/, FEATURE/evidence/other/. A file the orchestrator placed under FEATURE/evidence/other/ before execution (its preflight-clearance record) is kept; it is either already committed or is committed by P0-T13 with the rest of the feature tree, and P2-T11 sweeps it with every other feature-folder file. + +Feature documents whose checkbox state this run edits (no criterion text or plan text change by the executor): FEATURE/issue.md (the eight AC lines) and FEATURE/plan.2026-09-29T23-02.md (this file; task check-offs). The planner, not the executor, amended the AC8 text of FEATURE/issue.md on 2026-09-30 under the coordinator ruling (D-7) and added the dated note below the AC list; the box-state inventory regex of P0-T2 still matches exactly eight lines, all unchecked at the time of that edit. + +## Outside the Write Set (plain prose; none of these paths is modified by the final diff) + +Production files, never changed in the final diff, each mutated temporarily by named controls and reverted by the following task: UtilitiesCS/HelperClasses/FileSystem/PhysicalDirectoryInfoAdapter.cs (controls C1, C2, C3, C4, C8), UtilitiesCS/HelperClasses/FileSystem/PhysicalFileInfoAdapter.cs (C5, C6, C7) and UtilitiesCS/HelperClasses/FileSystem/DirectoryInfoWrapper.cs (C9, C10, C11). Every other file under UtilitiesCS/, including FileInfoWrapper.cs and the three interfaces under UtilitiesCS/Interfaces/IHelperClasses/. + +The three classified root-walk sites: TaskMaster.Test/Ribbon/RibbonControllerTests.cs, TaskMaster.Test/Bootstrap/FSharpCoreHintPathAlignmentTests.cs, UtilitiesCS.Test/EmailIntelligence/SortEmail_Tests.cs. The project file UtilitiesCS.Test/UtilitiesCS.Test.csproj (both Write Set files are already registered at its lines 232 and 233; no new file is created, so no registration changes). UtilitiesCS.Test/HelperClasses/FileInfoWrapper_Tests.cs (the #931 rewrite, used as the pattern precedent and never edited). + +Configuration read but never written: TaskMaster.runsettings, scripts/vscode/TaskMaster.cli.runsettings, coverage.config, every script under scripts/vscode/, the .claude directory, config/, artifacts/orchestration/orchestrator-state.json. + +Local, git-ignored, never staged: everything under coverage/ except coverage/.gitkeep (.gitignore lines 150 to 151), every trx document and every cobertura-named XML document (lines 146 to 147), packages/ (line 197, pattern `**/[Pp]ackages/*`), .dotnet-sdk/ (line 356, pattern `.dotnet*/`), artifacts/ (line 57), every bin and obj directory. + +## Verified Repository Facts + +Each fact was re-derived against the assigned worktree on 2026-09-29 during plan authoring with the Read, Grep and Glob tools. No shell was available in the planning session, so every git-state fact (branch, cleanliness, line endings) is re-observed by Phase 0 rather than asserted here; the one git fact stated below (fact 19) was read from the worktree's own `.git` reference file. + +1. `UtilitiesCS.Test/HelperClasses/PhysicalFileSystemAdapters_Tests.cs` (alias PFS) is 388 lines by a line-start count (the Read tool renders a trailing 389 for the terminating newline). `[TestClass] public class PhysicalFileSystemAdapters_Tests` at 12 to 13; four `[TestMethod]` members at 15, 137, 169, 313 (declarations 16, 138, 170, 314). `GetRepositoryRoot()` helper 349 to 371 (walk-up from `Assembly.Location` 354 to 356, `TaskMaster.sln` probe 361, `Should().NotBeNull` 367 to 369); `GetSolutionFile()` 373 to 377 (`TaskMaster.sln` at 376); `UnsupportedInfo` 379 to 386; class close 387, namespace close 388. Using block 1 to 8 (`System`, `System.IO`, `System.Reflection`, `System.Runtime.Serialization`, `System.Security.AccessControl`, `FluentAssertions`, `Microsoft.VisualStudio.TestTools.UnitTesting`, `UtilitiesCS.HelperClasses.FileSystem`); no `using System.Linq;`, no `using Moq;`, no nullable directive; the null-forgiving `)!;` at 149 compiles today without a nullable context. +2. In PFS: `GetRepositoryRoot()` calls at 19, 142, 155, 317 and 375 plus the declaration at 349 (six occurrences of the token `GetRepositoryRoot`); `GetSolutionFile` at 173, 318 and 373 (three); `catch (IOException)` at 43 and 195 (two; the only `catch` tokens in the file); `TaskMaster.sln` on lines 110, 111, 114, 116, 126, 127, 128, 129, 206, 211, 237, 361 and 376 (at least one, the positive control for the zero gate); `AppDomain` at 352 (comment) and 356 (code), two occurrences; `File.Exists(` at 361 (one); `Assembly.Location` at 351 (comment) and 259, 265, 271 and 355 (code), five; `FileShare.None` at 207, 240 and 246 (three, every one inside a comment); `FileShare.ReadWrite` at 203 (comment) and 217, 262, 268 and 274 (code), five; `adapter.IsReadOnly = adapter.IsReadOnly;` at 200 (one); `Thread.Sleep`, `Task.Delay`, `[Timeout`, `DoNotParallelize`, `Retry`, `Path.GetTemp`, `File.Create`, `File.WriteAll`, `Directory.CreateDirectory`, `RootedFixturePath`, `OwnedAssemblyFile`, `OwnedAssemblyDirectory`, `MissingOwnedPath(`, `__940_missing_`, `Exists.Should().BeFalse()`, `using System.Linq;` 0 each (grep over the tree: none of the six new identifiers occurs in any `.cs` file). The stale comment at 182 to 185 claims the setters target the test DLL while the code targets the solution file. Real mutations performed today: the `Attributes` setter (29) and six timestamp setters (36 to 41) on the repository root, `Create()` and `Create(security)` (48 to 49) and `CreateSubdirectory("UtilitiesCS", ...)` (51 to 55) on the repository root, `SetAccessControl` (92) on the root, the six timestamp setters (188 to 193), `IsReadOnly` (200) and `SetAccessControl` (287) on `TaskMaster.sln`, and `CopyTo` and `Replace` naming the solution file as destination (324 to 338). +3. `UtilitiesCS.Test/HelperClasses/DirectoryInfoWrapper_Tests.cs` (alias DIW) is 393 lines by a line-start count. Eight `[TestMethod]` members at 16, 26, 42, 63, 82, 96, 186, 331 (declarations 17, 27, 43, 64, 83, 97, 187, 332). The four affected tests: `Properties_ShouldMirrorWrappedDirectoryInfo` 27 to 40 (`GetRepositoryRoot()` at 30), `GetDirectoriesAndGetFiles_ShouldReturnWrappedEntries` 43 to 61 (46; `"UtilitiesCS"` 56; `"TaskMaster.sln"` 60), `EnumerateFileSystemInfos_ShouldWrapDirectoriesAndFiles` 64 to 80 (67; 76; 79), `ToString_ShouldDelegateToWrappedDirectoryInfo` 83 to 94 (86). Unaffected Moq-based tests 97 to 373. `GetRepositoryRoot()` helper 375 to 391 (walk-up from `AppDomain.CurrentDomain.BaseDirectory` 377, `TaskMaster.sln` probe 381). Using block 1 to 9 includes `System.Linq` (3) and `Moq` (8). Pre-edit counts: `GetRepositoryRoot` 5 (30, 46, 67, 86, 375), `TaskMaster.sln` 3 (60, 79, 381), `AppDomain` 1, `File.Exists(` 1, `catch` 0, `Assembly.Location` 0, `[TestMethod]` 8, `C:\Repo` 2 (125, 153; mock `FullName` values), and 0 for every determinism token and every new identifier listed in fact 2. +4. UtilitiesCS/HelperClasses/FileSystem/PhysicalDirectoryInfoAdapter.cs (alias PDA) is 217 lines, `#nullable enable` at 1, `using System;` at 2, `using System.Linq;` at 5; `internal sealed class PhysicalDirectoryInfoAdapter(DirectoryInfo directoryInfo) : IDirectoryInfo` at 11; `CreationTime` setter `set => _directoryInfo.CreationTime = value;` at 25; `public bool Exists => _directoryInfo.Exists;` at 34; `public string FullName => _directoryInfo.FullName;` at 38; `public string Name => _directoryInfo.Name;` at 64; `Parent` 70; `Root` 72; `Create()` 74; `Create(security)` 76 to 77; `CreateSubdirectory` 79 to 85; `public void Delete() => _directoryInfo.Delete();` at 87; `Delete(bool)` 89; enumerations 91 to 135; `GetAccessControl` 137 to 140; `public IFileInfo[] GetFiles() =>` at 160 with its body `_directoryInfo.GetFiles().Select(file => new FileInfoWrapper(file)).ToArray();` at 161; `GetObjectData` 190; `MoveTo` 193; `Refresh` 195; `SetAccessControl` 197 to 198; `ToString` 200; `WrapFileSystemInfo` 202 to 215 (throws `ArgumentException` at 214). Pre-edit counts: `!_directoryInfo.Exists` 0, `Array.Empty()` 0, `set { }` 0, `public void Delete() { }` 0, `_directoryInfo.FullName` 1. +5. UtilitiesCS/HelperClasses/FileSystem/PhysicalFileInfoAdapter.cs (alias PFA) is 183 lines, `#nullable enable`; public constructor 25 to 32; internal delegate-seam constructor 34 to 48; `IsReadOnly` 110 to 114 with `set => _fileInfo.IsReadOnly = value;` at 113; `public long Length => _fileInfo.Length;` at 116; `AppendText` 118; `CopyTo` 120 to 124; `Delete` 132; `GetAccessControl` 136 to 139; `public void MoveTo(string destFileName) => _fileInfo.MoveTo(destFileName);` at 144; `Open` 146 to 152 (the three-argument overload 151 to 152 is unseamed); `OpenRead` 154; `OpenText` 156; `OpenWrite` 158; `Replace` 162 to 176; `SetAccessControl` 178 to 179; `ToString` 181. Pre-edit counts: `set { }` 0, `public long Length => 0;` 0, `public void MoveTo(string destFileName) { }` 0. +6. UtilitiesCS/HelperClasses/FileSystem/DirectoryInfoWrapper.cs (alias DIWP) is 246 lines, `#nullable enable`, `using System;` at 2, `using System.Linq;` at 5; public constructor 15 to 20 wraps a `PhysicalDirectoryInfoAdapter`; internal `(IDirectoryInfo)` constructor 22 to 26; `EnumerateFileSystemInfos()` 143 to 146 with `return _directoryInfo.EnumerateFileSystemInfos();` at 145; `GetFiles()` 186 to 189 with `return _directoryInfo.GetFiles();` at 188; `ToString()` 226 to 229 with `return _directoryInfo.ToString();` at 228. Pre-edit counts: `Array.Empty()` 0, `Enumerable.Empty()` 0, `return string.Empty;` 0. +7. UtilitiesCS/Properties/AssemblyInfo.cs grants `InternalsVisibleTo` to `DynamicProxyGenAssembly2`, `UtilitiesCS.Test` and `ToDoModel.Test` (lines 18 to 20), so the internal adapters and the internal constructors are reachable from UtilitiesCS.Test without reflection. UtilitiesCS/Interfaces/IHelperClasses/IFileInfo.cs line 26 declares `FileStream OpenRead();`. UtilitiesCS/HelperClasses/FileSystem/FileInfoWrapper.cs internal seam constructor 21 to 24. UtilitiesCS/UtilitiesCS.csproj declares `OutputType` Library (12), `AssemblyName` UtilitiesCS (15) and no `TreatWarningsAsErrors` property (the only occurrence of that word is a comment at 1315), so a plain Build-target build of a mutated production file compiles whatever analyzer suggestion the mutation raises. +8. UtilitiesCS.Test/UtilitiesCS.Test.csproj: Platform default `AnyCPU` at 11; OutputPath `bin\Debug\` at 51 (`bin\x86\Debug\` at 66 for x86); `Compile Include` for DirectoryInfoWrapper_Tests.cs at 232, PhysicalFileSystemAdapters_Tests.cs at 233, FileInfoWrapper_Tests.cs at 234; `ProjectReference` to UtilitiesCS.csproj at 963. Because the output directory is at least two levels deep under the project directory, the owned directory's `Parent` is non-null under every configured build. UtilitiesCS.Test/packages.config pins FluentAssertions 8.11.0 (line 9), Moq 4.21.0 (65), MSTest.TestAdapter and MSTest.TestFramework 4.4.1 (67 to 68). +9. UtilitiesCS.Test/HelperClasses/FileInfoWrapper_Tests.cs (357 lines) is the #931 precedent: `private const string FixturePath = @"C:\Repo\fixture.sln";` at 18 with its comment 15 to 17; the three metadata tests 31 to 44, 47 to 58, 88 to 99 mirror path computations against the BCL object; `OpenRead_ShouldReturnReadableStreamForWrappedFile` 61 to 85 opens `typeof(FileInfoWrapper_Tests).Assembly.Location` read-only with `FileShare.ReadWrite` as the sentinel (68 to 73). The #931 fix changed no production file (its coverage-final record: `CHANGED-PRODUCTION-LINES: 0`). +10. The two class names are unique in the tree: `class PhysicalFileSystemAdapters_Tests` occurs only at PFS line 13 and `class DirectoryInfoWrapper_Tests` only at DIW line 14 (grep over every `.cs`), so a `FullyQualifiedName~HelperClasses.PhysicalFileSystemAdapters_Tests` or `~HelperClasses.DirectoryInfoWrapper_Tests` filter selects exactly that class. No test in UtilitiesCS.Test carries a `LiveOutlook` category (#931 fact 16), so scoped runs need no category filter. +11. The three AC7 sites, re-read: TaskMaster.Test/Ribbon/RibbonControllerTests.cs 297 to 311 (`RibbonFolderOperations_DoNotConstructThrowawayFolderTrees` reads the production source TaskMaster/Ribbon/RibbonController.FolderTree.cs with `File.ReadAllText` at 299 and asserts substrings 308 to 310) with `FindRepositoryRoot()` 422 to 437 (walk-up from the production assembly's `Assembly.Location` 425, `TaskMaster.sln` probe 429, throws `InvalidOperationException` at 436 when no ancestor holds it). TaskMaster.Test/Bootstrap/FSharpCoreHintPathAlignmentTests.cs: class remarks 11 to 21 state the design (reads the project files directly); `SolutionFileName = "TaskMaster.sln"` at 27; `FindRepositoryRoot()` 47 to 67 (walk-up from `AppDomain.CurrentDomain.BaseDirectory` 49, throws at 60 to 66); `DiscoverFSharpCoreHintPaths()` 83 onward enumerates directories under the root skipping dot-prefixed names and the `packages`, `bin`, `obj`, `node_modules` names (29 to 35, 101 to 107); consumers at 88, 156, 181. UtilitiesCS.Test/EmailIntelligence/SortEmail_Tests.cs: `GetRepositoryRoot()` 393 to 415 (walk-up from `Assembly.Location` 399, probe 405); consumers at 196 (`GetAttachmentsInfo`), 223 (`GetAttachmentsInfoAsync`), 241 (`TrySaveAttachmentAsync_WhenSaveSucceeds_ReturnsTrueAndCallsSaveAsFile`, 237 to 249, which passes `Path.Combine(root, "saved.txt")` to the production method), 255 (`SaveMessageAsMsgAsync`), 280 (`SaveMessageAsMSG`). UtilitiesCS/EmailIntelligence/EmailParsingSorting/SortEmail.cs: `[ExcludeFromCodeCoverage]` at 888 above `internal static async Task TrySaveAttachmentAsync(` at 889, and `System.IO.Directory.CreateDirectory(Path.GetDirectoryName(filePathSave));` at 896 (a real creation API invoked on the repository root, a no-op only because the root exists). +12. TaskMaster.runsettings (30 lines): `Workers` 0 at 5, `Scope` ClassLevel at 6, a `Code Coverage` data collector 9 to 29. scripts/vscode/TaskMaster.cli.runsettings (9 lines): the same Workers and Scope, no collector, no logger. +13. scripts/vscode/Invoke-MSTestWithCoverage.ps1 (461 lines): parameters `SearchRoot`, `Configuration`, `CoverageOutput` (default coverage\coverage.cobertura.xml), `NoExecute` only (1 to 13); `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 -CanonicalSettingsXml` 97 to 134 appends the `.*\.Test\.dll$` module exclusion; `Invoke-DotnetCoverageCollection` 190 to 269 throws `MSTest with coverage failed with exit code ` at 261 to 263 on a non-zero collector exit; strict mode and stop preference at 271 to 272; the inner function `Invoke-MSTestWithCoverageMain` (274) fixes `ResultsDirectory` coverage\test-results and `LogFileName` mstest-coverage-run.trx at 297 to 298 with no command-line parameter for either; discovery 348 to 355 excludes obj, ref and `.claude` relative paths; prints `Discovered N test assemblies.` at 374; post-processes the Cobertura in place 399 to 402; `Assert-CoberturaCoverageThresholdForRun` at 406 to 409 (scripts/vscode/Invoke-MSTestWithCoverage.Scope.ps1 line 59) asserts the 80 percent line and 75 percent branch floors on an unscoped run only; prints the first-party line at 410; writes the JaCoCo package projection beside the Cobertura as coverage\coverage.cobertura.jacoco.xml (415 to 423); writes coverage\test-results\mstest-coverage-run.summary.txt (439 to 447); entry guard `if ($MyInvocation.InvocationName -ne '.')` at 459 to 461, so the script is safe to dot-source. +14. scripts/vscode/Invoke-MSTestWithCoverage.Helpers.ps1 dot-sources the ClosureFilter, PackageRate, Threshold, FirstParty and Projection part files at 2 to 6 and declares `ConvertTo-KoverageCoberturaXml` at 407 (parameters `XmlContent` 412, `RepoRoot` 415). scripts/vscode/Invoke-MSTestWithCoverage.Threshold.ps1 declares `Assert-CoberturaLineCoverageThreshold` (3; parameter `CoberturaXml` 28; throws `Cobertura line coverage

% is below the required 80% threshold.` at 54) and `Assert-CoberturaBranchCoverageThreshold` (58; 88; message at 124 with 75). scripts/vscode/Invoke-MSTestWithCoverage.FirstParty.ps1 declares `Get-CoberturaFirstPartyCoverageReport` at 123 (parameter `CoberturaXml` 152), whose output line has the shape `First-party coverage: lines / (

%), branches / (

%)`. scripts/vscode/Invoke-MSTestWithCoverage.Projection.ps1 declares `ConvertTo-JacocoPackageProjection` at 14 (parameter `XmlDocument` 50; one `package` element per source package with exactly two counters, LINE then BRANCH, attributes `missed` and `covered`) and `Assert-JacocoProjectionReconciliation` at 83 (parameters `XmlDocument` 113, `ProjectionXml` 116). scripts/vscode/Invoke-MSTest.TrxSummary.ps1 declares `Get-TrxRunSummary` at 12 (parameter `TrxContent` 44) and `Format-TrxRunSummary` at 103, whose five rendered lines begin `Test run outcome:`, `Total `, `Skipped `, `Figures reported verbatim by the test platform:` and `Failed tests:`. +15. scripts/vscode/Invoke-Restore.ps1 takes `SolutionPath` (default TaskMaster.sln), `Configuration` (Debug) and `Platform` (Any CPU) (1 to 9), resolves MSBuild through vswhere (91 to 98) and runs the Restore target with the RestorePackagesConfig property set to true (107 to 110). scripts/vscode/Install-RepoDotNetSdk.ps1 installs SDK 8.0.205 (3) into `.dotnet-sdk` at the repository root (36); global.json names that version (3) and the `.dotnet-sdk` path (7). 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). +16. .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 `TestResults`-style directories (39, pattern `[Tt]est[Rr]esult*/`, which does not match the hyphenated coverage\test-results path), artifacts/ (57), coverage and coveragexml documents (140 to 141), every trx document (146, `*.trx`) and every cobertura-named XML document (147, `*cobertura*.xml`), everything under the coverage directory except its gitkeep marker (150 to 151), the packages directory (197) and the repo-local dotnet SDK directory pattern (356). The two patterns at 146 to 147 were inserted upstream after this plan's authoring pass (six lines after the former line 142) together with .github/workflows/_hygiene.yml, whose single step (line 24) runs scripts/hygiene/Test-RepositoryHygiene.ps1: the guard enumerates every tracked file through `git ls-files --eol -z` (Test-RepositoryHygiene.Git.ps1 line 70), skips paths under the .claude/ prefix (line 35 of the main script), and emits `HYGIENE raw-document ` for a tracked file classified by Test-RepositoryHygiene.Rules.ps1 lines 88 to 122 as a raw test-platform or coverage-collector document (extension trx, coverage or coveragexml; or an xml file whose root element is `coverage`, `results`, `TestRun`, `CoverageSession`, or `report` carrying a class, sourcefile, method or line element; a `report` root without those is the admitted JaCoCo projection and any other extension is `none`), and `HYGIENE profile-path :` for any line matching the user-profile path pattern `[a-z]:[\\/]+users[\\/]+[a-z0-9_.~-]` of Rules.ps1 line 21 (case-insensitive; a placeholder segment beginning `<` does not match), then prints `HYGIENE Findings=` and exits 1 when n is greater than 0 (main script lines 73 to 86), and `HYGIENE unreadable ` when the content read throws (main script line 59). Every TRX and every Cobertura document this plan produces stays under coverage\ (ignored by 146 to 147 and 150), every committed artifact is a `.md` file whose projection and summary blocks are text inside Markdown (rule A classifies the extension `none`), and rule B is the same match class as the `HYGIENE-PROFILE-PATTERN-MATCHES:` count of `CMD-SWEEP`; P2-T20 runs the guard itself over the staged tree before committing. +17. coverage.config exists at the repository root and lists third-party module exclusions only; the test-assembly exclusion is derived in memory by `ConvertTo-DerivedCoverageSettingsXml` (fact 13). coverage/.gitkeep is tracked. artifacts/orchestration/orchestrator-state.json exists in the worktree (fact 20). +18. The four UtilitiesCS.Test classes that stall a local vstest run on this workstation are `HelperClasses.ShellUtilities_Tests`, `HelperClasses.ShellUtilitiesStatic_Tests`, `HelperClasses.SysImageListHelperTests` and `EmailIntelligence.OSBrowser_Tests` (#931 fact 16; the #931 P0-T9 probe recorded `STALL-PROBE: REPRODUCES` on 2026-09-29 with one shell-icon test failing on an invalid Win32 icon handle, and its coverage route was DIRECT). `DictionaryExtensions_Tests.TryAddValuesAsync_UpdatesExistingValue` is a known intermittent failure (issue #780) and is never retried by this plan (D-12); its TRX `testName` value, the form every `FAILED-SET:` carries, is the bare method name `TryAddValuesAsync_UpdatesExistingValue` (scripts/vscode/Invoke-MSTest.TrxSummary.ps1 line 84 reads the `testName` attribute into `FailedTestName`). +19. The worktree's `.git` reference file names the branch reference bug/filesystem-wrapper-tests-open-repository-solution-file-940 as HEAD. The harness-supplied status block of the planning session described a different checkout and is not cited. The promoted record docs/features/potential/promoted/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file.md is present in the worktree and, per the checkpoint (fact 20), was restored onto this branch before execution; P0-T3 records it under `INHERITED-PROMOTION-RECORD:` when it lies in `INHERITED-CLAUSE-A:`. +20. .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), a truthy `lifecycle_ready` (242 to 243) and a `feature-folder` beginning docs/features/active/ (251); 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/ (22 to 28), models only `-m` and `--message` forms, requires at least one pathspec operand, and denies any line containing `$`, backtick, `>` or `<`. The checkpoint present at authoring time carries `issue-num` `940`, `feature-folder` docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940, `route_id` `preparation`, `path_selected` `small`, `preparation_mode` `true`, `work-mode` `minor-audit`, `lifecycle_ready` `true`, `plan-path` naming this file, and `next_step` `S4_atomic_planning`. Those values describe the preparation checkpoint only; the execution orchestrator re-seeds the file before the executor starts, and no task of this plan compares against the `route_id`, `path_selected`, `preparation_mode` or `next_step` values (P0-T3 records them and gates only on the presence of a route field and on `lifecycle_ready`, `issue-num` and `feature-folder`). +21. .claude/hooks/validate-planner-output.ps1 requires every task's opening line to carry a slash- or backslash-bearing path token (95), the phase heading form with an em dash (238), the task line form (239), 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); its planner-internal-review validation (113 to 228) reads the agent's final chat output, not this file. +22. Line endings. .gitattributes line 4 declares `* text=auto`; .editorconfig line 669 sets `end_of_line = crlf` inside the section that governs `.cs` files; no .csharpierrc file exists in the worktree. Git stores every text file with LF endings in the index and writes the working-tree ending that `core.autocrlf` selects; the #931 run observed `core.autocrlf` `true` and `i/lf w/crlf` for every `.cs` file it touched, and P1-T8 re-observes the effect through the working-tree column of `git ls-files --eol` rather than relying on the setting. A file the Edit tool rewrites may carry LF endings, so the working-tree bytes can 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; P1-T8 restores the checkout form before taking the `FIX-HASH-` anchors. +23. FluentAssertions 8.11.0 failure phrases relied on by the negative controls, each observed verbatim in committed evidence of this repository: `but found False` (`BeTrue()`), `but no exception was thrown` (`Throw()` on a non-throwing action), `to contain` followed by the quoted expected item (`Contain(item)` on a collection), `has a length of` (string `Be` with a length difference), `to be greater than` (`BeGreaterThan`), and the fixed-text phrase `not to be empty` printed by `NotBeEmpty()`. A `Throw()` that observes a different exception type prints `to be thrown, but found` followed by the observed type name in angle brackets, which is the token P1-T7 reads. The subject name in a message depends on caller identification and is never gated. +24. .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 hash observation rather than relying on the exit code. +25. Numeric derivation for the scoped-run totals. Population: `[TestMethod]`-attributed methods in the two Write Set files. Primary count by reading the attribute lines: PFS 4 (fact 1), DIW 8 (fact 3), total 12; cross-check by the count-mode grep `\[TestMethod\]` over the two files: 4 and 8. After Target Source A and B the counts are PFS 7 (T1a, T1b, T1c, T2, T3, T3b, T4) and DIW 8 (unchanged), total 15. Affected subset before the rewrite: PFS 4 of 4 (every method calls a root or solution helper), DIW 4 of 8 (lines 30, 46, 67, 86), 8 of 12, matching research section 8. + +## Decisions + +- **D-1 Test-only rewrite on owned fixtures (research approach A).** No production file changes. The fixtures are the running host's own loaded assembly image (`typeof().Assembly.Location`), its directory and that directory's parent, which exist for the whole run and are owned by the test process; a rooted literal `C:\Repo\fixture` for pure path computations (the #931 D-14 shape); and names under the owned directory that nothing creates, prefixed `__940_missing_`, for every mutating member, each asserted absent with `Exists.Should().BeFalse()` before the act so a stale entry fails loudly instead of being deleted or moved. Approach B (a delegate seam on `PhysicalDirectoryInfoAdapter`) is rejected because it adds production surface with no consumer and lowers real-delegation coverage; approach C (tolerate contention) is rejected because it needs a retry or a catch, both prohibited. +- **D-2 Every member the two files exercise today stays exercised (AC4).** Target Source A keeps all 18 enumeration overloads, both `Create` overloads, both `CreateSubdirectory` overloads (as no-ops on existing owned entries whose returned wrapper is asserted to name the owned directory, which makes the delegation and the wrapping observable), `GetAccessControl` x2, `SetAccessControl`, `GetObjectData`, `Refresh`, `ToString`, `Parent`, `Root`, every getter, every setter (through the missing-path throw), `Delete` x2, `MoveTo`, `CopyTo` x2, `Replace` x2, the three unseamed opens and the four seamed members. P2-T8 gates this with a per-member token census (Token Census Expectations) and P2-T7 with the per-file covered-line comparison (D-8). +- **D-3 Direct runs use the CLI runsettings file** scripts/vscode/TaskMaster.cli.runsettings with the isolation switch (fact 12), the same parallel regime the coverage route uses; no collector output is produced by a direct run. +- **D-4 Coverage route is selected by a recorded observation (P0-T9).** `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 (P1-T8).** Each test-file revert is then anchored: `git checkout -- ` restores exactly the committed fixed state, `git diff --exit-code HEAD -- ` exits 0 and the SHA-256 equals the `FIX-HASH-` anchor. Production-file reverts are anchored to the P0-T4 `PRE-EDIT-HASH-` values. +- **D-6 Predicted failing assertion per control (Control Table).** Each control names the one assertion predicted to fail and the fixed phrase its `MESSAGE` must contain (fact 23). Any other failing assertion, a passing control, or a `MESSAGE` lacking the phrase is `MUTATION PREDICTION MISMATCH`: stop and report the text; do not adjust the test. +- **D-7 Per-file no-regression rule and floors (AC8; coordinator ruling of 2026-09-30, option 1, applied at the P2-T7 stop).** AC8's coverage clause is the repository policy wording (a code change must not reduce coverage for the lines that were changed) applied to the three files whose coverage the rewritten tests carry: for each of UtilitiesCS/HelperClasses/FileSystem/PhysicalDirectoryInfoAdapter.cs, UtilitiesCS/HelperClasses/FileSystem/PhysicalFileInfoAdapter.cs and UtilitiesCS/HelperClasses/FileSystem/DirectoryInfoWrapper.cs, the covered-line count and the covered-branch count of the gating measurement must each be no lower than the same counts in the Phase 0 baseline document (`CMD-FILE-COMPARE`, `NOT-LOWER=True` on all three `FILE` lines), and the repository first-party totals of the same measurement must satisfy the CLAUDE.md floors (line at least 80 percent, branch at least 75 percent; the 90 percent new-code target has an empty subject because no production line is added). The gating measurement is MEASUREMENT 3, taken by P2-T7 on the merged head after the post-merge rebuilds; a `FILE` line reading `NOT-LOWER=False` there is `AC8: NOT MET`, stop and report, and no further re-measurement is run. The former package-level (`PACKAGE UtilitiesCS`) and repository-level (`REPO-LINE`) not-lower-than-baseline comparison and its second-measurement rule are withdrawn: two identical measurements of the same committed tree (MEASUREMENT 1 and 2 in coverage-final.md) moved between 2 and 10 covered lines and branches in UtilitiesCS files this item does not touch (UtilitiesCS\Interfaces\IWinForm\PropertyStore.cs, UtilitiesCS\OutlookObjects\Table\OlTableExtensions.Etl.cs, UtilitiesCS\EmailIntelligence\SubjectMap\SubjectMapSco.Orchestration.cs) while every `FILE` line stayed `NOT-LOWER=True`, so those aggregates measure run-to-run variance of unrelated tests rather than this change. `CMD-PACKAGE-COMPARE` is retained only to produce the observational figures of the variance table and the first-party re-derivation; no acceptance reads the `NOT-LOWER=` field of a `PACKAGE` or `REPO-LINE` line. The comparator is shown able to fail by `CMD-FILE-COMPARE-CONTROL` (an in-memory copy of the `final3` document lowered by one covered line, and separately by one covered branch, must read `NOT-LOWER=False`). +- **D-8 Per-file comparison (AC8 and AC4).** `CMD-FILE-COMPARE` reads the two post-processed Cobertura documents and, for each of the three adapter and wrapper files, unions the line elements reached through the class-level lines axis and through the methods axis of every `class` element whose `filename` ends with that file name (max hits per line number; for branches, the covered and total counts of the `condition-coverage` attribute's `(covered/total)` fragment, keeping the larger covered value per line number), prints one `FILE` line per file with covered and valid lines and covered and total branches on both sides, and requires both final covered counts to be no lower than the baseline counts. The valid-line and total-branch counts are expected equal because no production line changes (recorded, not gated). The three `FILE` lines are also AC4's evidence that coverage of the changed wrapper and adapter lines did not regress. +- **D-9 Exception types are observed before they are pinned (P1-T7).** Target Source A writes the research's predicted types (`FileNotFoundException` for every setter on a missing path and for `CopyTo`, `MoveTo` and `Replace` on a missing source; `DirectoryNotFoundException` for `Delete`, `Delete(true)` and `MoveTo` on a missing directory, already observed by today's passing assertions). The first scoped run of the rewritten classes is the observation: a test failing with `to be thrown, but found` names the observed type, and the executor pins exactly that type in exactly that assertion, then re-runs P1-T5 through P1-T7 as the next iteration. Because FluentAssertions stops a method at its first failing assertion, one iteration can expose only the first mismatching assertion of each method, so a later iteration may expose a further, not-yet-pinned assertion of the same method that the earlier failure masked; that takes the pinning branch again, bounded at nine iterations. A mismatch on an assertion pinned in an earlier iteration is `EXCEPTION TYPE UNSTABLE`: stop and report. Each pinned type T not already in the token list adds the literal `Throw()` to the `CMD-CENSUS` token list for every later census, and `THROW-SUM:` (the sum of the `TOKEN` values of `Throw()`, `Throw()` and each appended literal; the `Throw()` occurrence is not a census token and is excluded) stays 23. An action that throws nothing (`but no exception was thrown`) is `SETTER DID NOT THROW`: stop and report, because the throw-based verification shape needs a plan revision. An `IOException` reported by a `Test method ... threw exception` line for the `OpenRead` or `OpenText` call is `OPENREAD SHARE CONFLICT`: stop and report; the remedy (routing those two members through the existing seam) is a production change outside this plan's Write Set and is never a catch. +- **D-10 No production change.** P1-T31 and P2-T10 require the anchored diff of UtilitiesCS/ to be empty, the porcelain of UtilitiesCS/ to be empty, and the three production hashes to equal the P0-T4 values. +- **D-11 AC7 is classification only (P1-T1).** The artifact classifies each of the three sites by re-reading fact 11 in the assigned worktree; the classes are `legitimate repository-file read` (RibbonControllerTests source-as-specification read; FSharpCoreHintPathAlignmentTests build-configuration alignment by design) and `same defect class` (the SortEmail_Tests `TrySaveAttachmentAsync` site, where production code calls `Directory.CreateDirectory` on the repository root), with the four string-only SortEmail_Tests consumers recorded as `layout dependence without file-system I/O`. P2-T10 proves none of the three files changed. No potential entry and no issue is created by the executor; the orchestrator files any follow-up. +- **D-12 Known #780 flake is not retried.** `DictionaryExtensions_Tests.TryAddValuesAsync_UpdatesExistingValue` is admissible only in the Phase 0 baseline failed set (`BASELINE-ADMISSIBLE` is exactly the one TRX `testName` value `TryAddValuesAsync_UpdatesExistingValue`; `Get-TrxRunSummary` fills `FailedTestName` from the TRX `testName` attribute, which carries the method name without its class, so every `FAILED-SET:` entry is compared with that bare name). In the P0-T11 baseline a `FAILED-SET:` whose every name is in `BASELINE-ADMISSIBLE` or in `NAMES-PRE` (the twelve pre-edit names of the two classes, whose failure before the rewrite is the defect this item removes, D-15) records the `NAMES-PRE` subset as `BASELINE-DEFECT-CLASS-FAILURES:` and completes; any other name is `BASELINE NOT GREEN`. AC8 requires the coverage route to pass with no new failures relative to the baseline; a failure of that test in P2-T6 or in the P2-T7 MEASUREMENT 3 that was also in the baseline set is recorded as `NEWLY-FAILING: NONE` and the task completes (no coverage comparison re-runs a measurement, D-7); a failure of it that was not in the baseline set is `AC8: NOT MET`, reported with the `Failed tests:` line, and the run stops without re-running. +- **D-13 Fail-before evidence.** No committed test can deterministically reproduce the defect without holding the solution file from another process, which is prohibited; P1-T2 writes the exception dossier and the eleven controls are the deterministic observed-failing evidence. +- **D-14 Wrap-tolerant tokens.** CSharpier may break a `.Should()` chain across lines, so every gated token is a short single-line literal by construction (a member name with its opening parenthesis, an identifier, a constructor call with its first argument's opening parenthesis). No gated token spans a call whose argument list can exceed the 100-column print width. +- **D-15 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:`. A P0-T10 failed set that is non-empty is `BASELINE NOT GREEN` for the two classes (their pre-edit tests can fail only through the very contention this item removes, which would be a live instance of the defect; it is recorded and the task completes, because the rewrite does not depend on the pre-edit tests passing). A P0-T11 failed set whose every name is in `BASELINE-ADMISSIBLE` or in `NAMES-PRE` completes the task, the `NAMES-PRE` subset recorded as `BASELINE-DEFECT-CLASS-FAILURES:` (D-12); any other name is `BASELINE NOT GREEN` and stops the run. + +## 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`, `FILTERARG`, `NAMES`, `STAGE`, `RAW`, `GATEARGS`, `ANCHOR-SHA` (the P0-T3 `ANCHOR-SHA:` value, Refs) and `ANCHOR-SHA-2` (the P2-T7 `ANCHOR-SHA-2:` value, Refs) are substituted by the executor as each task states. `STAGE` takes one of `baseline`, `final`, `final2` and `final3` (`final3` is the post-merge MEASUREMENT 3 of P2-T7). + +Names for the `NAMES` token: `NAMES-PFS` is the seven quoted method names of Target Source A (`"PhysicalDirectoryInfoAdapter_AccessorsAndNoOpCreation_MirrorOwnedDirectory", "PhysicalDirectoryInfoAdapter_Enumeration_WrapsOwnedDirectoryEntries", "PhysicalDirectoryInfoAdapter_SettersOnMissingDirectory_ThrowWithoutCreatingEntries", "PhysicalDirectoryInfoAdapter_MissingDirectoryAndUnsupportedInfo_BranchesBehaveAsExpected", "PhysicalFileInfoAdapter_PropertiesStreamsAndAccessors_MirrorFileInfo", "PhysicalFileInfoAdapter_SettersOnMissingFile_ThrowWithoutCreatingFiles", "PhysicalFileInfoAdapter_MissingFileBranches_ThrowOrNoOpWithoutCreatingFiles"`); `NAMES-DIW` is the eight quoted method names of fact 3 (`"Constructor_WhenDirectoryInfoIsNull_ThrowsArgumentNullException", "Properties_ShouldMirrorWrappedDirectoryInfo", "GetDirectoriesAndGetFiles_ShouldReturnWrappedEntries", "EnumerateFileSystemInfos_ShouldWrapDirectoriesAndFiles", "ToString_ShouldDelegateToWrappedDirectoryInfo", "PropertyDelegates_ShouldMirrorMockedIDirectoryInfo", "EnumerationAndArrayMethods_ShouldDelegateToWrappedIDirectoryInfo", "LifecycleAndAccessControlMethods_ShouldDelegateToWrappedIDirectoryInfo"`); `NAMES-BOTH` is `NAMES-PFS` followed by `NAMES-DIW` (fifteen names); `NAMES-PRE` is the four pre-edit PFS names of fact 1 (`"PhysicalDirectoryInfoAdapter_PropertiesEnumerationAndAccessors_MirrorDirectoryInfo", "PhysicalDirectoryInfoAdapter_MissingDirectoryAndUnsupportedInfo_BranchesBehaveAsExpected", "PhysicalFileInfoAdapter_PropertiesStreamsAndAccessors_MirrorFileInfo", "PhysicalFileInfoAdapter_MissingFileBranches_ThrowOrNoOpWithoutCreatingFiles"`) followed by `NAMES-DIW` (twelve names); `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-BOTH` is `FullyQualifiedName~HelperClasses.PhysicalFileSystemAdapters_Tests|FullyQualifiedName~HelperClasses.DirectoryInfoWrapper_Tests` (expected `total` 12 before the rewrite, 15 after); `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`; each control's per-method filter is `FullyQualifiedName~HelperClasses..` as the Control Table lists (expected `total` 1). `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. The test-case-filter switch and the tests switch are mutually exclusive on the vstest command line; only the filter form is used. + +Assembly: `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 = @("GetRepositoryRoot", "GetSolutionFile", "TaskMaster.sln", "AppDomain", "File.Exists(", "catch (IOException)", "catch", "Assembly.Location", "RootedFixturePath", "OwnedAssemblyFile", "OwnedAssemblyDirectory", "MissingOwnedPath(", "__940_missing_", "Exists.Should().BeFalse()", "new DirectoryInfo(", "new DirectoryInfo(RootedFixturePath)", "new DirectoryInfo(MissingOwnedPath(", "new FileInfo(", "new FileInfo(MissingOwnedPath(", "new FileInfo(typeof(", "[TestMethod]", "using System.Linq;", "using Moq;", "Thread.Sleep", "Task.Delay", "[Timeout", "DoNotParallelize", "Retry", "Path.GetTemp", "File.Create", "File.WriteAll", "Directory.CreateDirectory", "FileShare.ReadWrite", "FileShare.None", ".Create()", ".Create(", ".CreateSubdirectory(", ".EnumerateDirectories(", ".EnumerateFiles(", ".EnumerateFileSystemInfos(", ".GetDirectories(", ".GetFiles(", ".GetFileSystemInfos(", ".GetAccessControl(", ".GetObjectData(", ".Refresh()", ".SetAccessControl(", ".ToString()", ".Delete()", ".Delete(recursive: true)", ".MoveTo(", ".CopyTo(", ".Replace(", ".Open(", ".OpenRead()", ".OpenText()", ".AppendText()", ".OpenWrite()", ".Parent", ".Root", ".Length", ".IsReadOnly", ".Attributes", ".CreationTime", ".CreationTimeUtc", ".LastAccessTime", ".LastAccessTimeUtc", ".LastWriteTime", ".LastWriteTimeUtc", ".MemberCount", "Throw()", "Throw()", "!_directoryInfo.Exists", "Array.Empty()", "Enumerable.Empty()", "set { }", "public void Delete() { }", "_directoryInfo.FullName", "public long Length => 0;", "public void MoveTo(string destFileName) { }", "return string.Empty;") + 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) + +Once P1-T7 has pinned an exception type T, every later `CMD-CENSUS` run (the following P1-T5 iterations and P2-T8) carries one additional literal `Throw()` per pinned type appended to the `$tokens` array, so the pinned assertions remain counted; the executor records the appended literals in the artifact as `CENSUS-TOKENS-APPENDED:` (or `NONE`). + +**CMD-BUILD** (UtilitiesCS.Test project build after a source edit; rebuilds UtilitiesCS through the ProjectReference when a production file changed; `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 = "UtilitiesCS.Test\bin\Debug\UtilitiesCS.Test.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 "UtilitiesCS.Test\UtilitiesCS.Test.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\UtilitiesCS.Test.dll")).Count) + Write-Output ("PROD_CSC_OUT_LINES: " + [regex]::Matches($log, [regex]::Escape("/out:obj\Debug\UtilitiesCS.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)`. The `Command:` field records `msbuild UtilitiesCS.Test\UtilitiesCS.Test.csproj /t:Build /m /p:Configuration=Debug /p:Platform=AnyCPU` with the note `resolved through vswhere, plus /nodeReuse:false`. + +**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 ("UCS_TEST_CSC_OUT_LINES: " + [regex]::Matches($log, [regex]::Escape("/out:obj\Debug\UtilitiesCS.Test.dll")).Count) + Write-Output ("UCS_CSC_OUT_LINES: " + [regex]::Matches($log, [regex]::Escape("/out:obj\Debug\UtilitiesCS.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 "*PhysicalFileSystemAdapters_Tests.cs*" -or $_ -like "*DirectoryInfoWrapper_Tests.cs*") -and ($_ -like "*warning *" -or $_ -like "*error *") }).Count) + +**CMD-VSTEST** (one assembly under the CLI runsettings with the isolation switch; `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 "UtilitiesCS.Test\bin\Debug\UtilitiesCS.Test.dll").Path + $settings = (Resolve-Path -LiteralPath "scripts\vscode\TaskMaster.cli.runsettings").Path + $results = Join-Path (Get-Location).Path "coverage\test-results\940\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 "scripts\vscode\TaskMaster.cli.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) + 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 `Command:` field records `vstest.console.exe UtilitiesCS.Test\bin\Debug\UtilitiesCS.Test.dll "/Settings:scripts\vscode\TaskMaster.cli.runsettings" /InIsolation "/ResultsDirectory:coverage\test-results\940\" "/Logger:trx;LogFileName=.trx" "/Blame:CollectHangDump;TestTimeout=4min;HangDumpType=None"` with the note `resolved through vswhere`. A `MESSAGE` line is transcribed into an artifact only after the artifact-hygiene substitution (a FluentAssertions object dump can carry the assembly image's absolute path in a `Name =` line). + +**CMD-COVERAGE-RUNNER** (CLAUDE.md step 4 route; `STAGE` is `baseline`, `final`, `final2` or `final3`; 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-940.cobertura.xml", "coverage\STAGE-940.trx", "coverage\STAGE-940.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-940.runner.log" + Write-Output ("RUNNER_EXIT_CODE: " + $LASTEXITCODE) + $log = Get-Content -LiteralPath "coverage\logs\STAGE-940.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")) + if (Test-Path -LiteralPath "coverage\coverage.cobertura.xml") { Copy-Item -LiteralPath "coverage\coverage.cobertura.xml" -Destination "coverage\STAGE-940.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-940.trx" -Force } + Write-Output ("TRX_PRESENT: " + (Test-Path -LiteralPath "coverage\STAGE-940.trx")) + +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). The stale-output removal makes each `_PRESENT` value an observation of this run; the stage-copy removal clears this stage's own copies only, so the `final` run never touches a `baseline` copy. + +**CMD-COVERAGE-DIRECT** (the runner's inner invocation issued directly with the four-class exclusion; used when `COVERAGE-ROUTE: DIRECT`; `STAGE` substituted, `baseline`, `final`, `final2` or `final3`): + + 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-940.cobertura.xml", "coverage\STAGE-940.trx", "coverage\STAGE-940.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-940.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-940.cobertura.xml" + $settings = Join-Path $repo "scripts\vscode\TaskMaster.cli.runsettings" + $results = Join-Path $repo "coverage\test-results\940\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-940.trx" "/Blame:CollectHangDump;TestTimeout=4min;HangDumpType=None" 2>&1 | Tee-Object -FilePath "coverage\logs\STAGE-940.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-940.trx")) { Copy-Item -LiteralPath (Join-Path $results "STAGE-940.trx") -Destination "coverage\STAGE-940.trx" -Force } + Write-Output ("TRX_PRESENT: " + (Test-Path -LiteralPath "coverage\STAGE-940.trx")) + +The `ASSEMBLY:` lines print the path relative to the worktree root (the leading backslash is the first character). The `Command:` field records `dotnet-coverage collect --output coverage\-940.cobertura.xml --output-format cobertura --settings coverage\effective-coverage-940.config -- vstest.console.exe "/Settings:scripts\vscode\TaskMaster.cli.runsettings" /InIsolation "/TestCaseFilter:" "/ResultsDirectory:coverage\test-results\940\" "/Logger:trx;LogFileName=-940.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 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. 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 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-940.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-940.cobertura.xml" -Raw -Encoding UTF8 + if ("RAW" -eq "True") { $doc = ConvertTo-KoverageCoberturaXml -XmlContent $doc -RepoRoot $repo; Set-Content -LiteralPath "coverage\STAGE-940.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-940.jacoco.xml" -Value $projection -Encoding UTF8 + Write-Output "PROJECTION-BEGIN" + Write-Output $projection + Write-Output "PROJECTION-END" + +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** (observational since the coordinator ruling of 2026-09-30, D-7: the UtilitiesCS package figures of two JaCoCo projections, the repository first-party line figures `REPO-LINE`, plus the first-party re-derivation; `STAGE` is `final`, `final2` or `final3`. The `NOT-LOWER=` field its `PACKAGE` and `REPO-LINE` lines print is kept for continuity with the MEASUREMENT 1 and 2 records already in coverage-final.md and is read by no acceptance; the block text is unchanged from the form those two measurements ran): + + Set-Location -LiteralPath "WORKTREE" + [xml]$b = Get-Content -LiteralPath "coverage\baseline-940.jacoco.xml" -Raw -Encoding UTF8 + [xml]$f = Get-Content -LiteralPath "coverage\STAGE-940.jacoco.xml" -Raw -Encoding UTF8 + $q = [char]39 + foreach ($type in @("LINE", "BRANCH")) { $xp = "/report/package[@name=" + $q + "UtilitiesCS" + $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 UtilitiesCS " + $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 UtilitiesCS " + $type + " baseline=" + $bCov + "/" + $bVal + " rate=" + $bRate + " final=" + $fCov + "/" + $fVal + " rate=" + $fRate + " NOT-LOWER=" + ($fRate -ge $bRate)) } + . (Join-Path (Get-Location).Path "scripts\vscode\Invoke-MSTestWithCoverage.Helpers.ps1") + $bLine = [string](Get-CoberturaFirstPartyCoverageReport -CoberturaXml (Get-Content -LiteralPath "coverage\baseline-940.cobertura.xml" -Raw -Encoding UTF8)) + $fLine = [string](Get-CoberturaFirstPartyCoverageReport -CoberturaXml (Get-Content -LiteralPath "coverage\STAGE-940.cobertura.xml" -Raw -Encoding UTF8)) + Write-Output ("BASELINE-FIRST-PARTY: " + $bLine) + Write-Output ("FINAL-FIRST-PARTY: " + $fLine) + $bm = [regex]::Match($bLine, "lines (\d+)/(\d+)") + $fm = [regex]::Match($fLine, "lines (\d+)/(\d+)") + $rbCov = [int]$bm.Groups[1].Value + $rbVal = [int]$bm.Groups[2].Value + $rfCov = [int]$fm.Groups[1].Value + $rfVal = [int]$fm.Groups[2].Value + $rbRate = if ($rbVal -gt 0) { [math]::Round($rbCov / $rbVal, 6) } else { 0 } + $rfRate = if ($rfVal -gt 0) { [math]::Round($rfCov / $rfVal, 6) } else { 0 } + Write-Output ("REPO-LINE baseline=" + $rbCov + "/" + $rbVal + " rate=" + $rbRate + " final=" + $rfCov + "/" + $rfVal + " rate=" + $rfRate + " NOT-LOWER=" + ($rfRate -ge $rbRate)) + +The first-party floors are read from the `First-party coverage:` line `CMD-COVERAGE-POST` printed for the stage: the task transcribes the two percentages as `FIRST-PARTY-LINE-PERCENT:` and `FIRST-PARTY-BRANCH-PERCENT:` and compares them with 80 and 75. The `REPO-LINE` line re-derives the two `First-party coverage:` lines from the two post-processed Cobertura documents with `Get-CoberturaFirstPartyCoverageReport` (scripts/vscode/Invoke-MSTestWithCoverage.FirstParty.ps1 line 123, which returns the rendered string, `OutputType` string at 149), so the two `lines /` figures it compares are the ones the two coverage artifacts transcribe; a `REPO-LINE` whose either `rate=` is 0 with a `/0` denominator is `FIRST-PARTY LINE ABSENT`: stop and report. Since the ruling of 2026-09-30 the floors are the only figures of this block that any acceptance reads; its `PACKAGE` and `REPO-LINE` lines feed the variance table of P2-T7 step (h). + +**CMD-FILE-COMPARE** (per-file covered-line and covered-branch comparison of the two post-processed Cobertura documents for the three adapter and wrapper files, D-7 and D-8; `STAGE` is `final`, `final2` or `final3`; the gate for AC8 and AC4 is the `final3` run, the `final` and `final2` runs are observational re-reads that supply the branch columns of the variance table): + + Set-Location -LiteralPath "WORKTREE" + [xml]$b = Get-Content -LiteralPath "coverage\baseline-940.cobertura.xml" -Raw -Encoding UTF8 + [xml]$f = Get-Content -LiteralPath "coverage\STAGE-940.cobertura.xml" -Raw -Encoding UTF8 + $sep = [char]92 + $read = { param($doc, $suffix) $m = @{}; foreach ($c in @($doc.SelectNodes("//class"))) { if ($c.GetAttribute("filename") -like $suffix) { foreach ($n in (@($c.SelectNodes("lines/line")) + @($c.SelectNodes("methods/method/lines/line")))) { $k = [int]$n.GetAttribute("number"); $h = [int]$n.GetAttribute("hits"); $bc = 0; $bt = 0; $cc = [regex]::Match($n.GetAttribute("condition-coverage"), "\((\d+)/(\d+)\)"); if ($cc.Success) { $bc = [int]$cc.Groups[1].Value; $bt = [int]$cc.Groups[2].Value }; if (-not $m.ContainsKey($k)) { $m[$k] = @{ h = $h; bc = $bc; bt = $bt } } else { if ($m[$k].h -lt $h) { $m[$k].h = $h }; if ($m[$k].bc -lt $bc) { $m[$k].bc = $bc; $m[$k].bt = $bt } } } } }; return $m } + $compare = { param($label, $bl, $fl) $bLines = @($bl.Values | Where-Object { $_.h -gt 0 }).Count; $fLines = @($fl.Values | Where-Object { $_.h -gt 0 }).Count; $bBr = 0; $bBrT = 0; foreach ($v in $bl.Values) { $bBr += $v.bc; $bBrT += $v.bt }; $fBr = 0; $fBrT = 0; foreach ($v in $fl.Values) { $fBr += $v.bc; $fBrT += $v.bt }; Write-Output ("FILE " + $label + " LINES baseline=" + $bLines + "/" + $bl.Count + " final=" + $fLines + "/" + $fl.Count + " BRANCHES baseline=" + $bBr + "/" + $bBrT + " final=" + $fBr + "/" + $fBrT + " NOT-LOWER=" + (($fLines -ge $bLines) -and ($fBr -ge $bBr))) } + foreach ($leaf in @("PhysicalDirectoryInfoAdapter.cs", "PhysicalFileInfoAdapter.cs", "DirectoryInfoWrapper.cs")) { $suffix = "*" + $sep + $leaf; $bl = & $read $b $suffix; $fl = & $read $f $suffix; & $compare $leaf $bl $fl } + +Each `FILE` line has the shape `FILE LINES baseline=/ final=/ BRANCHES baseline=/ final=/ NOT-LOWER=`: a and c are covered lines, b and d valid lines, e and g covered branches, f and h total branches, and `NOT-LOWER=True` requires both c at least a and g at least e. The branch counts come from the per-line `condition-coverage` attribute of the post-processed document, whose value has the shape `50% (1/2)` on a branch line and which is absent on a non-branch line (observed in coverage\final-940.cobertura.xml at the class elements of the three files: PhysicalDirectoryInfoAdapter.cs line 13 `(1/2)`, PhysicalFileInfoAdapter.cs line 27 `(1/2)`, DirectoryInfoWrapper.cs line 16 `(2/2)`; the `filename` attribute of the post-processed document is the repository-relative path with backslash separators, which the `*` plus separator plus leaf suffix matches). A `FILE` line whose baseline LINES denominator is 0 means the file was not instrumented in the baseline document and is `FILE NOT INSTRUMENTED`: stop and report (the three files carry no `ExcludeFromCodeCoverage` attribute, facts 4 to 6, so each is expected instrumented). The `$read` and `$compare` statements are copied verbatim into the two blocks below. + +**CMD-FILE-COMPARE-CONTROL** (in-memory negative control of the per-file comparator, P2-T7 step (g); `STAGE` is `final3`; nothing on disk is modified, which the two hash lines prove): + + Set-Location -LiteralPath "WORKTREE" + $doc = "coverage\STAGE-940.cobertura.xml" + Write-Output ("CONTROL-DOC-HASH-BEFORE: " + (Get-FileHash -Algorithm SHA256 -LiteralPath $doc).Hash) + [xml]$f = Get-Content -LiteralPath $doc -Raw -Encoding UTF8 + $sep = [char]92 + $read = { param($doc, $suffix) $m = @{}; foreach ($c in @($doc.SelectNodes("//class"))) { if ($c.GetAttribute("filename") -like $suffix) { foreach ($n in (@($c.SelectNodes("lines/line")) + @($c.SelectNodes("methods/method/lines/line")))) { $k = [int]$n.GetAttribute("number"); $h = [int]$n.GetAttribute("hits"); $bc = 0; $bt = 0; $cc = [regex]::Match($n.GetAttribute("condition-coverage"), "\((\d+)/(\d+)\)"); if ($cc.Success) { $bc = [int]$cc.Groups[1].Value; $bt = [int]$cc.Groups[2].Value }; if (-not $m.ContainsKey($k)) { $m[$k] = @{ h = $h; bc = $bc; bt = $bt } } else { if ($m[$k].h -lt $h) { $m[$k].h = $h }; if ($m[$k].bc -lt $bc) { $m[$k].bc = $bc; $m[$k].bt = $bt } } } } }; return $m } + $compare = { param($label, $bl, $fl) $bLines = @($bl.Values | Where-Object { $_.h -gt 0 }).Count; $fLines = @($fl.Values | Where-Object { $_.h -gt 0 }).Count; $bBr = 0; $bBrT = 0; foreach ($v in $bl.Values) { $bBr += $v.bc; $bBrT += $v.bt }; $fBr = 0; $fBrT = 0; foreach ($v in $fl.Values) { $fBr += $v.bc; $fBrT += $v.bt }; Write-Output ("FILE " + $label + " LINES baseline=" + $bLines + "/" + $bl.Count + " final=" + $fLines + "/" + $fl.Count + " BRANCHES baseline=" + $bBr + "/" + $bBrT + " final=" + $fBr + "/" + $fBrT + " NOT-LOWER=" + (($fLines -ge $bLines) -and ($fBr -ge $bBr))) } + $clone = { param($m) $c = @{}; foreach ($k in $m.Keys) { $c[$k] = @{ h = $m[$k].h; bc = $m[$k].bc; bt = $m[$k].bt } }; return $c } + foreach ($leaf in @("PhysicalDirectoryInfoAdapter.cs", "PhysicalFileInfoAdapter.cs", "DirectoryInfoWrapper.cs")) { $suffix = "*" + $sep + $leaf; $fl = & $read $f $suffix; & $compare ($leaf + " CONTROL-SELF") $fl $fl; $cl = & $clone $fl; $kl = @($cl.Keys | Where-Object { $cl[$_].h -gt 0 } | Sort-Object)[0]; $cl[$kl].h = 0; & $compare ($leaf + " CONTROL-LINE") $fl $cl; $cb = & $clone $fl; $kb = @($cb.Keys | Where-Object { $cb[$_].bc -gt 0 } | Sort-Object)[0]; $cb[$kb].bc = $cb[$kb].bc - 1; & $compare ($leaf + " CONTROL-BRANCH") $fl $cb } + Write-Output ("CONTROL-DOC-HASH-AFTER: " + (Get-FileHash -Algorithm SHA256 -LiteralPath $doc).Hash) + +The control feeds the comparator the `final3` document as both sides (`CONTROL-SELF`, expected `NOT-LOWER=True`), then an in-memory copy with the lowest-numbered covered line's hits set to 0 (`CONTROL-LINE`, expected `NOT-LOWER=False` with the LINES `final=` covered count one lower than `baseline=`), then an in-memory copy with the lowest-numbered branch line's covered-branch count lowered by one (`CONTROL-BRANCH`, expected `NOT-LOWER=False` with the BRANCHES `final=` covered count one lower). The lowest-numbered branch line of each file is its constructor guard (the three lines named above), so `$kb` is defined for all three files. `CONTROL-DOC-HASH-BEFORE:` and `CONTROL-DOC-HASH-AFTER:` must be equal. + +**CMD-UNTOUCHED-LINES** (covered-line counts of the three untouched UtilitiesCS files in which MEASUREMENT 1 and 2 recorded losses, one line per stage and file; observational, P2-T7 step (h)): + + Set-Location -LiteralPath "WORKTREE" + $sep = [char]92 + $read = { param($doc, $suffix) $m = @{}; foreach ($c in @($doc.SelectNodes("//class"))) { if ($c.GetAttribute("filename") -like $suffix) { foreach ($n in (@($c.SelectNodes("lines/line")) + @($c.SelectNodes("methods/method/lines/line")))) { $k = [int]$n.GetAttribute("number"); $h = [int]$n.GetAttribute("hits"); $bc = 0; $bt = 0; $cc = [regex]::Match($n.GetAttribute("condition-coverage"), "\((\d+)/(\d+)\)"); if ($cc.Success) { $bc = [int]$cc.Groups[1].Value; $bt = [int]$cc.Groups[2].Value }; if (-not $m.ContainsKey($k)) { $m[$k] = @{ h = $h; bc = $bc; bt = $bt } } else { if ($m[$k].h -lt $h) { $m[$k].h = $h }; if ($m[$k].bc -lt $bc) { $m[$k].bc = $bc; $m[$k].bt = $bt } } } } }; return $m } + foreach ($stage in @("baseline", "final", "final2", "final3")) { $path = "coverage" + $sep + $stage + "-940.cobertura.xml"; if (-not (Test-Path -LiteralPath $path)) { Write-Output ("UNTOUCHED " + $stage + " DOCUMENT ABSENT"); continue }; [xml]$d = Get-Content -LiteralPath $path -Raw -Encoding UTF8; foreach ($rel in @("Interfaces\IWinForm\PropertyStore.cs", "OutlookObjects\Table\OlTableExtensions.Etl.cs", "EmailIntelligence\SubjectMap\SubjectMapSco.Orchestration.cs")) { $m = & $read $d ("*" + $sep + $rel); $cov = @($m.Values | Where-Object { $_.h -gt 0 }).Count; $br = 0; foreach ($v in $m.Values) { $br += $v.bc }; Write-Output ("UNTOUCHED " + $stage + " " + $rel + " covered=" + $cov + "/" + $m.Count + " branches=" + $br) } } + +Each of the three relative suffixes matches exactly one `filename` value in coverage\final-940.cobertura.xml (observed: `UtilitiesCS\Interfaces\IWinForm\PropertyStore.cs`, `UtilitiesCS\OutlookObjects\Table\OlTableExtensions.Etl.cs`, `UtilitiesCS\EmailIntelligence\SubjectMap\SubjectMapSco.Orchestration.cs`), so the count is per file, not per leaf name. A `DOCUMENT ABSENT` line for `final` or `final2` is recorded as such (the two documents were on disk when this revision was written; their absence removes an observational row, not a gate). + +**CMD-FOREIGN-PROCESS-PROBE** (P2-T7 step (c); lists every running vstest.console, testhost or dotnet-coverage process whose command line is not rooted in this worktree; prints only process names, process ids and the worktree id fragment of the command line, never a path): + + Set-Location -LiteralPath "WORKTREE" + $root = (Get-Location).Path + $all = @(Get-CimInstance -ClassName Win32_Process | Where-Object { $_.Name -like "vstest.console*" -or $_.Name -like "testhost*" -or $_.Name -like "dotnet-coverage*" }) + $foreign = @($all | Where-Object { ([string]$_.CommandLine).IndexOf($root, [System.StringComparison]::OrdinalIgnoreCase) -lt 0 }) + Write-Output ("TEST-PROCESSES: " + $all.Count) + Write-Output ("FOREIGN-TEST-PROCESSES: " + $foreign.Count) + foreach ($p in $foreign) { $m = [regex]::Match([string]$p.CommandLine, "agent-[0-9a-f]+"); $wt = if ($m.Success) { $m.Value } else { "OTHER-OR-UNREADABLE" }; Write-Output ("FOREIGN " + $p.Name + " pid=" + $p.ProcessId + " worktree=" + $wt) } + +A process whose command line cannot be read is counted as foreign (conservative). The probe is re-invoked by the executor until it prints `FOREIGN-TEST-PROCESSES: 0`; the payload contains no sleep and no loop over time. + +**CMD-ADDED-LINES** (counts over the added lines of the anchored `-U0` diff of UtilitiesCS.Test; 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; anchored at ANCHOR-SHA-2 since the post-merge re-anchor, Refs, which P2-T7's `MERGE-UCS-DIFF-EXIT: 0` shows yields the same UtilitiesCS.Test patch as ANCHOR-SHA): + + Set-Location -LiteralPath "WORKTREE" + $added = @(git diff -U0 ANCHOR-SHA-2 -- 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) + Write-Output ("ADDED-TEMP: " + @($added | Where-Object { $_.Contains("Path.GetTemp") -or $_.Contains("File.Create") -or $_.Contains("File.WriteAll") -or $_.Contains("Directory.CreateDirectory") }).Count) + Write-Output ("ADDED-CATCH: " + @($added | Where-Object { $_.Contains("catch") }).Count) + Write-Output ("ADDED-ROOTWALK: " + @($added | Where-Object { $_.Contains("GetRepositoryRoot") -or $_.Contains("GetSolutionFile") -or $_.Contains("TaskMaster.sln") -or $_.Contains("AppDomain") }).Count) + +`ADDED-LINE-COUNT:` is the positive control: it is at least 1 by construction (the rewrite adds lines), so ten zeros over an empty diff cannot pass. The block, not the prose, is the definition of each count. + +**CMD-SWEEP** (host-identifier and raw-document sweep over the feature folder including this plan; the tokens are derived at run time and never written into an artifact): + + Set-Location -LiteralPath "WORKTREE" + $folder = "docs\features\active\2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940" + $all = @(Get-ChildItem -LiteralPath $folder -Recurse -File) + $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: " + @($all | Select-String -Pattern ("(?i)\b" + $account + "\b")).Count) + Write-Output ("PROFILE-LEAF-MATCHES: " + @($all | Select-String -Pattern ("(?i)\b" + $profileLeaf + "\b")).Count) + Write-Output ("MACHINE-TOKEN-MATCHES: " + @($all | Select-String -Pattern ("(?i)\b" + $machine + "\b")).Count) + Write-Output ("WORKTREE-ROOT-MATCHES: " + @($all | Select-String -Pattern ("(?i)" + $root)).Count) + $b = [regex]::Escape([string][char]92) + Write-Output ("USERS-PATH-MATCHES: " + @($all | Select-String -Pattern ("(?i)[a-z]:[" + $b + "/]users[" + $b + "/]")).Count) + Write-Output ("HYGIENE-PROFILE-PATTERN-MATCHES: " + @($all | Select-String -Pattern ("(?i)[a-z]:[" + $b + "/]+users[" + $b + "/]+[a-z0-9_.~-]")).Count) + Write-Output ("RAW-DOCUMENT-FILES: " + @($all | Where-Object { $_.Extension -in @(".trx", ".xml", ".coverage") }).Count) + +The sweep runs over the whole feature folder, this plan included, because a host identifier or a raw document is prohibited wherever it appears. The two path patterns are assembled from `$b` (the escaped backslash) at run time so that the command block survives the Bash tool's handling of backslashes intact (a literal `[\\/]` class inside the payload reached PowerShell as `[\/]` and could not match a backslash-separated path, the round-1 preflight finding); the prose quotation of the rule-B pattern in fact 16 is preceded by a bracket, not by a drive letter and colon, so it matches neither pattern. The `HYGIENE-PROFILE-PATTERN-MATCHES:` count applies the exact rule-B pattern of scripts/hygiene/Test-RepositoryHygiene.Rules.ps1 line 21 (fact 16), so a zero here predicts a zero from the CI guard over the feature folder. The `Owner:` field of this plan is a GitHub handle authored by the maintainer and is not an account or machine token. + +## Token Census Expectations + +File aliases: `PFS` is UtilitiesCS.Test/HelperClasses/PhysicalFileSystemAdapters_Tests.cs, `DIW` is UtilitiesCS.Test/HelperClasses/DirectoryInfoWrapper_Tests.cs, `PDA`, `PFA` and `DIWP` are the three production files of facts 4 to 6. `pre` is the pre-edit value P0-T12 records; `post` is the value after P1-T5 that holds through the end of the run except where a control column says otherwise. Exact values are gated where the column says `exact`; `at least` values are gated as lower bounds (the token is a member call the file must keep exercising, AC4, and a formatter re-wrap cannot lower an occurrence count of a single-line token). Tokens not listed for a file are recorded and not gated. + +| File | Token | pre | post | gate | +| --- | --- | --- | --- | --- | +| PFS | `LINES` | 388 (recorded, not gated; a value that differs by one is a line-terminator artefact and is recorded as such) | at most 500 (expected about 424: Target Source A is 410 lines as written, and the scoped format may re-wrap the longer assertion lines) | exact bound | +| PFS | `GetRepositoryRoot` | 6 | 0 | exact | +| PFS | `GetSolutionFile` | 3 | 0 | exact | +| PFS | `TaskMaster.sln` | at least 1 (13 expected) | 0 | exact post; pre at least 1 | +| PFS | `AppDomain` | 2 (comment 352, code 356) | 0 | exact | +| PFS | `catch (IOException)` | 2 | 0 | exact | +| PFS | `catch` | 2 | 0 | exact | +| PFS | `[TestMethod]` | 4 | 7 | exact | +| PFS | `Assembly.Location` | 5 (comment 351; code 259, 265, 271, 355) | 4 | at least 1 | +| PFS | `MissingOwnedPath(`, `__940_missing_`, `OwnedAssemblyFile`, `OwnedAssemblyDirectory` | 0 each | 10, 1, 4, 4 | `MissingOwnedPath(` at least 4 and `__940_missing_` exact 1; the other two at least 1 | +| PFS | `Exists.Should().BeFalse()` | 0 | 6 | at least 4 (one per missing-path test T1c, T2, T3b, T4) | +| PFS | `new DirectoryInfo(` | 2 (145 and the helper's construction at 357) | 2 | exact, and equal to `new DirectoryInfo(MissingOwnedPath(` (2) plus `new DirectoryInfo(RootedFixturePath)` (0) | +| PFS | `new FileInfo(` | 2 (320, 376) | 3 | exact, and equal to `new FileInfo(MissingOwnedPath(` (2) plus `new FileInfo(typeof(` (1) | +| PFS | `RootedFixturePath`, `using Moq;`, `FileShare.None` | 0, 0, 3 (the three `FileShare.None` occurrences are the comments at 207, 240 and 246) | 0 each | exact | +| PFS | `using System.Linq;` | 0 | 1 | exact | +| PFS | `FileShare.ReadWrite` | 5 (comment 203; code 217, 262, 268, 274) | 4 | at least 4 | +| PFS | `Thread.Sleep`, `Task.Delay`, `[Timeout`, `DoNotParallelize`, `Retry`, `Path.GetTemp`, `File.Create`, `File.WriteAll`, `Directory.CreateDirectory` | 0 each | 0 each | exact | +| PFS | `Throw()` | 5 | 20 (7 in T1c, 8 in T3b, 5 in T4) | exact 20 before P1-T7 pins a type; after pinning, 20 minus the number of pinned assertions whose predicted type was this one plus the number whose observed type is this one | +| PFS | `Throw()` | 3 | 3 | exact 3 before P1-T7 pins a type; after pinning, 3 minus the number of pinned assertions whose predicted type was this one plus the number whose observed type is this one | +| PFS | `Throw()` for each type T pinned by P1-T7 | 0 | `PINNED-TOKEN Throw() = ` as P1-T7 records it | exact n; and `THROW-SUM:`, the sum of the `TOKEN` values of the census literals `Throw()`, `Throw()` and each literal appended by P1-T7 (a census-token sum, not a scan for every `Throw<`; the one `Throw()` occurrence is not a census token and is excluded), is exact 23 after every census from P1-T3 onward | +| PFS | `.MemberCount` | 0 | 2 (one per `GetObjectData` call, AC4) | at least 2 | +| PFS | `.Create()`, `.OpenRead()`, `.OpenText()`, `.AppendText()`, `.OpenWrite()`, `.Delete(recursive: true)`, `.Length` | 1 each | 1 each | at least 1 | +| PFS | `.Create(`, `.CreateSubdirectory(`, `.GetObjectData(`, `.SetAccessControl(`, `.Delete()`, `.MoveTo(`, `.CopyTo(`, `.Replace(`, `.Root` | 2 each | 2 each | at least 2 | +| PFS | `.EnumerateDirectories(`, `.EnumerateFiles(`, `.EnumerateFileSystemInfos(`, `.GetDirectories(`, `.GetFiles(`, `.GetFileSystemInfos(`, `.Open(`, `.Parent` | 3 each (`.Parent` counts the helper's walk-up at 364) | 3 each | at least 3 | +| PFS | `.GetAccessControl(`, `.Refresh()`, `.ToString()`, `.Attributes`, `.IsReadOnly`, `.CreationTimeUtc`, `.LastAccessTimeUtc`, `.LastWriteTimeUtc` | 6, 2, 4, 2, 2, 4, 4, 4 (each pre-edit self-assignment line carries the token twice) | 6, 4, 4, 6, 3, 6, 6, 6 | at least 2 | +| DIW | `LINES` | 393 (recorded, not gated) | at most 500 (expected about 395) | exact bound | +| DIW | `GetRepositoryRoot` | 5 | 0 | exact | +| DIW | `TaskMaster.sln` | 3 | 0 | exact | +| DIW | `AppDomain`, `File.Exists(` | 1 each | 0 each | exact | +| DIW | `catch`, `GetSolutionFile`, `MissingOwnedPath(`, `__940_missing_` | 0 each | 0 each | exact | +| DIW | `[TestMethod]` | 8 | 8 | exact | +| DIW | `RootedFixturePath` | 0 | 3 (the declaration and two uses; the comment names it nowhere) | exact | +| DIW | `new DirectoryInfo(` | 1 (377) | 2 | exact, and equal to `new DirectoryInfo(RootedFixturePath)` (2) | +| DIW | `new FileInfo(` | 0 | 1 | exact, and equal to `new FileInfo(typeof(` (1) | +| DIW | `Assembly.Location`, `OwnedAssemblyFile`, `OwnedAssemblyDirectory` | 0 each | 1, 4, 3 | at least 1 | +| DIW | `using Moq;`, `using System.Linq;` | 1 each | 1 each | exact | +| DIW | `Thread.Sleep`, `Task.Delay`, `[Timeout`, `DoNotParallelize`, `Retry`, `Path.GetTemp`, `File.Create`, `File.WriteAll`, `Directory.CreateDirectory` | 0 each | 0 each | exact | +| DIW | `.GetFiles(`, `.GetDirectories(`, `.EnumerateFileSystemInfos(`, `.ToString()` | 7, 7, 7, 4 (the Moq-based tests carry six, six, six and two of them) | 7, 7, 8, 4 | at least 7, 7, 7, 4 | +| PDA | `!_directoryInfo.Exists` | 0 | 0 | C1 (P1-T9): 1; after P1-T10: 0 | +| PDA | `Array.Empty()` | 0 | 0 | C2 (P1-T11): 1; after P1-T12: 0 | +| PDA | `set { }` | 0 | 0 | C3 (P1-T13): 1; after P1-T14: 0 | +| PDA | `public void Delete() { }` | 0 | 0 | C4 (P1-T15): 1; after P1-T16: 0 | +| PDA | `_directoryInfo.FullName` | 1 | 1 | C8 (P1-T23): 2; after P1-T24: 1 | +| PFA | `public long Length => 0;` | 0 | 0 | C5 (P1-T17): 1; after P1-T18: 0 | +| PFA | `set { }` | 0 | 0 | C6 (P1-T19): 1; after P1-T20: 0 | +| PFA | `public void MoveTo(string destFileName) { }` | 0 | 0 | C7 (P1-T21): 1; after P1-T22: 0 | +| DIWP | `Array.Empty()` | 0 | 0 | C9 (P1-T25): 1; after P1-T26: 0 | +| DIWP | `Enumerable.Empty()` | 0 | 0 | C10 (P1-T27): 1; after P1-T28: 0 | +| DIWP | `return string.Empty;` | 0 | 0 | C11 (P1-T29): 1; after P1-T30: 0 | + +No comment the executor writes contains the identifiers `RootedFixturePath` or `MissingOwnedPath`, the prefix `__940_missing_`, the words `catch` or `Retry`, the literals `TaskMaster.sln`, `GetRepositoryRoot`, `GetSolutionFile`, `FileShare.None`, or any member token of the table followed by its opening parenthesis; the Target Source comments below already comply and are written verbatim. + +## Target Source + +Each block is shown with its outermost lines at column 0 of the block; the executor writes the file content exactly as shown (the four-space block indentation of this document is not content) and P1-T5's scoped format normalizes whitespace. Whitespace is not a counted token. + +### A. New content of UtilitiesCS.Test/HelperClasses/PhysicalFileSystemAdapters_Tests.cs (entire file, replacing every line) + + using System; + using System.IO; + using System.Linq; + using System.Reflection; + using System.Runtime.Serialization; + using System.Security.AccessControl; + using FluentAssertions; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using UtilitiesCS.HelperClasses.FileSystem; + + namespace UtilitiesCS.Test.HelperClasses + { + [TestClass] + public class PhysicalFileSystemAdapters_Tests + { + // Issue #940 fixtures. The running host's own loaded assembly image exists for the whole + // run and its physical path is the project output directory, so that directory and its + // parent are owned by this test process; no repository file and no temporary file is + // involved. Every mutating member is invoked either on a path under the owned directory + // that is asserted absent beforehand, so the wrapped BCL member reports the missing path + // before touching the disk, or as a no-op on an owned entry that already exists. + private static FileInfo OwnedAssemblyFile => + new FileInfo(typeof(PhysicalFileSystemAdapters_Tests).Assembly.Location); + + private static DirectoryInfo OwnedAssemblyDirectory => OwnedAssemblyFile.Directory; + + private static string MissingOwnedPath(string suffix) => + Path.Combine(OwnedAssemblyDirectory.FullName, "__940_missing_" + suffix); + + [TestMethod] + public void PhysicalDirectoryInfoAdapter_AccessorsAndNoOpCreation_MirrorOwnedDirectory() + { + // Arrange + var directory = OwnedAssemblyDirectory; + var parent = directory.Parent; + var adapter = new PhysicalDirectoryInfoAdapter(directory); + var parentAdapter = new PhysicalDirectoryInfoAdapter(parent); + var security = adapter.GetAccessControl(); + var serialized = new SerializationInfo( + typeof(PhysicalDirectoryInfoAdapter), + new FormatterConverter() + ); + var context = new StreamingContext(StreamingContextStates.All); + + // Act: the creation members are invoked on entries that already exist, so nothing + // is created; the returned wrappers name the owned directory, which proves both the + // delegation and the wrapping. + adapter.Create(); + adapter.Create(directory.GetAccessControl()); + var createdSubdirectory = parentAdapter.CreateSubdirectory(directory.Name); + var createdSubdirectoryWithSecurity = parentAdapter.CreateSubdirectory( + directory.Name, + parent.GetAccessControl() + ); + var accessWithSections = adapter.GetAccessControl(AccessControlSections.Access); + adapter.GetObjectData(serialized, context); + adapter.Refresh(); + adapter.SetAccessControl(security); + var toStringValue = adapter.ToString(); + + // Assert + adapter.Exists.Should().BeTrue(); + adapter.Attributes.Should().Be(directory.Attributes); + adapter.CreationTime.Should().Be(directory.CreationTime); + adapter.CreationTimeUtc.Should().Be(directory.CreationTimeUtc); + adapter.LastAccessTime.Should().Be(directory.LastAccessTime); + adapter.LastAccessTimeUtc.Should().Be(directory.LastAccessTimeUtc); + adapter.LastWriteTime.Should().Be(directory.LastWriteTime); + adapter.LastWriteTimeUtc.Should().Be(directory.LastWriteTimeUtc); + adapter.Extension.Should().Be(directory.Extension); + adapter.FullName.Should().Be(directory.FullName); + adapter.Name.Should().Be(directory.Name); + adapter.Parent.FullName.Should().Be(parent.FullName); + adapter.Root.FullName.Should().Be(directory.Root.FullName); + createdSubdirectory.FullName.Should().Be(directory.FullName); + createdSubdirectoryWithSecurity.FullName.Should().Be(directory.FullName); + security.Should().NotBeNull(); + accessWithSections.Should().NotBeNull(); + toStringValue.Should().Be(directory.ToString()); + serialized.MemberCount.Should().BeGreaterThan(0, "GetObjectData adds values"); + } + + [TestMethod] + public void PhysicalDirectoryInfoAdapter_Enumeration_WrapsOwnedDirectoryEntries() + { + // Arrange + var directory = OwnedAssemblyDirectory; + var fileName = OwnedAssemblyFile.Name; + var directoryName = directory.Name; + var adapter = new PhysicalDirectoryInfoAdapter(directory); + var parentAdapter = new PhysicalDirectoryInfoAdapter(directory.Parent); + + // Act: files are enumerated from the owned directory and directories from its + // parent, because the parent containing the owned directory is guaranteed by + // construction while subdirectories inside the owned directory are not. + var enumeratedDirectories = parentAdapter.EnumerateDirectories(); + var enumeratedDirectoriesByPattern = parentAdapter.EnumerateDirectories(directoryName); + var enumeratedDirectoriesByPatternAndOption = parentAdapter.EnumerateDirectories( + directoryName, + SearchOption.TopDirectoryOnly + ); + var enumeratedFiles = adapter.EnumerateFiles(); + var enumeratedFilesByPattern = adapter.EnumerateFiles(fileName); + var enumeratedFilesByPatternAndOption = adapter.EnumerateFiles( + fileName, + SearchOption.TopDirectoryOnly + ); + var enumeratedInfos = adapter.EnumerateFileSystemInfos(); + var enumeratedInfosByPattern = parentAdapter.EnumerateFileSystemInfos(directoryName); + var enumeratedInfosByPatternAndOption = adapter.EnumerateFileSystemInfos( + fileName, + SearchOption.TopDirectoryOnly + ); + var directories = parentAdapter.GetDirectories(); + var directoriesByPattern = parentAdapter.GetDirectories(directoryName); + var directoriesByPatternAndOption = parentAdapter.GetDirectories( + directoryName, + SearchOption.TopDirectoryOnly + ); + var files = adapter.GetFiles(); + var filesByPattern = adapter.GetFiles(fileName); + var filesByPatternAndOption = adapter.GetFiles(fileName, SearchOption.TopDirectoryOnly); + var infos = parentAdapter.GetFileSystemInfos(); + var infosByPattern = adapter.GetFileSystemInfos(fileName); + var infosByPatternAndOption = parentAdapter.GetFileSystemInfos( + directoryName, + SearchOption.TopDirectoryOnly + ); + + // Assert + files.Select(item => item.Name).Should().Contain(fileName); + files.Should().OnlyContain(item => item is FileInfoWrapper); + filesByPattern.Should().ContainSingle().Which.Name.Should().Be(fileName); + filesByPatternAndOption.Should().ContainSingle().Which.Name.Should().Be(fileName); + enumeratedFiles.Select(item => item.Name).Should().Contain(fileName); + enumeratedFilesByPattern.Should().ContainSingle().Which.Name.Should().Be(fileName); + enumeratedFilesByPatternAndOption.Should().ContainSingle().Which.Name.Should().Be(fileName); + directories.Select(item => item.Name).Should().Contain(directoryName); + directories.Should().OnlyContain(item => item is DirectoryInfoWrapper); + directoriesByPattern.Should().ContainSingle().Which.Name.Should().Be(directoryName); + directoriesByPatternAndOption.Should().ContainSingle().Which.Name.Should().Be(directoryName); + enumeratedDirectories.Select(item => item.Name).Should().Contain(directoryName); + enumeratedDirectoriesByPattern.Should().ContainSingle().Which.Name.Should().Be(directoryName); + enumeratedDirectoriesByPatternAndOption.Should().ContainSingle().Which.Name.Should().Be(directoryName); + enumeratedInfos.OfType().Select(item => item.Name).Should().Contain(fileName); + enumeratedInfosByPattern.Should().ContainSingle().Which.Should().BeOfType(); + enumeratedInfosByPatternAndOption.Should().ContainSingle().Which.Should().BeOfType(); + infos.OfType().Select(item => item.Name).Should().Contain(directoryName); + infosByPattern.Should().ContainSingle().Which.Should().BeOfType(); + infosByPatternAndOption.Should().ContainSingle().Which.Should().BeOfType(); + } + + [TestMethod] + public void PhysicalDirectoryInfoAdapter_SettersOnMissingDirectory_ThrowWithoutCreatingEntries() + { + // Arrange + var adapter = new PhysicalDirectoryInfoAdapter( + new DirectoryInfo(MissingOwnedPath("directory-setters")) + ); + var stamp = new DateTime(2024, 1, 2, 3, 4, 5, DateTimeKind.Utc); + adapter.Exists.Should().BeFalse(); + + // Act + Action setCreationTime = () => adapter.CreationTime = stamp; + Action setCreationTimeUtc = () => adapter.CreationTimeUtc = stamp; + Action setLastAccessTime = () => adapter.LastAccessTime = stamp; + Action setLastAccessTimeUtc = () => adapter.LastAccessTimeUtc = stamp; + Action setLastWriteTime = () => adapter.LastWriteTime = stamp; + Action setLastWriteTimeUtc = () => adapter.LastWriteTimeUtc = stamp; + Action setAttributes = () => adapter.Attributes = FileAttributes.Directory; + + // Assert: each setter reaches the wrapped BCL member, which reports the missing path. + setCreationTime.Should().Throw(); + setCreationTimeUtc.Should().Throw(); + setLastAccessTime.Should().Throw(); + setLastAccessTimeUtc.Should().Throw(); + setLastWriteTime.Should().Throw(); + setLastWriteTimeUtc.Should().Throw(); + setAttributes.Should().Throw(); + adapter.Refresh(); + adapter.Exists.Should().BeFalse(); + } + + [TestMethod] + public void PhysicalDirectoryInfoAdapter_MissingDirectoryAndUnsupportedInfo_BranchesBehaveAsExpected() + { + // Arrange + var adapter = new PhysicalDirectoryInfoAdapter( + new DirectoryInfo(MissingOwnedPath("directory-delete")) + ); + var wrapMethod = typeof(PhysicalDirectoryInfoAdapter).GetMethod( + "WrapFileSystemInfo", + BindingFlags.Static | BindingFlags.NonPublic + )!; + var directoryMoveTarget = MissingOwnedPath("directory-moved"); + adapter.Exists.Should().BeFalse(); + Directory.Exists(directoryMoveTarget).Should().BeFalse(); + + // Act + Action delete = () => adapter.Delete(); + Action deleteRecursive = () => adapter.Delete(recursive: true); + Action move = () => adapter.MoveTo(directoryMoveTarget); + Action wrapUnsupported = () => + wrapMethod.Invoke(null, new object[] { new UnsupportedInfo() }); + + // Assert + delete.Should().Throw(); + deleteRecursive.Should().Throw(); + move.Should().Throw(); + wrapUnsupported + .Should() + .Throw() + .WithInnerException(); + } + + [TestMethod] + public void PhysicalFileInfoAdapter_PropertiesStreamsAndAccessors_MirrorFileInfo() + { + // Arrange + var file = OwnedAssemblyFile; + var adapter = new PhysicalFileInfoAdapter(file); + var security = adapter.GetAccessControl(); + var serialized = new SerializationInfo( + typeof(PhysicalFileInfoAdapter), + new FormatterConverter() + ); + var context = new StreamingContext(StreamingContextStates.All); + + // Act: the read-only opens target the running host's own loaded assembly image. The + // three-argument Open requests read-write sharing; OpenRead and OpenText request read + // sharing, which admits the loader's own read handles on the image. + bool openModeReadSharedCanRead; + using ( + var openModeReadShared = adapter.Open( + FileMode.Open, + FileAccess.Read, + FileShare.ReadWrite + ) + ) + { + openModeReadSharedCanRead = openModeReadShared.CanRead; + } + + bool openReadCanRead; + using (var openRead = adapter.OpenRead()) + { + openReadCanRead = openRead.CanRead; + } + + string openTextLine; + using (var openText = adapter.OpenText()) + { + openTextLine = openText.ReadLine(); + } + + // The write-mode members and the two-argument Open are exercised through the + // adapter's internal injectable-delegate seam with test-owned sentinel streams, so no + // write handle and no exclusive handle is ever requested on the image. The append + // sentinel wraps an in-memory stream because StreamWriter requires a writable backing + // stream; the other sentinels are read-only opens of the image with read-write sharing. + using var sentinelAppendStream = new MemoryStream(); + using var sentinelAppendWriter = new StreamWriter( + sentinelAppendStream, + System.Text.Encoding.UTF8, + 1024, + leaveOpen: true + ); + using var sentinelOpenModeStream = new FileStream( + typeof(PhysicalFileSystemAdapters_Tests).Assembly.Location, + FileMode.Open, + FileAccess.Read, + FileShare.ReadWrite + ); + using var sentinelOpenModeAndAccessStream = new FileStream( + typeof(PhysicalFileSystemAdapters_Tests).Assembly.Location, + FileMode.Open, + FileAccess.Read, + FileShare.ReadWrite + ); + using var sentinelOpenWriteStream = new FileStream( + typeof(PhysicalFileSystemAdapters_Tests).Assembly.Location, + FileMode.Open, + FileAccess.Read, + FileShare.ReadWrite + ); + var seamAdapter = new PhysicalFileInfoAdapter( + file, + () => sentinelAppendWriter, + _ => sentinelOpenModeStream, + (mode, access) => sentinelOpenModeAndAccessStream, + () => sentinelOpenWriteStream + ); + + var accessWithSections = adapter.GetAccessControl(AccessControlSections.Access); + adapter.GetObjectData(serialized, context); + adapter.Refresh(); + adapter.SetAccessControl(security); + var toStringValue = adapter.ToString(); + + // Assert + adapter.Exists.Should().BeTrue(); + adapter.Extension.Should().Be(".dll"); + adapter.FullName.Should().Be(file.FullName); + adapter.Name.Should().Be(file.Name); + adapter.Directory.FullName.Should().Be(file.Directory.FullName); + adapter.DirectoryName.Should().Be(file.DirectoryName); + adapter.Length.Should().BeGreaterThan(0); + adapter.IsReadOnly.Should().Be(file.IsReadOnly); + adapter.Attributes.Should().Be(file.Attributes); + adapter.CreationTime.Should().Be(file.CreationTime); + adapter.CreationTimeUtc.Should().Be(file.CreationTimeUtc); + adapter.LastAccessTime.Should().Be(file.LastAccessTime); + adapter.LastAccessTimeUtc.Should().Be(file.LastAccessTimeUtc); + adapter.LastWriteTime.Should().Be(file.LastWriteTime); + adapter.LastWriteTimeUtc.Should().Be(file.LastWriteTimeUtc); + openModeReadSharedCanRead.Should().BeTrue(); + openReadCanRead.Should().BeTrue(); + openTextLine.Should().NotBeNull(); + seamAdapter.AppendText().Should().BeSameAs(sentinelAppendWriter); + seamAdapter.Open(FileMode.Open).Should().BeSameAs(sentinelOpenModeStream); + seamAdapter + .Open(FileMode.Open, FileAccess.Read) + .Should() + .BeSameAs(sentinelOpenModeAndAccessStream); + seamAdapter.OpenWrite().Should().BeSameAs(sentinelOpenWriteStream); + security.Should().NotBeNull(); + accessWithSections.Should().NotBeNull(); + toStringValue.Should().Be(file.ToString()); + serialized.MemberCount.Should().BeGreaterThan(0, "GetObjectData adds values"); + } + + [TestMethod] + public void PhysicalFileInfoAdapter_SettersOnMissingFile_ThrowWithoutCreatingFiles() + { + // Arrange + var adapter = new PhysicalFileInfoAdapter( + new FileInfo(MissingOwnedPath("file-setters.txt")) + ); + var stamp = new DateTime(2024, 1, 2, 3, 4, 5, DateTimeKind.Utc); + adapter.Exists.Should().BeFalse(); + + // Act + Action setIsReadOnly = () => adapter.IsReadOnly = true; + Action setAttributes = () => adapter.Attributes = FileAttributes.Normal; + Action setCreationTime = () => adapter.CreationTime = stamp; + Action setCreationTimeUtc = () => adapter.CreationTimeUtc = stamp; + Action setLastAccessTime = () => adapter.LastAccessTime = stamp; + Action setLastAccessTimeUtc = () => adapter.LastAccessTimeUtc = stamp; + Action setLastWriteTime = () => adapter.LastWriteTime = stamp; + Action setLastWriteTimeUtc = () => adapter.LastWriteTimeUtc = stamp; + + // Assert: each setter reaches the wrapped BCL member, which reports the missing path. + setIsReadOnly.Should().Throw(); + setAttributes.Should().Throw(); + setCreationTime.Should().Throw(); + setCreationTimeUtc.Should().Throw(); + setLastAccessTime.Should().Throw(); + setLastAccessTimeUtc.Should().Throw(); + setLastWriteTime.Should().Throw(); + setLastWriteTimeUtc.Should().Throw(); + adapter.Refresh(); + adapter.Exists.Should().BeFalse(); + } + + [TestMethod] + public void PhysicalFileInfoAdapter_MissingFileBranches_ThrowOrNoOpWithoutCreatingFiles() + { + // Arrange: every destination and backup is another missing owned path, so no existing + // file is a target under any outcome. + var adapter = new PhysicalFileInfoAdapter( + new FileInfo(MissingOwnedPath("file-source.txt")) + ); + var copyTarget = MissingOwnedPath("file-copy.txt"); + var moveTarget = MissingOwnedPath("file-moved.txt"); + var replaceTarget = MissingOwnedPath("file-replace.txt"); + var backupTarget = MissingOwnedPath("file-backup.bak"); + adapter.Exists.Should().BeFalse(); + File.Exists(copyTarget).Should().BeFalse(); + File.Exists(moveTarget).Should().BeFalse(); + File.Exists(replaceTarget).Should().BeFalse(); + File.Exists(backupTarget).Should().BeFalse(); + + // Act + Action delete = () => adapter.Delete(); + Action copy = () => adapter.CopyTo(copyTarget); + Action copyOverwrite = () => adapter.CopyTo(copyTarget, overwrite: true); + Action move = () => adapter.MoveTo(moveTarget); + Action replace = () => adapter.Replace(replaceTarget, backupTarget); + Action replaceIgnore = () => + adapter.Replace(replaceTarget, backupTarget, ignoreMetadataErrors: true); + + // Assert + delete.Should().NotThrow(); + copy.Should().Throw(); + copyOverwrite.Should().Throw(); + move.Should().Throw(); + replace.Should().Throw(); + replaceIgnore.Should().Throw(); + } + + private sealed class UnsupportedInfo : FileSystemInfo + { + public override bool Exists => false; + + public override string Name => "unsupported"; + + public override void Delete() { } + } + } + } + +The literal test names quoted here, `PhysicalDirectoryInfoAdapter_AccessorsAndNoOpCreation_MirrorOwnedDirectory`, `PhysicalDirectoryInfoAdapter_Enumeration_WrapsOwnedDirectoryEntries`, `PhysicalDirectoryInfoAdapter_SettersOnMissingDirectory_ThrowWithoutCreatingEntries` and `PhysicalFileInfoAdapter_SettersOnMissingFile_ThrowWithoutCreatingFiles`, are new to the tree; the other three names are unchanged from fact 1. No test name is a substring of another test name in either class, so a per-method `FullyQualifiedName~` filter selects exactly one method. The `UnsupportedInfo` nested class is the pre-edit content of lines 379 to 386, unchanged. The two `serialized.MemberCount` assertions (AC4, amended 2026-09-30) make the `GetObjectData` delegation observable: both adapters forward to the wrapped BCL object (PDA lines 190 to 191, PFA lines 141 to 142), and on .NET Framework 4.8 `FileSystemInfo.GetObjectData`, which neither `DirectoryInfo` nor `FileInfo` overrides, adds the two entries `OriginalPath` and `FullPath` to the `SerializationInfo` (reference source, System.IO.FileSystemInfo), so `MemberCount` reads 2 after either call; the derivation is from the reference source and not from a run, so P1-T7 branch (f) names the stop condition for the case where the observation differs, and the executor neither removes nor weakens the assertion. The `because` clause makes the failure message carry the fixed phrase `because GetObjectData adds values`, which distinguishes it from the `adapter.Length` assertion of the same method that prints the same `to be greater than` phrase. + +### B. Edits to UtilitiesCS.Test/HelperClasses/DirectoryInfoWrapper_Tests.cs (pre-edit numbering; apply bottom-up) + +1. Delete lines 374 through 391 (the blank line and `GetRepositoryRoot`), so that line 373 (` }`) is followed by the class-closing brace that was at 392. +2. Replace line 86 ` var directory = GetRepositoryRoot();` (inside `ToString_ShouldDelegateToWrappedDirectoryInfo`) with ` var directory = new DirectoryInfo(RootedFixturePath);`. No other line of that test changes. +3. Replace lines 64 through 80 (`EnumerateFileSystemInfos_ShouldWrapDirectoriesAndFiles`) with: + + public void EnumerateFileSystemInfos_ShouldWrapDirectoriesAndFiles() + { + // Arrange + var directory = OwnedAssemblyDirectory; + var wrapper = new DirectoryInfoWrapper(directory); + var parentWrapper = new DirectoryInfoWrapper(directory.Parent); + + // Act + var fileSystemInfos = wrapper.EnumerateFileSystemInfos().ToArray(); + var parentFileSystemInfos = parentWrapper.EnumerateFileSystemInfos().ToArray(); + + // Assert + fileSystemInfos + .OfType() + .Select(item => item.Name) + .Should() + .Contain(OwnedAssemblyFile.Name); + parentFileSystemInfos + .OfType() + .Select(item => item.Name) + .Should() + .Contain(directory.Name); + } + +4. Replace lines 43 through 61 (`GetDirectoriesAndGetFiles_ShouldReturnWrappedEntries`) with: + + public void GetDirectoriesAndGetFiles_ShouldReturnWrappedEntries() + { + // Arrange + var directory = OwnedAssemblyDirectory; + var wrapper = new DirectoryInfoWrapper(directory); + var parentWrapper = new DirectoryInfoWrapper(directory.Parent); + + // Act + var files = wrapper.GetFiles(); + var directories = parentWrapper.GetDirectories(); + + // Assert + files.Should().NotBeEmpty(); + files.Should().OnlyContain(item => item is FileInfoWrapper); + files.Select(item => item.Name).Should().Contain(OwnedAssemblyFile.Name); + + directories.Should().NotBeEmpty(); + directories.Should().OnlyContain(item => item is DirectoryInfoWrapper); + directories.Select(item => item.Name).Should().Contain(directory.Name); + } + +5. Replace line 30 ` var directory = GetRepositoryRoot();` (inside `Properties_ShouldMirrorWrappedDirectoryInfo`) with ` var directory = new DirectoryInfo(RootedFixturePath);`. No other line of that test changes. +6. Insert after line 15 (the class opening brace) the fixture block, followed by one blank line: + + // Issue #940 fixtures. The rooted literal below is not expected to exist and does not point + // into the repository: the two metadata tests assert only path computations mirrored against + // the BCL object, so the outcome does not depend on the machine. The owned entries are the + // running host's own loaded assembly image and its directory, which exist for the whole run; + // the two enumeration tests only read them. + private const string RootedFixturePath = @"C:\Repo\fixture"; + + private static FileInfo OwnedAssemblyFile => + new FileInfo(typeof(DirectoryInfoWrapper_Tests).Assembly.Location); + + private static DirectoryInfo OwnedAssemblyDirectory => OwnedAssemblyFile.Directory; + +The `[TestMethod]` attribute lines 42 and 63 stay in place above the replaced method bodies of edits 3 and 4. The four Moq-based tests (pre-edit lines 96 to 373) and the constructor test (16 to 24) are untouched. The rooted literal `C:\Repo\fixture` is not a file-system root, so `Parent` is non-null and `Parent.FullName` is `C:\Repo`. No other line changes. + +### C. Negative-control mutations (each applied by the Edit tool to one production file and reverted by `git checkout -- `; no control mutates a test file) + +- C1 (P1-T9), PDA line 34: replace `public bool Exists => _directoryInfo.Exists;` with `public bool Exists => !_directoryInfo.Exists;`. +- C2 (P1-T11), PDA lines 160 to 161: replace the two lines `public IFileInfo[] GetFiles() =>` and `_directoryInfo.GetFiles().Select(file => new FileInfoWrapper(file)).ToArray();` with the single line `public IFileInfo[] GetFiles() => Array.Empty();` (`using System;` at PDA line 2 resolves `Array`). +- C3 (P1-T13), PDA line 25: replace `set => _directoryInfo.CreationTime = value;` with `set { }`. +- C4 (P1-T15), PDA line 87: replace `public void Delete() => _directoryInfo.Delete();` with `public void Delete() { }`. +- C5 (P1-T17), PFA line 116: replace `public long Length => _fileInfo.Length;` with `public long Length => 0;`. +- C6 (P1-T19), PFA line 113: replace `set => _fileInfo.IsReadOnly = value;` with `set { }`. +- C7 (P1-T21), PFA line 144: replace `public void MoveTo(string destFileName) => _fileInfo.MoveTo(destFileName);` with `public void MoveTo(string destFileName) { }`. +- C8 (P1-T23), PDA line 64: replace `public string Name => _directoryInfo.Name;` with `public string Name => _directoryInfo.FullName;`. +- C9 (P1-T25), DIWP line 188: replace `return _directoryInfo.GetFiles();` with `return Array.Empty();` (`using System;` at DIWP line 2). +- C10 (P1-T27), DIWP line 145: replace `return _directoryInfo.EnumerateFileSystemInfos();` with `return Enumerable.Empty();` (`using System.Linq;` at DIWP line 5). +- C11 (P1-T29), DIWP line 228: replace `return _directoryInfo.ToString();` with `return string.Empty;`. + +A mutation compiles under the plain Build target of `CMD-BUILD` whatever analyzer suggestion it raises (fact 7); the analyzer and nullable gates of Phase 2 run only after every mutation has been reverted (P1-T31). + +## Control Table + +Each control is one mutate-and-observe task (`[expect-fail]`) followed by one revert-and-confirm task, both under the Negative-control mechanics convention. The `Filter` column is substituted for `FILTERARG` as the double-quoted test-case-filter switch followed by the column's value; `NAMES` is `NAMES-PFS` for a PFS test and `NAMES-DIW` for a DIW test; the expected `total` is 1. The `Phrase` column is the fixed text (fact 23) the failing test's `MESSAGE` line must contain; the `Census` column is the transition `CMD-CENSUS` must show on the mutated file (mutated value, then the value after revert). + +| Control | Test method (class) | Tasks | Mutated file and line | Census | Filter | Phrase | Artifact (regression-testing/) | +| --- | --- | --- | --- | --- | --- | --- | --- | +| C1 | `PhysicalDirectoryInfoAdapter_AccessorsAndNoOpCreation_MirrorOwnedDirectory` (PFS) | P1-T9, P1-T10 | PDA 34 | `!_directoryInfo.Exists` 1, then 0 | `FullyQualifiedName~HelperClasses.PhysicalFileSystemAdapters_Tests.PhysicalDirectoryInfoAdapter_AccessorsAndNoOpCreation_MirrorOwnedDirectory` | `but found False` (the first assertion, `adapter.Exists.Should().BeTrue()`) | mutation-directory-exists-inverted.md | +| C2 | `PhysicalDirectoryInfoAdapter_Enumeration_WrapsOwnedDirectoryEntries` (PFS) | P1-T11, P1-T12 | PDA 160 to 161 | `Array.Empty()` 1, then 0 | `FullyQualifiedName~HelperClasses.PhysicalFileSystemAdapters_Tests.PhysicalDirectoryInfoAdapter_Enumeration_WrapsOwnedDirectoryEntries` | `to contain "UtilitiesCS.Test.dll"` (the first assertion, over `files`) | mutation-directory-getfiles-empty.md | +| C3 | `PhysicalDirectoryInfoAdapter_SettersOnMissingDirectory_ThrowWithoutCreatingEntries` (PFS) | P1-T13, P1-T14 | PDA 25 | `set { }` 1, then 0 | `FullyQualifiedName~HelperClasses.PhysicalFileSystemAdapters_Tests.PhysicalDirectoryInfoAdapter_SettersOnMissingDirectory_ThrowWithoutCreatingEntries` | `but no exception was thrown` (the first assertion, `setCreationTime`) | mutation-directory-creationtime-setter-noop.md | +| C4 | `PhysicalDirectoryInfoAdapter_MissingDirectoryAndUnsupportedInfo_BranchesBehaveAsExpected` (PFS) | P1-T15, P1-T16 | PDA 87 | `public void Delete() { }` 1, then 0 | `FullyQualifiedName~HelperClasses.PhysicalFileSystemAdapters_Tests.PhysicalDirectoryInfoAdapter_MissingDirectoryAndUnsupportedInfo_BranchesBehaveAsExpected` | `but no exception was thrown` (the first assertion, `delete`) | mutation-directory-delete-noop.md | +| C5 | `PhysicalFileInfoAdapter_PropertiesStreamsAndAccessors_MirrorFileInfo` (PFS) | P1-T17, P1-T18 | PFA 116 | `public long Length => 0;` 1, then 0 | `FullyQualifiedName~HelperClasses.PhysicalFileSystemAdapters_Tests.PhysicalFileInfoAdapter_PropertiesStreamsAndAccessors_MirrorFileInfo` | `to be greater than` (`adapter.Length`; the six assertions before it do not read `Length`, and the `MemberCount` assertion that prints the same phrase sits last in the method and is not reached once `Length` fails, so a `MESSAGE` that also contains `because GetObjectData adds values` is a `MUTATION PREDICTION MISMATCH`) | mutation-file-length-zero.md | +| C6 | `PhysicalFileInfoAdapter_SettersOnMissingFile_ThrowWithoutCreatingFiles` (PFS) | P1-T19, P1-T20 | PFA 113 | `set { }` 1, then 0 | `FullyQualifiedName~HelperClasses.PhysicalFileSystemAdapters_Tests.PhysicalFileInfoAdapter_SettersOnMissingFile_ThrowWithoutCreatingFiles` | `but no exception was thrown` (the first assertion, `setIsReadOnly`) | mutation-file-isreadonly-setter-noop.md | +| C7 | `PhysicalFileInfoAdapter_MissingFileBranches_ThrowOrNoOpWithoutCreatingFiles` (PFS) | P1-T21, P1-T22 | PFA 144 | `public void MoveTo(string destFileName) { }` 1, then 0 | `FullyQualifiedName~HelperClasses.PhysicalFileSystemAdapters_Tests.PhysicalFileInfoAdapter_MissingFileBranches_ThrowOrNoOpWithoutCreatingFiles` | `but no exception was thrown` (`move`; `delete`, `copy` and `copyOverwrite` before it are unaffected) | mutation-file-moveto-noop.md | +| C8 | `Properties_ShouldMirrorWrappedDirectoryInfo` (DIW) | P1-T23, P1-T24 | PDA 64 | `_directoryInfo.FullName` 2, then 1 | `FullyQualifiedName~HelperClasses.DirectoryInfoWrapper_Tests.Properties_ShouldMirrorWrappedDirectoryInfo` | `"fixture"` (the `wrapper.Name` assertion; `Exists` and `FullName` before it are unaffected) | mutation-directory-name-fullname.md | +| C9 | `GetDirectoriesAndGetFiles_ShouldReturnWrappedEntries` (DIW) | P1-T25, P1-T26 | DIWP 188 | `Array.Empty()` 1, then 0 | `FullyQualifiedName~HelperClasses.DirectoryInfoWrapper_Tests.GetDirectoriesAndGetFiles_ShouldReturnWrappedEntries` | `not to be empty` (the first assertion, `files.Should().NotBeEmpty()`) | mutation-wrapper-getfiles-empty.md | +| C10 | `EnumerateFileSystemInfos_ShouldWrapDirectoriesAndFiles` (DIW) | P1-T27, P1-T28 | DIWP 145 | `Enumerable.Empty()` 1, then 0 | `FullyQualifiedName~HelperClasses.DirectoryInfoWrapper_Tests.EnumerateFileSystemInfos_ShouldWrapDirectoriesAndFiles` | `to contain "UtilitiesCS.Test.dll"` (the first assertion, over `fileSystemInfos`) | mutation-wrapper-enumeratefilesysteminfos-empty.md | +| C11 | `ToString_ShouldDelegateToWrappedDirectoryInfo` (DIW) | P1-T29, P1-T30 | DIWP 228 | `return string.Empty;` 1, then 0 | `FullyQualifiedName~HelperClasses.DirectoryInfoWrapper_Tests.ToString_ShouldDelegateToWrappedDirectoryInfo` | `"C:\Repo\fixture"` (the only assertion, `result.Should().Be(directory.ToString())`, whose expected value is printed in every FluentAssertions message format) | mutation-wrapper-tostring-empty.md | + +The phrase `to contain "UtilitiesCS.Test.dll"` assumes the Debug AnyCPU build the plan runs, in which `OwnedAssemblyFile.Name` is `UtilitiesCS.Test.dll` (fact 8). `PROD_CSC_OUT_LINES:` is at least 1 for every production-file control (C1 to C11; every control in this plan mutates a production file) and `CSC_OUT_LINES:` at least 1 with `DLL_ADVANCED: True` for every control; a production-file control additionally requires the mutated production file to have been recompiled before the run, which `PROD_CSC_OUT_LINES:` proves. + +## AC3 Mutating-Call Inventory (expected) + +AC3 (amended 2026-09-30) admits a mutating call only against a Moq mock, a path under the test assembly's own output directory that is asserted not to exist before the call, or an existing entry of that output directory, the output directory itself, or its parent directory, on which the call is a no-op by construction. The inventoried member set is AC3's list plus `Refresh` (named by AC4's no-op clause): an assignment to `Attributes`, `IsReadOnly`, `CreationTime`, `CreationTimeUtc`, `LastAccessTime`, `LastAccessTimeUtc`, `LastWriteTime` or `LastWriteTimeUtc`, and a call to `SetAccessControl(`, `Create(`, `CreateSubdirectory(`, `CopyTo(`, `Replace(`, `MoveTo(`, `Delete(` or `Refresh(`. The target classes are exactly these five tokens: `mock`, `missing owned path`, `owned output directory`, `its parent`, `existing owned entry`. P2-T14 derives one `CALL: | | ` line per call site by reading the two files as formatted, and compares the result with this section. + +PFS (Target Source A), 34 call sites: + +| Test method | Call sites | Class (count) | +| --- | --- | --- | +| `PhysicalDirectoryInfoAdapter_AccessorsAndNoOpCreation_MirrorOwnedDirectory` | `adapter.Create()`, `adapter.Create(directory.GetAccessControl())`, `adapter.Refresh()`, `adapter.SetAccessControl(security)` | `owned output directory` (4) | +| `PhysicalDirectoryInfoAdapter_AccessorsAndNoOpCreation_MirrorOwnedDirectory` | `parentAdapter.CreateSubdirectory(directory.Name)`, `parentAdapter.CreateSubdirectory(directory.Name, parent.GetAccessControl())` | `its parent` (2) | +| `PhysicalDirectoryInfoAdapter_SettersOnMissingDirectory_ThrowWithoutCreatingEntries` | the six timestamp setters, the `Attributes` setter, `adapter.Refresh()` | `missing owned path` (8) | +| `PhysicalDirectoryInfoAdapter_MissingDirectoryAndUnsupportedInfo_BranchesBehaveAsExpected` | `adapter.Delete()`, `adapter.Delete(recursive: true)`, `adapter.MoveTo(directoryMoveTarget)` | `missing owned path` (3) | +| `PhysicalFileInfoAdapter_PropertiesStreamsAndAccessors_MirrorFileInfo` | `adapter.Refresh()`, `adapter.SetAccessControl(security)` | `existing owned entry` (2; the loaded assembly image) | +| `PhysicalFileInfoAdapter_SettersOnMissingFile_ThrowWithoutCreatingFiles` | the `IsReadOnly` setter, the `Attributes` setter, the six timestamp setters, `adapter.Refresh()` | `missing owned path` (9) | +| `PhysicalFileInfoAdapter_MissingFileBranches_ThrowOrNoOpWithoutCreatingFiles` | `adapter.Delete()`, `adapter.CopyTo(copyTarget)`, `adapter.CopyTo(copyTarget, overwrite: true)`, `adapter.MoveTo(moveTarget)`, `adapter.Replace(replaceTarget, backupTarget)`, `adapter.Replace(replaceTarget, backupTarget, ignoreMetadataErrors: true)` | `missing owned path` (6) | + +Expected PFS distribution: `owned output directory` 4, `its parent` 2, `existing owned entry` 2, `missing owned path` 26, `mock` 0; total 34. Every `missing owned path` site is preceded in its method by an `Exists.Should().BeFalse()` precondition on the adapter's own path; the file copy, move and replace destinations and the backup path are each preceded by a `File.Exists(...).Should().BeFalse()` precondition, and the directory move destination `directoryMoveTarget` by `Directory.Exists(directoryMoveTarget).Should().BeFalse()`, so every destination path of a mutating call is asserted absent before the call (AC3). The `Directory.Exists(...)` line does not contain the census token `Exists.Should().BeFalse()` (the character after `Exists` is an opening parenthesis, not a period), so that token stays 6 in PFS. `NON-INVENTORIED:` `AppendText()`, `OpenWrite()`, `Open(FileMode.Open)` and `Open(FileMode.Open, FileAccess.Read)` are routed through the adapter's internal delegate seam to test-owned sentinel streams and issue no file-system call; they are outside AC3's mutating-call list and are recorded under that label, not as inventory lines. + +DIW (Target Source B): every inventoried call site lies in one of the three Moq-based tests `PropertyDelegates_ShouldMirrorMockedIDirectoryInfo`, `EnumerationAndArrayMethods_ShouldDelegateToWrappedIDirectoryInfo` and `LifecycleAndAccessControlMethods_ShouldDelegateToWrappedIDirectoryInfo`, whose wrapper wraps `Mock.Object`, class `mock`. The lifecycle test alone carries seven (`wrapper.Create()`, `wrapper.Create(directorySecurity)`, `wrapper.Delete()`, `wrapper.Delete(recursive: true)`, `wrapper.MoveTo("moved")`, `wrapper.Refresh()`, `wrapper.SetAccessControl(directorySecurity)`, pre-edit lines 359 to 362 and 369 to 371, untouched by the edits), so `DIW-MUTATING-CALLS:` is at least 7, every DIW line's class is `mock`, and every DIW line's method is one of those three names; the constructor test and the four retargeted tests carry no inventoried call. + +## AC4 Member Forms (expected) + +AC4 (amended 2026-09-30) requires each previously exercised member to be asserted against a Moq mock, a test-owned read-only fixture, or a documented non-existent-path outcome, and a member whose effect on an existing test-owned entry is a no-op by construction (`Create`, `Refresh`, and `SetAccessControl` with an unmodified security object) to be exercised against that entry and complete without an exception. The forms are exactly these four tokens: `Moq`, `owned read-only fixture`, `non-existent-path outcome`, `no-op-no-throw`. P2-T15 writes one `FORM: | | |

` row per row below with an `EVIDENCE:` pointer (the test's `RESULT ... = Passed` line of test-run-final.md and the P2-T8 census token that proves the member is still called). + +| Type | Member group | Test method | Form | +| --- | --- | --- | --- | +| PDA | getters `Exists`, `Attributes`, `CreationTime`, `CreationTimeUtc`, `LastAccessTime`, `LastAccessTimeUtc`, `LastWriteTime`, `LastWriteTimeUtc`, `Extension`, `FullName`, `Name`, `Parent`, `Root`; `GetAccessControl()`, `GetAccessControl(sections)`; `GetObjectData` (asserted through `MemberCount`); `ToString` | `PhysicalDirectoryInfoAdapter_AccessorsAndNoOpCreation_MirrorOwnedDirectory` | `owned read-only fixture` | +| PDA | `Create()`, `Create(security)`, `CreateSubdirectory(name)`, `CreateSubdirectory(name, security)`, `Refresh()`, `SetAccessControl(security)` | `PhysicalDirectoryInfoAdapter_AccessorsAndNoOpCreation_MirrorOwnedDirectory` | `no-op-no-throw` | +| PDA | the 18 enumeration overloads (`EnumerateDirectories`, `EnumerateFiles`, `EnumerateFileSystemInfos`, `GetDirectories`, `GetFiles`, `GetFileSystemInfos`, three overloads each) | `PhysicalDirectoryInfoAdapter_Enumeration_WrapsOwnedDirectoryEntries` | `owned read-only fixture` | +| PDA | setters `Attributes`, `CreationTime`, `CreationTimeUtc`, `LastAccessTime`, `LastAccessTimeUtc`, `LastWriteTime`, `LastWriteTimeUtc` | `PhysicalDirectoryInfoAdapter_SettersOnMissingDirectory_ThrowWithoutCreatingEntries` | `non-existent-path outcome` | +| PDA | `Delete()`, `Delete(true)`, `MoveTo` | `PhysicalDirectoryInfoAdapter_MissingDirectoryAndUnsupportedInfo_BranchesBehaveAsExpected` | `non-existent-path outcome` | +| PDA | `WrapFileSystemInfo` unsupported branch (the test-owned `UnsupportedInfo`) | `PhysicalDirectoryInfoAdapter_MissingDirectoryAndUnsupportedInfo_BranchesBehaveAsExpected` | `owned read-only fixture` | +| PFA | getters `Exists`, `Extension`, `FullName`, `Name`, `Directory`, `DirectoryName`, `Length`, `IsReadOnly`, `Attributes`, the six timestamps; `GetAccessControl()`, `GetAccessControl(sections)`; `GetObjectData` (asserted through `MemberCount`); `ToString`; `Open(mode, access, share)`, `OpenRead()`, `OpenText()` | `PhysicalFileInfoAdapter_PropertiesStreamsAndAccessors_MirrorFileInfo` | `owned read-only fixture` | +| PFA | `AppendText()`, `Open(mode)`, `Open(mode, access)`, `OpenWrite()` through the internal delegate seam with test-owned sentinel streams | `PhysicalFileInfoAdapter_PropertiesStreamsAndAccessors_MirrorFileInfo` | `owned read-only fixture` | +| PFA | `Refresh()`, `SetAccessControl(security)` | `PhysicalFileInfoAdapter_PropertiesStreamsAndAccessors_MirrorFileInfo` | `no-op-no-throw` | +| PFA | setters `IsReadOnly`, `Attributes`, the six timestamps | `PhysicalFileInfoAdapter_SettersOnMissingFile_ThrowWithoutCreatingFiles` | `non-existent-path outcome` | +| PFA | `Delete()` (documented no-throw on a missing file), `CopyTo(target)`, `CopyTo(target, overwrite)`, `MoveTo`, `Replace(target, backup)`, `Replace(target, backup, ignoreMetadataErrors)` | `PhysicalFileInfoAdapter_MissingFileBranches_ThrowOrNoOpWithoutCreatingFiles` | `non-existent-path outcome` | +| DIWP | `Exists`, `FullName`, `Name`, `Extension`, `Parent`, `Root` (the rooted literal `C:\Repo\fixture`, a test-owned constant whose path computations are mirrored against the BCL object without disk access) | `Properties_ShouldMirrorWrappedDirectoryInfo` | `owned read-only fixture` | +| DIWP | `ToString` (the same rooted literal) | `ToString_ShouldDelegateToWrappedDirectoryInfo` | `owned read-only fixture` | +| DIWP | `GetFiles()`, `GetDirectories()`, `EnumerateFileSystemInfos()` (the owned directory and its parent) | `GetDirectoriesAndGetFiles_ShouldReturnWrappedEntries`, `EnumerateFileSystemInfos_ShouldWrapDirectoriesAndFiles` | `owned read-only fixture` | +| DIWP | every member the three Moq-based tests exercise (unchanged by this item) | `PropertyDelegates_ShouldMirrorMockedIDirectoryInfo`, `EnumerationAndArrayMethods_ShouldDelegateToWrappedIDirectoryInfo`, `LifecycleAndAccessControlMethods_ShouldDelegateToWrappedIDirectoryInfo` | `Moq` | + +Fifteen rows. Every member the two pre-edit files exercised (D-2) appears in exactly one row. + +## Revision Log + +- **Round 1 (2026-09-30).** Ten preflight defects applied after the orchestrator's fast-forward to 231e1c0b5 and the AC3 and AC4 amendments; detail in the Planner Self-Review Record paragraph "Revision round 1". +- **Round 2 (2026-09-30).** Eight preflight defects applied; two-dot worktree gates anchored to the recorded ANCHOR-SHA; detail in the paragraph "Revision round 2". +- **Round 3, entry A (2026-09-30): coordinator AC8 ruling of 2026-09-30 (option 1), applied at the P2-T7 stop.** Execution had checked P0-T1 through P2-T6; P2-T7 stopped with `AC8: NOT MET` because the UtilitiesCS `PACKAGE` rates and `REPO-LINE` read lower on MEASUREMENT 2 while all three `FILE` lines stayed `NOT-LOWER=True` (coverage-final.md). Changes: D-7 replaced by the per-file lines-and-branches no-regression rule for the three changed files plus the first-party floors, with the package-level and repository-level not-lower comparison and its second-measurement rule withdrawn; D-8 extended to branches and named as AC8 evidence; D-12 no longer names a re-measurement; the Binding Operator Constraints coverage bullet and the two bullets on anchors and coverage figures reworded; `CMD-FILE-COMPARE` extended to emit `LINES` and `BRANCHES` counts per file with `NOT-LOWER` requiring both covered counts not lower; `CMD-FILE-COMPARE-CONTROL` (in-memory negative control, one covered line and separately one covered branch lowered, nothing on disk modified), `CMD-UNTOUCHED-LINES` (covered-line counts of PropertyStore.cs, OlTableExtensions.Etl.cs and SubjectMapSco.Orchestration.cs per stage) and `CMD-FOREIGN-PROCESS-PROBE` (the pre-step that waits for a vstest.console, testhost or dotnet-coverage process rooted in another worktree to exit) added; `CMD-PACKAGE-COMPARE` relabelled observational (retained only for the variance table and the first-party re-derivation); `STAGE` admits `final3`; P2-T7 rewritten as steps (a) to (h) with ten acceptance items and no re-measurement loop; the Phase 2 preamble names `POST-MERGE REBUILD NOT CLEAN`; P2-T9 quotes the P2-T7 post-merge rebuild records and MEASUREMENT 3; P2-T15 evidence keeps the `FILE` lines (now lines and branches) and drops the `PACKAGE` lines; P2-T19 evidence names the per-file rule, the floors and the negative control; the AC4 and AC8 mapping lines reworded; the Long-running payloads and Stall handling conventions name P2-T7; the Write Set section records the planner's AC8 amendment. Outside the plan: FEATURE/issue.md line 71 (AC8) reworded to the ruling's clause and the dated note added directly below the AC list (line 73); no other criterion changed; the eight AC boxes stay unchecked. Task count unchanged at 64; no task added, removed, batched or unchecked; no task writes a .ps1, .psm1 or .psd1 file. Per the ruling, the post-merge re-run covers the two rebuilds and the coverage route only; the CSharpier check is not re-run on the merged tree (the merged files arrive from origin/main, whose format check passes in CI, and the P2-T2 record stands for the Write Set). +- **Round 3, entry B (2026-09-30): merge re-anchor.** After P2-T6 the orchestrator merged origin/main into the branch (merge commit 40e587ce20bbd41cd6915707271faae937f1a8e2, parents 97261cb4a1934af9887c1278adcd4b35f6d575bd and 66afa6372fd82fc1ffd7c81f85a1ad65eebc5817; the worktree reflog's last entry is that merge). Changes: the Base bullet carries the post-merge paragraph; Refs defines ANCHOR-SHA-2 (the merge commit's second parent, derived by P2-T7 with `git rev-parse ^2`, never pinned as an expectation of HEAD); the anchors bullet of the Binding Operator Constraints names it; P2-T7 step (b) records `MERGE-COMMIT-IN-HEAD-EXIT:`, `ANCHOR-SHA-2:`, `MERGE-BASE-NOW:`, `ORIGIN-MAIN-NOW:`, `MERGE-OUT-OF-SET-DELTA:` and gates `MERGE-UCS-DIFF-EXIT: 0` (the ANCHOR-SHA and ANCHOR-SHA-2 trees identical under UtilitiesCS/ and UtilitiesCS.Test/; the planning session had no shell, so the coordinator's statement that the merge touched no UtilitiesCS path is gated there rather than asserted here); the P2-T10 two-dot spans (`OUT-OF-SET-DIFF-EXIT:`, the three per-file diffs) and the `CMD-ADDED-LINES` span substitute ANCHOR-SHA-2, P2-T10 records `MERGE-BASE-NOW:` gated equal to `ANCHOR-SHA-2:` (`MERGE BASE MOVED`) and its footprint prose states why a merged sibling path cannot enter `THIS-ITEM-FOOTPRINT:`; P2-T11 notes the anchor of its three-dot span; P2-T20 records `MERGE-BASE-NOW:` gated the same way. Each re-anchored check stays able to fail: a main-side change under either source tree flips `MERGE-UCS-DIFF-EXIT:` to 1; a second merge or a rewound remote flips `MERGE-BASE-NOW:`; an out-of-set edit in the working tree flips `OUT-OF-SET-DIFF-EXIT:` against the merged tree exactly as it did against the pre-merge tree. The checked tasks P1-T31 and P2-T1 are left as executed against ANCHOR-SHA. + +## Planner Self-Review Record (initial authoring 2026-09-29; revision rounds 1, 2 and 3, 2026-09-30) + +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 with the Read, Grep and Glob tools; none is carried forward from the research record without a fresh read. The planning session had no shell, so no git command and no build was run here; every git-state, toolchain and run-time claim the plan relies on is re-observed by a Phase 0 task with a stop branch rather than asserted. Sibling regions re-checked: the whole of both Write Set files (388 and 393 lines), the whole of the three production files (217, 183 and 246 lines), lines 1 to 100 of FileInfoWrapper_Tests.cs, the runner script's parameter block (1 to 13), argument builder (41 to 95), derived-settings function (97 to 134), collection function (190 to 269) and main function tail (276 to 305, 340 to 461), the function declarations and parameter names of the five helper part files, and the three AC7 sites with their production callee. + +Findings that changed the plan relative to the research record: (1) the research counted PFS at 389 lines and DIW at 394; a line-start count gives 388 and 393, so the pre-edit `LINES` values are recorded and not gated, and every other pre-edit count in the Token Census Expectations table was re-derived from the file text (`.ToString()` 4 not 2, the three timestamp-Utc tokens 4 each because the self-assignment lines carry each token twice, `.Parent` 3 because the walk-up helper reads it, DIW `.GetFiles(` and siblings 7 because the Moq-based tests carry six of each); (2) the runner script has grown to 461 lines and its threshold assertion now lives in a Scope part file that skips the floors on a scoped run (facts 13 and 14), so the DIRECT route calls the two Threshold functions directly as #931 did, and the research's line citations into the runner (348 to 355, 400 to 402, 406 to 409) were confirmed; (3) FluentAssertions is 8.11.0 (fact 8), and every phrase the Control Table gates on was confirmed in committed evidence of this repository (fact 23) rather than taken from the research's message shapes; the D1 and D4 controls gate on the printed expected value instead of a format-dependent phrase; (4) the `IsReadOnly` setter on a missing file reads `Attributes` first, which on .NET Framework returns an all-bits value without throwing for a missing path, so the thrown type of that setter is the least certain prediction and P1-T7's pinning branch exists for it; (5) the checkpoint present in the worktree is a preparation-mode checkpoint (fact 20), so P0-T3 records its fields and the plan relies on the hook's own decision at the first `.cs` edit rather than predicting it; (6) the two `Contain` predicate assertions of the research's D3 shape were rewritten as `OfType` plus `Select` plus a value `Contain` so that the C2 and C10 phrases are the in-repo-confirmed `to contain` form. No acceptance-criterion text was amended by the planner. + +Revision round 1 (2026-09-30). The orchestrator fast-forwarded the branch from ddbab26a0 to 231e1c0b5 (the then-current origin/main) and amended AC3 and AC4; the round-1 preflight (atomic-executor, validation only) returned ten defects, all applied. Re-derived in this pass against the current tree with the Read, Grep and Glob tools: (1) the PFS pre-edit census values as `CMD-CENSUS` produces them over raw text, comment occurrences included: `AppDomain` 2 (352, 356), `Assembly.Location` 5 (351, 259, 265, 271, 355), `FileShare.None` 3 (207, 240, 246, all comments), `FileShare.ReadWrite` 5 (203, 217, 262, 268, 274), `Throw()` 5 (342 to 346), `Throw()` 3 (160 to 162), `.MemberCount` 0, `catch` 2 (43, 195), `File.Exists(` 1 (361), `GetRepositoryRoot` 6, `GetSolutionFile` 3; (2) .gitignore after the six-line upstream insertion: `[Tt]est[Rr]esult*/` 39 (which does not match the hyphenated coverage\test-results path; the earlier claim that line 39 ignores every test-results directory was wrong and is withdrawn, with no gate depending on it because every TRX lies under coverage\), artifacts/ 57, `*.coverage` and `*.coveragexml` 140 to 141, `*.trx` 146, `*cobertura*.xml` 147, `coverage/*` 150, `!coverage/.gitkeep` 151, `**/[Pp]ackages/*` 197, `.dotnet*/` 356; (3) the new hygiene guard: .github/workflows/_hygiene.yml (25 lines; the single run step at 24), scripts/hygiene/Test-RepositoryHygiene.ps1 (88 lines; governance prefix 35, classification 51 to 56, profile-path scan 67 to 70, `HYGIENE Findings=` 74, exit code 75 and 83 to 87), scripts/hygiene/Test-RepositoryHygiene.Rules.ps1 (124 lines; pattern 21, `Find-UserProfilePathMatch` 24 to 55, `Get-RawEvidenceDocumentKind` 57 to 123 with the extension switch 88 to 95 and the root-element switch 109 to 120) and scripts/hygiene/Test-RepositoryHygiene.Git.ps1 (162 lines; `git ls-files --eol -z` at 70; working-tree bytes read at 124); a case-insensitive grep of the whole feature folder, research record included, for the rule-B pattern returned no match; (4) FEATURE/issue.md (77 lines): `- Work Mode: minor-audit` 12, `## Acceptance Criteria` 62, AC1 to AC8 at 64 to 71 all unchecked, the AC3 line (66) ending `...or its parent directory, on which the call is a no-op by construction.` and the AC4 line (67) carrying the no-op clause naming `Create`, `Refresh` and `SetAccessControl`; (5) the `GetObjectData` delegations at PDA 190 to 191 and PFA 141 to 142 (grep; the wrappers FileSystemInfoWrapper.cs 73 to 76, DirectoryInfoWrapper.cs 236 to 239 and FileInfoWrapper.cs 201 to 204 delegate the same way), and DIW's `Properties_ShouldMirrorWrappedDirectoryInfo` members at 34 to 39, `ToString` test at 83 to 94 and Moq lifecycle test at 331 to 373 with its seven mutating calls at 359 to 362 and 369 to 371; (6) `Get-CoberturaFirstPartyCoverageReport` returns the rendered string (scripts/vscode/Invoke-MSTestWithCoverage.FirstParty.ps1: `OutputType` string at 149, the formatter's `return` at 161, the template at 120 beginning `First-party coverage:`); (7) the promoted record and artifacts/orchestration/orchestrator-state.json are present in the worktree (Glob); (8) the pre-implementation gate hook and its helpers carry no `merge`, `rebase` or `checkout` classification text (grep), so P0-T3 names a PreToolUse refusal of a merge span `PRE-IMPLEMENTATION GATE BLOCKED` instead of predicting the hook's decision. Deviations from the verbatim round-1 deltas, each with its reason: the AC4 `MemberCount` assertion carries a `because` clause (`"GetObjectData adds values"`) so its failure message is distinguishable from the `adapter.Length` assertion of the same method, which prints the same `to be greater than` phrase; the defect-2 rule is the docs-only merge form (fast-forward with no ahead commit, `git merge --no-edit origin/main` when every ahead path lies under docs/features/ or .claude/agent-memory/, stop otherwise) with a `CITED TREE ADVANCED` stop when the reconciliation touched any of the five census files; the defect-6 pattern is applied verbatim and a second count with the guard's exact rule-B pattern is added beside it; defect 10 is applied to P0-T11 branch (b), D-12 and D-15. Sibling regions re-checked in this pass: the whole Token Census Expectations table against Target Source A and B as revised, the Control Table's C5 phrase against the new `MemberCount` assertion, every `git` span of the plan for a backslash (none remains), every `.gitignore` citation (46, 76, fact 16, the enumeration and the `CITATION:` line), and every task whose acceptance count changed (P0-T2 six, P0-T3 eight, P0-T11 seven, P0-T13 five, P1-T5 four, P1-T8 five, shape R six, P2-T7 six, P2-T10 eleven, P2-T20 seven). + +Revision round 2 (2026-09-30). The round-2 preflight (atomic-executor, validation only) confirmed the ten round-1 fixes and returned eight local defects, all applied, with these re-derivations against the current tree: (1) `BASELINE-ADMISSIBLE` is the bare TRX `testName` value (D-12, fact 18): scripts/vscode/Invoke-MSTest.TrxSummary.ps1 was re-read in full (151 lines) and line 84 reads the `testName` attribute into `FailedTestName`; P0-T10, P0-T11, P2-T6 and D-15 compare bare names already (`NAMES-PRE` is a quoted method-name list and `CMD-VSTEST` matches `testName` against it), so no other site carried the class-qualified form. (2) Target Source A now asserts the directory move destination absent through `directoryMoveTarget` before `MoveTo`; the AC3 inventory row and its prose were updated; census recount over the revised block: `MissingOwnedPath(` 10 (the call moved from the `MoveTo` line to the new `var` line), `.MoveTo(` 2, `Exists.Should().BeFalse()` 6 (the new `Directory.Exists(...)` line contains no census token: `Exists` is followed by a parenthesis, and neither `File.Exists(` nor `Directory.CreateDirectory` matches `Directory.Exists(`); Target Source A is 410 lines as written (408 plus 2) and the `LINES` estimate is 424. Every other destination of a mutating call in Target Source A (copy, move, replace and backup paths) already carried a `File.Exists(...).Should().BeFalse()` precondition, and the DIW `MoveTo("moved")` acts on a Moq mock. (3) ANCHOR-SHA: the Refs convention defines it; P0-T3 writes it (gated equal to `ORIGIN-MAIN-SHA:`, within the existing eight-item acceptance); `CMD-ADDED-LINES`, P1-T31, P2-T1 and the four P2-T10 spans carry the bare token; P1-T31 and P2-T10 record `ORIGIN-MAIN-NOW:` ungated; the Base bullet and constraint lines 26 and 28 are reworded; a grep for the two-dot-with-pathspec form (the ref name followed by a space and the pathspec separator) over the plan matches only this sentence and the corresponding self-review entry, both prose, and no command span; every three-dot span is unchanged, ANCHOR-SHA is a non-flag operand (G8) and carries no placeholder character, and the `Command:` convention substitutes it with a 40-character SHA that is not a host identifier. (4) P2-T10 records `COMMITTED-CLAUDE-OR-CONFIG-RAW:` verbatim and `COMMITTED-CLAUDE-OR-CONFIG:` after the Clause A and Clause B subtraction; the acceptance stays `NONE`. (5) `THROW-SUM:` is defined as the census-token sum in P1-T5, the table row and D-9, with the `Throw()` occurrence excluded; P1-T7 branch (b) and the two `Throw<...>` table rows carry the cross-pin rule (no literal appended when the observed type is already a token; that count rises by one and the predicted type's count falls by one). (6) P1-T7 branch (d) reads the text after the first `" :: "`, the separator `CMD-VSTEST` prints between `testName` and the message. (7) The Control Table note names C1 to C11 as production-file controls; Target Source C re-read: every control mutates PDA, PFA or DIWP and none mutates a test file, and its heading now says so. (8) P2-T20's `HYGIENE-ITEM-FINDINGS:` admits `HYGIENE unreadable` lines; fact 16 cites main script line 59 (scripts/hygiene/Test-RepositoryHygiene.ps1 re-read in full, 88 lines: unreadable 59, Findings 74, exit 75, entry guard 83 to 87). Advisory: P0-T2 records a pre-existing FEATURE/evidence/ tree under `PRE-EXISTING-EVIDENCE:` and treats it as evidence content, so the orchestrator's preflight-clearance record under FEATURE/evidence/other/ is not `UNEXPECTED FEATURE DOCUMENT`; P0-T13 commits it with the feature tree when it is uncommitted (its path begins the FEATURE/ prefix the acceptance requires); P2-T10 classifies it as Clause A or as FEATURE/ footprint, both admitted; P2-T11 sweeps it with every other feature-folder file, and the orchestrator writes no absolute path into it. The feature folder at this pass holds issue.md, this plan and research/ only (Glob), and PFS is still 388 lines (count-mode grep). Sibling regions re-checked in this pass: Target Source A in full as revised, the whole Token Census Expectations table, the AC3 inventory section, the Control Table with Target Source C, every `git diff` span of the plan, D-9, D-10, D-12, D-15, facts 16 and 18, P0-T2, P0-T3, P0-T13, P1-T5, P1-T7, P1-T31, P2-T1, P2-T10, P2-T11 and P2-T20. + +Revision round 3 (2026-09-30; coordinator AC8 ruling, option 1, and the post-merge re-anchor; Revision Log entries A and B). This session again had no shell, so every git fact came from the worktree's own git files and every coverage fact from the documents and artifacts on disk, read with the Read and Grep tools in the assigned worktree. Re-derived in this pass: (1) FEATURE/evidence/qa-gates/coverage-final.md read in full (216 lines before this revision): MEASUREMENT 1 `PACKAGE UtilitiesCS BRANCH ... NOT-LOWER=False` (9411 against 9413 of 11271) at line 100 with LINE and REPO-LINE True; MEASUREMENT 2 `PACKAGE` LINE 38813 against 38815, BRANCH 9410 against 9413 and `REPO-LINE` 56082 against 56084 of 65736 all False at 186 to 188; all six `FILE` lines True (81 to 91 of 91, 69 to 71 of 75, 123 of 123) at 102 to 104 and 189 to 191; the diagnostic observation at 206 to 210 naming PropertyStore.cs, OlTableExtensions.Etl.cs and SubjectMapSco.Orchestration.cs as the loss sites; `### Outcome` with `STOP: AC8: NOT MET (P2-T7)` at 212 to 215; both first-party lines and both floors `MET`. (2) coverage\final-940.cobertura.xml (git-ignored, on disk, read by explicit path): `condition-coverage=` occurs 13612 times, the same count as in coverage\baseline-940.cobertura.xml; the class element for PhysicalDirectoryInfoAdapter.cs at line 143395 carries `filename="UtilitiesCS\HelperClasses\FileSystem\PhysicalDirectoryInfoAdapter.cs"` (backslash separators) and its `.ctor` method's line 13 reads `branch="True" condition-coverage="50% (1/2)"`; DirectoryInfoWrapper.cs at 142481 with line 16 `(2/2)`; PhysicalFileInfoAdapter.cs at 144014 with line 27 `(1/2)`; the three untouched files resolve to exactly one `filename` value each (`UtilitiesCS\Interfaces\IWinForm\PropertyStore.cs` 45706, `UtilitiesCS\EmailIntelligence\SubjectMap\SubjectMapSco.Orchestration.cs` 84687, `UtilitiesCS\OutlookObjects\Table\OlTableExtensions.Etl.cs` 102997); the attribute shape and the presence of a covered branch in each of the three files are what the extended `CMD-FILE-COMPARE` and its control rely on, and the block was authored against that observed shape rather than the Cobertura documentation. (3) The worktree's git files: HEAD names refs/heads/bug/filesystem-wrapper-tests-open-repository-solution-file-940; the reflog's last entry is `97261cb4a... 40e587ce2... merge origin/main: Merge made by the 'ort' strategy.`, preceded by the P2 evidence commit, the P1-T8 fix commit, the Phase 0 commit, the preparation commit and the fast-forward to 231e1c0b5; the coordinator's parent SHAs (97261cb4a1934af9887c1278adcd4b35f6d575bd, 66afa6372fd82fc1ffd7c81f85a1ad65eebc5817) are consistent with that entry and the P0-T3 artifact's `ANCHOR-SHA: 231e1c0b55105aeb626bf5a6e8d0266a567cacad`; no git command could be run, so the statement that the merge touched no UtilitiesCS or UtilitiesCS.Test path is gated by P2-T7's `MERGE-UCS-DIFF-EXIT: 0` instead of asserted. (4) FEATURE/evidence/baseline/p0-t3-worktree-context.2026-09-30T07-13.md (61 lines): `RECONCILE: NOT NEEDED`, `ANCHOR-SHA:` at 30, `INHERITED-CLAUSE-A:` 34 to 42 (the five prep-commit paths plus two Phase 0 evidence files), `SOURCE-PORCELAIN: EMPTY`. FEATURE/evidence/baseline/p0-t4-channel-and-toolchain.2026-09-30T07-14.md (33 lines): the three `PRE-EDIT-HASH-` production anchors at 19 to 21. FEATURE/evidence/qa-gates/p2-t3-msbuild-analyzers.2026-09-30T08-09.md (18 lines): the P2-T3 field set as the executor wrote it (`ITERATION: 1`, `MSBUILD_EXIT_CODE:`, `SKIP_CORECOMPILE_LINES: 0`, `UCS_TEST_CSC_OUT_LINES: 2`, `UCS_CSC_OUT_LINES: 2`, `WARNINGS-DELTA:`), the shape the P2-T7 sub-records copy. The evidence tree holds 40 artifacts (grep for `^Timestamp:`): 20 under regression-testing/ (test-run-final.md, the eleven mutation records, the P1-T3 to P1-T8 and P1-T31 records, the fail-before dossier), 13 under baseline/, 5 under qa-gates/ (the P2-T1 to P2-T4 records and coverage-final.md), 2 under other/. (5) FEATURE/issue.md before and after the amendment: AC8 at line 71 ended `with repository line coverage at or above the baseline figure.`; it now ends `first-party line coverage at least 80 percent and branch coverage at least 75 percent.`, the dated note is the paragraph at line 73 between the AC list and `## Next Step` (now line 75), the regex `^- \[[ x]\] AC[1-8]: ` still matches exactly eight lines, all unchecked, and the AC3 and AC4 literals P0-T2 gated are unchanged. (6) This plan: every `ANCHOR-SHA` span was enumerated (`-o` grep); the unexecuted ones (P2-T10, `CMD-ADDED-LINES`) now carry ANCHOR-SHA-2 and the executed ones (P1-T31, P2-T1, P0-T3) are untouched; every `final2` and second-measurement reference was enumerated and each remaining one is either a `STAGE` enumeration, the executed P2-T6 text, a MEASUREMENT 2 record reference, or the withdrawal text of D-7; every three-dot span (P0-T3, P2-T10, P2-T11, P2-T20) was re-read against the merge-base argument of the Base bullet. Sibling regions re-checked in this pass: the whole Command Reference (the `$read` and `$compare` statements are byte-identical in the three blocks that carry them; every payload line is one statement, no single quote, no comment line, no double-quoted literal ending in a backslash; the regex `\((\d+)/(\d+)\)` uses single backslashes only, the form `CMD-REBUILD`'s `(\d+) Warning\(s\)` already passed through the command channel in P2-T3), the Decisions section, the Phase 2 preamble and P2-T7 through P2-T20, the AC-MAPPING block, and the P0-T2 acceptance text against the amended issue.md. Deviations from the verbatim ruling text, each with its reason: the post-merge rebuilds are P2-T7 sub-records (`p2-t7-msbuild-analyzers`, `p2-t7-msbuild-nullable`, `ITERATION: 1`, `POST-MERGE: YES`) rather than `ITERATION 2` artifacts of P2-T3 and P2-T4, because P2-T3 and P2-T4 are checked and `ITERATION` counts loop restarts from P2-T1, which did not occur; `MERGE-BASE-NOW:` is gated equal to `ANCHOR-SHA-2:` in P2-T7, P2-T10 and P2-T20 (the ruling asked for a re-anchor that stays able to fail; a second merge or a rewound remote is the failure this detects); the merge commit SHA appears in the plan as a git operand from which ANCHOR-SHA-2 is derived, not as an expectation of HEAD. + +SELF-REVIEW: RE-DERIVED THIS PASS +- UtilitiesCS.Test/HelperClasses/PhysicalFileSystemAdapters_Tests.cs | lines 1 to 388 read in full; `[TestMethod]` at 15, 137, 169, 313; `GetRepositoryRoot` at 19, 142, 155, 317, 349, 375; `GetSolutionFile` at 173, 318, 373; `catch (IOException)` at 43 and 195; `AppDomain` at 352 (comment) and 356; `Assembly.Location` at 351 (comment), 259, 265, 271, 355; `FileShare.None` in comments at 207, 240 and 246; `FileShare.ReadWrite` at 203 (comment), 217, 262, 268, 274; `Throw()` at 160 to 162; `Throw()` at 342 to 346; `MemberCount` absent; `new DirectoryInfo(` at 145 and 357; `new FileInfo(` at 320 and 376; the stale comment 182 to 185; `UnsupportedInfo` 379 to 386; no `using System.Linq;`, no `using Moq;` (re-derived 2026-09-30; 388 lines re-confirmed by count-mode grep in round 2). +- UtilitiesCS.Test/HelperClasses/DirectoryInfoWrapper_Tests.cs | lines 1 to 393 read in full; `[TestMethod]` at 16, 26, 42, 63, 82, 96, 186, 331; `GetRepositoryRoot` at 30, 46, 67, 86, 375; `TaskMaster.sln` at 60, 79, 381; `AppDomain` at 377; `new DirectoryInfo(` at 377; `C:\Repo` at 125 and 153; `using System.Linq;` at 3 and `using Moq;` at 8; class close at 392; `Properties_ShouldMirrorWrappedDirectoryInfo` members at 34 to 39, `ToString` test 83 to 94, Moq lifecycle test 331 to 373 with `wrapper.Create()`, `Create(directorySecurity)`, `Delete()`, `Delete(recursive: true)` at 359 to 362 and `MoveTo("moved")`, `Refresh()`, `SetAccessControl(directorySecurity)` at 369 to 371 (re-derived 2026-09-30). +- UtilitiesCS/HelperClasses/FileSystem/PhysicalDirectoryInfoAdapter.cs | lines 1 to 217 read in full; `CreationTime` setter 25; `Exists` 34; `FullName` 38; `Name` 64; `Delete()` 87; `GetFiles()` 160 to 161; `GetObjectData` 190 to 191 (delegates to `_directoryInfo.GetObjectData`, grep 2026-09-30); `WrapFileSystemInfo` 202 to 215; `using System;` 2 and `using System.Linq;` 5; `#nullable enable` 1. +- UtilitiesCS/HelperClasses/FileSystem/PhysicalFileInfoAdapter.cs | lines 1 to 183 read in full; constructors 25 to 48; `IsReadOnly` setter 113; `Length` 116; `GetObjectData` 141 to 142 (delegates to `_fileInfo.GetObjectData`, grep 2026-09-30); `MoveTo` 144; `Open` overloads 146 to 152; `OpenRead` 154; `OpenText` 156; `Replace` 162 to 176. +- UtilitiesCS/HelperClasses/FileSystem/DirectoryInfoWrapper.cs | lines 1 to 246 read in full; constructors 15 to 26; `EnumerateFileSystemInfos()` 143 to 146; `GetFiles()` 186 to 189; `ToString()` 226 to 229; `using System;` 2 and `using System.Linq;` 5. +- UtilitiesCS/Properties/AssemblyInfo.cs | `InternalsVisibleTo` 18 to 20 (grep). UtilitiesCS/Interfaces/IHelperClasses/IFileInfo.cs | `FileStream OpenRead();` 26 (grep). UtilitiesCS/HelperClasses/FileSystem/FileInfoWrapper.cs | internal seam constructor 21 to 24 (grep). +- UtilitiesCS/UtilitiesCS.csproj | `OutputType` 12, `AssemblyName` 15, the word TreatWarningsAsErrors only in the comment at 1315 (grep). +- UtilitiesCS.Test/UtilitiesCS.Test.csproj | Platform 11; OutputPath 51, 59, 66, 70; Compile Include 232, 233, 234; ProjectReference 963 (grep). UtilitiesCS.Test/packages.config | FluentAssertions 8.11.0 at 9, Moq 4.21.0 at 65, MSTest 4.4.1 at 67 to 68 (grep). +- UtilitiesCS.Test/HelperClasses/FileInfoWrapper_Tests.cs | lines 1 to 100 read; `FixturePath` 18; the four #931 tests 31 to 99; 357 lines by line-start count. +- TaskMaster.Test/Ribbon/RibbonControllerTests.cs | lines 290 to 314 and 418 to 442 read; `File.ReadAllText` 299; `FindRepositoryRoot` 301 and 422 to 437; `TaskMaster.sln` 429. +- TaskMaster.Test/Bootstrap/FSharpCoreHintPathAlignmentTests.cs | lines 10 to 107 read; remarks 11 to 21; `SolutionFileName` 27; `FindRepositoryRoot` 47 to 67; `DiscoverFSharpCoreHintPaths` 83 to 107; consumers 88, 156, 181 (grep). +- UtilitiesCS.Test/EmailIntelligence/SortEmail_Tests.cs | lines 186 to 296 and 388 to 417 read; `GetRepositoryRoot()` calls 196, 223, 241, 255, 280; helper 393 to 415; `TrySaveAttachmentAsync` test 237 to 249. +- UtilitiesCS/EmailIntelligence/EmailParsingSorting/SortEmail.cs | `ExcludeFromCodeCoverage` 888; `TrySaveAttachmentAsync` 889; `Directory.CreateDirectory` 896 (grep; the research cited the file without its EmailParsingSorting directory). +- TaskMaster.runsettings | 30 lines read; Workers 5; Scope 6; collector 9 to 29. scripts/vscode/TaskMaster.cli.runsettings | 9 lines read. +- scripts/vscode/Invoke-MSTestWithCoverage.ps1 | parameters 1 to 13; argument builder 41 to 95 with the filter at 91 and results-directory and logger at 92 to 93; `ConvertTo-DerivedCoverageSettingsXml` 97 to 134; `Invoke-DotnetCoverageCollection` 190 to 269 with the throw at 261 to 263; strict mode 271 to 272; main function 274 with defaults 293 to 301; discovery 348 to 355; `Discovered` line 374; post-process 399 to 402; threshold call 406 to 409; first-party line 410; projection 415 to 423; summary 439 to 447; guard 459 to 461. +- scripts/vscode/Invoke-MSTestWithCoverage.Helpers.ps1 | dot-sources 2 to 6; `Get-CoberturaClassLineSummary` 160; `ConvertTo-KoverageCoberturaXml` 407 with parameters 412 and 415 (grep). scripts/vscode/Invoke-MSTestWithCoverage.Threshold.ps1 | functions 3 and 58, parameters 28 and 88, messages 54 and 124. scripts/vscode/Invoke-MSTestWithCoverage.FirstParty.ps1 | function 123, parameter 152. scripts/vscode/Invoke-MSTestWithCoverage.Projection.ps1 | functions 14 and 83, parameters 50, 113, 116. scripts/vscode/Invoke-MSTestWithCoverage.Scope.ps1 | `Assert-CoberturaCoverageThresholdForRun` 59. scripts/vscode/Invoke-MSTest.TrxSummary.ps1 | read in full (151 lines, round 2); functions 12 and 103, parameter 44; failed-name loop 74 to 85 with `GetAttribute('testName')` at 84; `FailedTestName` 99. +- scripts/vscode/Invoke-Restore.ps1 | parameters 1 to 9; vswhere 91 to 98; restore switches 107 to 110 (grep). scripts/vscode/Install-RepoDotNetSdk.ps1 | version 3; install directory 36 (grep). global.json | version 3; paths 7 (grep). dotnet-tools.json | csharpier 1.2.6 (via the #931 record and the CLAUDE.md pin). +- .csharpierignore | 18 lines read. .gitignore | 39 (`[Tt]est[Rr]esult*/`), 57, 140 to 141, 146 to 147 (`*.trx`, `*cobertura*.xml`, inserted upstream), 150 to 151, 197, 356 (read 2026-09-30 at 36 to 41, 135 to 159, 192 to 199, 350 to 359). .github/workflows/_hygiene.yml | 25 lines read; run step 24. scripts/hygiene/Test-RepositoryHygiene.ps1 | 88 lines read (re-read in full, round 2); governance prefix 35; classification 51 to 56; `HYGIENE unreadable` 59; profile-path scan 67 to 70; Findings line 74; exit 75, 83 to 87. scripts/hygiene/Test-RepositoryHygiene.Rules.ps1 | 124 lines read; pattern 21; matcher 24 to 55; classifier 57 to 123. scripts/hygiene/Test-RepositoryHygiene.Git.ps1 | 162 lines read; `ls-files --eol -z` 70; default content delegate 124. scripts/vscode/Invoke-MSTestWithCoverage.FirstParty.ps1 | `OutputType` string 149, `return` 161, template 120 (grep 2026-09-30). .claude/hooks/enforce-orchestration-preimplementation-gate.ps1 and its helpers | no `merge`, `rebase` or `checkout` text (grep 2026-09-30). .gitattributes | `* text=auto` 4 (grep). .editorconfig | `end_of_line = crlf` 669 (grep). coverage.config, coverage/.gitkeep and artifacts/orchestration/orchestrator-state.json | present (Glob); the checkpoint read in full (56 lines). +- .claude/hooks/validate-planner-output.ps1 | read in full; path regex 95; phase regex 238; task regex 239; sequential check 299 to 302; Phase 0 checks 325 to 330; final-phase check 339; review validation 113 to 228. +- .claude/hooks/enforce-orchestration-preimplementation-gate.ps1 | checkpoint path 31; readiness properties 235 to 243 and 251; deny reason 429 (grep); helpers exempt trees 22 to 28 (grep). +- .claude/rules/plan-acceptance-gates.md | read in full (G1 to G9, register, attribution window). +- docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/issue.md | read in full (77 lines before, 79 after the round-3 amendment, 2026-09-30); `- Work Mode: minor-audit` 12; `## Acceptance Criteria` 62; AC1 to AC8 at 64 to 71, all unchecked; AC3 (66) amended with `its parent directory, on which the call is a no-op by construction`; AC4 (67) amended with the `no-op by construction` clause naming `Create`, `Refresh`, and `SetAccessControl`; AC8 (71) amended in round 3 to the per-file clause (`not lower than the baseline in each of the three changed wrapper and adapter files`) with the dated note at 73 and `## Next Step` at 75. +- docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/qa-gates/coverage-final.md | read in full (216 lines, round 3); MEASUREMENT 1 comparison 99 to 108; MEASUREMENT 2 section 110 to 200; `FILE` lines 102 to 104 and 189 to 191 all `NOT-LOWER=True`; diagnostic observation 202 to 210; `### Outcome` 212 to 215. +- docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/baseline/p0-t3-worktree-context.2026-09-30T07-13.md | 61 lines; `ANCHOR-SHA:` 30; `INHERITED-CLAUSE-A:` 34 to 42; `SOURCE-PORCELAIN: EMPTY` 45. evidence/baseline/p0-t4-channel-and-toolchain.2026-09-30T07-14.md | `PRE-EDIT-HASH-PDA:`, `-PFA:`, `-DIWP:` 19 to 21. evidence/qa-gates/p2-t3-msbuild-analyzers.2026-09-30T08-09.md | 18 lines; the P2-T3 field set 3 to 17. +- coverage/final-940.cobertura.xml and coverage/baseline-940.cobertura.xml (git-ignored, on disk in the assigned worktree, read by explicit path) | `condition-coverage=` 13612 occurrences in each; class elements with backslash-separated `filename` values at 143395 (PhysicalDirectoryInfoAdapter.cs, ctor line 13 `(1/2)`), 142481 (DirectoryInfoWrapper.cs, ctor line 16 `(2/2)`) and 144014 (PhysicalFileInfoAdapter.cs, ctor line 27 `(1/2)`) of the final document; the three untouched files at 45706, 84687 and 102997, one `filename` each. +- The worktree's git files under the repository's .git/worktrees directory | HEAD names refs/heads/bug/filesystem-wrapper-tests-open-repository-solution-file-940; logs/HEAD (10 entries) ends with the `merge origin/main: Merge made by the 'ort' strategy.` entry from 97261cb4a to 40e587ce2 (round 3). +- docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/ (whole folder, research record included) | case-insensitive grep for the hygiene rule-B pattern: no match (2026-09-30). +- docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/research/2026-09-29T21-10-filesystem-wrapper-tests-open-repository-solution-file-research.md | read in full (218 lines); sections 1 to 9 consumed. +- docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/plan.2026-09-28T20-01.md | header, conventions, command reference, target source E, controls and Phases 0 to 4 read; docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/regression-testing/mutation-openread-sentinel.md, evidence/baseline/coverage-baseline.md, evidence/baseline/p0-t4-channel-and-toolchain.2026-09-29T08-56.md, evidence/baseline/p0-t9-stall-probe.2026-09-29T09-00.md, evidence/regression-testing/fail-before-exception.2026-09-29T09-07.md, evidence/qa-gates/p4-t12-hygiene-sweep.2026-09-29T09-45.md and evidence/qa-gates/post-merge-coverage-final.md read in full. +- docs/features/potential/promoted/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file.md | present in the worktree (Glob). +- The worktree's git reference file under the repository's .git/worktrees directory | HEAD names refs/heads/bug/filesystem-wrapper-tests-open-repository-solution-file-940. +- This plan | every task line carries a path token on its opening line; phase headings use the em dash; task ids are sequential per phase (13, 31, 20; no task added or removed in rounds 1, 2 or 3; round 3 rewrote the text of P2-T7 and touched P2-T9, P2-T10, P2-T11, P2-T15, P2-T19 and P2-T20 without unchecking any box); Phase 0 carries the policy-read and baseline vocabulary; the final phase title and tasks carry the QA vocabulary the hook requires; no fenced block carries a column-zero `#` line; no `git` span carries a backslash (grep 2026-09-30); no two-dot `git diff` span names `origin/main` (grep for the ref name followed by a space and the pathspec separator after round 2: matches only the round-2 self-review sentence and this entry, no command span); the file is written with LF line endings (grep for a carriage return: none). +- docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/ (folder listing, round 2) | issue.md, plan.2026-09-29T23-02.md and research/ only (Glob); no evidence/ tree exists yet, so the orchestrator's preflight-clearance record is a file P0-T2 will meet under FEATURE/evidence/other/ and record as `PRE-EXISTING-EVIDENCE:`. + +PLANNER-INTERNAL-REVIEW: PASS +CITATION-TO-TREE: PASS +AC-TRACEABILITY: PASS +SCOPE-BOUNDARY: PASS +CITATION: UtilitiesCS.Test/HelperClasses/PhysicalFileSystemAdapters_Tests.cs | 388 lines; tests 15-135, 137-167, 169-311, 313-347; helpers 349-377; `catch (IOException)` 43 and 195; `TaskMaster.sln` 110-129, 206, 211, 237, 361, 376; `AppDomain` 352, 356; `Assembly.Location` 351, 259, 265, 271, 355; `FileShare.None` 207, 240, 246; `FileShare.ReadWrite` 203, 217, 262, 268, 274; `Throw()` 160-162; `Throw()` 342-346 +CITATION: UtilitiesCS.Test/HelperClasses/DirectoryInfoWrapper_Tests.cs | 393 lines; affected tests 27-40, 43-61, 64-80, 83-94; helper 375-391; `TaskMaster.sln` 60, 79, 381; Moq lifecycle test 331-373 with mutating calls 359-362, 369-371 +CITATION: UtilitiesCS/HelperClasses/FileSystem/PhysicalDirectoryInfoAdapter.cs | 217 lines; setter 25; `Exists` 34; `Name` 64; `Delete()` 87; `GetFiles()` 160-161; `GetObjectData` 190-191 +CITATION: UtilitiesCS/HelperClasses/FileSystem/PhysicalFileInfoAdapter.cs | 183 lines; seam constructor 34-48; `IsReadOnly` setter 113; `Length` 116; `GetObjectData` 141-142; `MoveTo` 144; `OpenRead` 154 +CITATION: UtilitiesCS/HelperClasses/FileSystem/DirectoryInfoWrapper.cs | 246 lines; `EnumerateFileSystemInfos()` 145; `GetFiles()` 188; `ToString()` 228 +CITATION: UtilitiesCS/HelperClasses/FileSystem/FileInfoWrapper.cs | internal seam constructor 21-24 +CITATION: UtilitiesCS/Interfaces/IHelperClasses/IFileInfo.cs | `FileStream OpenRead();` 26 +CITATION: UtilitiesCS/Properties/AssemblyInfo.cs | InternalsVisibleTo 18-20 +CITATION: UtilitiesCS/UtilitiesCS.csproj | OutputType 12; AssemblyName 15; no TreatWarningsAsErrors property (comment 1315 only) +CITATION: UtilitiesCS.Test/UtilitiesCS.Test.csproj | Platform 11; OutputPath 51; Compile Include 232-233; ProjectReference 963 +CITATION: UtilitiesCS.Test/packages.config | FluentAssertions 8.11.0 at 9; Moq 4.21.0 at 65; MSTest 4.4.1 at 67-68 +CITATION: UtilitiesCS.Test/HelperClasses/FileInfoWrapper_Tests.cs | `FixturePath` 15-18; sentinel 68-73; 357 lines +CITATION: TaskMaster.Test/Ribbon/RibbonControllerTests.cs | test 297-311; `FindRepositoryRoot` 422-437 +CITATION: TaskMaster.Test/Bootstrap/FSharpCoreHintPathAlignmentTests.cs | remarks 11-21; `FindRepositoryRoot` 47-67; `DiscoverFSharpCoreHintPaths` 83-107; consumers 88, 156, 181 +CITATION: UtilitiesCS.Test/EmailIntelligence/SortEmail_Tests.cs | consumers 196, 223, 241, 255, 280; `TrySaveAttachmentAsync` test 237-249; helper 393-415 +CITATION: UtilitiesCS/EmailIntelligence/EmailParsingSorting/SortEmail.cs | `ExcludeFromCodeCoverage` 888; `TrySaveAttachmentAsync` 889; `Directory.CreateDirectory` 896 +CITATION: TaskMaster.runsettings | Workers 5; Scope 6; collector 9-29 +CITATION: scripts/vscode/TaskMaster.cli.runsettings | 9 lines; Workers 5; Scope 6 +CITATION: scripts/vscode/Invoke-MSTestWithCoverage.ps1 | parameters 1-13; filter 91; derived settings 97-134; collection 190-269; defaults 297-298; discovery 348-355; post-process 399-402; threshold 406-409; first-party 410; projection 415-423; summary 439-447; guard 459-461 +CITATION: scripts/vscode/Invoke-MSTestWithCoverage.Helpers.ps1 | dot-sources 2-6; `ConvertTo-KoverageCoberturaXml` 407 (parameters 412, 415) +CITATION: scripts/vscode/Invoke-MSTestWithCoverage.Threshold.ps1 | functions 3 and 58; messages 54 and 124 +CITATION: scripts/vscode/Invoke-MSTestWithCoverage.FirstParty.ps1 | `Get-CoberturaFirstPartyCoverageReport` 123; `OutputType` string 149; `return` 161; template 120 +CITATION: scripts/vscode/Invoke-MSTestWithCoverage.Projection.ps1 | functions 14 and 83; parameters 50, 113, 116 +CITATION: scripts/vscode/Invoke-MSTestWithCoverage.Scope.ps1 | `Assert-CoberturaCoverageThresholdForRun` 59 +CITATION: scripts/vscode/Invoke-MSTest.TrxSummary.ps1 | functions 12 and 103; parameter 44; `testName` read at 84; `FailedTestName` 99 +CITATION: scripts/vscode/Invoke-Restore.ps1 | parameters 1-9; vswhere 91-98; restore switches 107-110 +CITATION: scripts/vscode/Install-RepoDotNetSdk.ps1 | version 3; install directory 36 +CITATION: global.json | version 3; paths 7 +CITATION: coverage.config | present at the repository root +CITATION: .csharpierignore | 18 lines +CITATION: .gitignore | 39, 57, 140-141, 146-147, 150-151, 197, 356 +CITATION: .github/workflows/_hygiene.yml | run step 24 +CITATION: scripts/hygiene/Test-RepositoryHygiene.ps1 | governance prefix 35; classification 51-56; unreadable 59; profile-path scan 67-70; Findings line 74; exit 75, 83-87 +CITATION: scripts/hygiene/Test-RepositoryHygiene.Rules.ps1 | pattern 21; `Find-UserProfilePathMatch` 24-55; `Get-RawEvidenceDocumentKind` 57-123 +CITATION: scripts/hygiene/Test-RepositoryHygiene.Git.ps1 | `git ls-files --eol -z` 70; content delegate 124 +CITATION: .gitattributes | `* text=auto` 4 +CITATION: .editorconfig | `end_of_line = crlf` 669 +CITATION: artifacts/orchestration/orchestrator-state.json | 56 lines; issue-num 12; work-mode 13; feature-folder 14; plan-path 15; lifecycle_ready 17; route_id 3; preparation_mode 5 +CITATION: .claude/hooks/validate-planner-output.ps1 | 95, 113-228, 238-239, 299-302, 325-330, 339 +CITATION: .claude/hooks/enforce-orchestration-preimplementation-gate.ps1 | 31, 235-243, 251, 429 +CITATION: .claude/hooks/enforce-orchestration-preimplementation-gate-helpers.ps1 | exempt trees 22-28 +CITATION: .claude/rules/plan-acceptance-gates.md | G1-G9 rule table 31-42; attribution window 52-54 +CITATION: docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/issue.md | Work Mode 12; `## Acceptance Criteria` 62; AC1-AC8 64-71; AC3 amended 66 (`its parent directory`); AC4 amended 67 (`no-op by construction`); AC8 amended 71 (`not lower than the baseline in each of the three changed`); dated note 73; `## Next Step` 75 +CITATION: docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/qa-gates/coverage-final.md | MEASUREMENT 1 comparison 99-108; MEASUREMENT 2 comparison 186-195; FILE lines 102-104 and 189-191; diagnostic observation 206-210; Outcome 212-215 +CITATION: docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/baseline/p0-t3-worktree-context.2026-09-30T07-13.md | ANCHOR-SHA 30; INHERITED-CLAUSE-A 34-42; SOURCE-PORCELAIN 45 +CITATION: docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/baseline/p0-t4-channel-and-toolchain.2026-09-30T07-14.md | PRE-EDIT-HASH-PDA, -PFA, -DIWP 19-21 +CITATION: docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence/qa-gates/p2-t3-msbuild-analyzers.2026-09-30T08-09.md | field set 3-17 +CITATION: coverage/final-940.cobertura.xml | git-ignored document on disk; class elements 142481 (DirectoryInfoWrapper.cs, line 16 `(2/2)`), 143395 (PhysicalDirectoryInfoAdapter.cs, line 13 `(1/2)`), 144014 (PhysicalFileInfoAdapter.cs, line 27 `(1/2)`); untouched files 45706, 84687, 102997; `condition-coverage=` 13612 +CITATION: coverage/baseline-940.cobertura.xml | git-ignored document on disk; `condition-coverage=` 13612 +CITATION: docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/research/2026-09-29T21-10-filesystem-wrapper-tests-open-repository-solution-file-research.md | sections 1.1-1.3, 2, 4.2-4.4, 5, 6, 8, 9 +CITATION: docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/plan.2026-09-28T20-01.md | conventions 33-53; command reference 135-349; target source E 614-697; controls 703-708; Phases 0-4 830-974 +CITATION: docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/regression-testing/mutation-openread-sentinel.md | M4 record format +CITATION: docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/baseline/p0-t9-stall-probe.2026-09-29T09-00.md | STALL-PROBE REPRODUCES; COVERAGE-ROUTE DIRECT +CITATION: docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/qa-gates/post-merge-coverage-final.md | UtilitiesCS LINE 38811/43423 and BRANCH 9413/11271 at HEAD 55a50e92; collector variance record +CITATION: docs/features/potential/promoted/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file.md | present in the worktree +AC-INVENTORY: AC1, AC2, AC3, AC4, AC5, AC6, AC7, AC8 +AC-MAPPING: AC1 | IMPLEMENTATION: P1-T3 (Target Source A) and P1-T4 (Target Source B) | TESTS: P2-T5 fifteen named results; P2-T8 zero counts for GetRepositoryRoot, GetSolutionFile, TaskMaster.sln, AppDomain | EVIDENCE: p1-t3-pfs-census, p1-t4-diw-census, p2-t8-post-format-census, test-run-final.md; check-off P2-T12 +AC-MAPPING: AC2 | IMPLEMENTATION: P1-T3 (both catch blocks deleted) | TESTS: P2-T8 catch count 0 in both files; P2-T10 ADDED-CATCH 0 | EVIDENCE: p2-t8-post-format-census, p2-t10-scope-boundary; check-off P2-T13 +AC-MAPPING: AC3 | IMPLEMENTATION: P1-T3 and P1-T4 (owned fixtures, rooted literal, missing owned paths with Exists preconditions, no-op calls on the owned directory, its parent and the owned image, D-1) | TESTS: P2-T8 constructor-form equalities and precondition counts; P2-T10 ADDED-ROOTWALK and ADDED-TEMP 0; P2-T5 the four missing-path tests Passed; P2-T14 MUTATING-CALL-INVENTORY (34 PFS call sites in the five classes, DIW calls all mock) | EVIDENCE: p2-t8-post-format-census, p2-t10-scope-boundary, test-run-final.md, p2-t14-ac3-mutating-call-inventory; check-off P2-T14 +AC-MAPPING: AC4 | IMPLEMENTATION: D-2 member preservation in Target Source A and B, the two MemberCount assertions for GetObjectData, the no-op-no-throw Create, CreateSubdirectory, Refresh and SetAccessControl calls on owned entries | TESTS: P2-T8 member-token lower bounds and .MemberCount 2; P2-T7 per-file FILE lines NOT-LOWER for covered lines and covered branches (MEASUREMENT 3, D-8); P2-T5 (no-exception evidence for the no-op rows); P2-T15 AC4-FORM fifteen rows | EVIDENCE: p2-t8-post-format-census, coverage-final.md MEASUREMENT 3 comparison section, test-run-final.md, p2-t15-ac4-form; check-off P2-T15 +AC-MAPPING: AC5 | IMPLEMENTATION: Target Source A and B (no temporary file, no attribute, no runsettings edit) | TESTS: P2-T8 determinism tokens 0; P2-T10 eight ADDED- counts 0 and OUT-OF-SET-DIFF-EXIT 0 | EVIDENCE: p2-t8-post-format-census, p2-t10-scope-boundary; check-off P2-T16 +AC-MAPPING: AC6 | IMPLEMENTATION: P1-T9 through P1-T30 (Control Table C1-C11) and P1-T31 | TESTS: eleven per-method filtered runs failing with the predicted phrase, each followed by a confirming pass | EVIDENCE: the eleven mutation-*.md records, p1-t31-post-control-clean-tree; check-off P2-T17 +AC-MAPPING: AC7 | IMPLEMENTATION: P1-T1 (classification only, D-11) | TESTS: P2-T10 OUT-OF-SET-DIFF-EXIT 0 over the three named files and UCS-TEST-CHANGED excluding SortEmail_Tests.cs | EVIDENCE: root-walk-site-classification..md, p2-t10-scope-boundary; check-off P2-T18 +AC-MAPPING: AC8 | IMPLEMENTATION: P0-T6 through P0-T11 baselines, the P2-T1 through P2-T6 loop, the P2-T7 post-merge rebuilds and MEASUREMENT 3, P2-T9 | TESTS: P2-T2 check exit 0, P2-T3 and P2-T4 rebuilds and the P2-T7 post-merge rebuilds (p2-t7a, p2-t7b), the P2-T7 MEASUREMENT 3 coverage route with NEWLY-FAILING NONE and both floors MET (line at least 80, branch at least 75), the three FILE lines for final3 NOT-LOWER=True for covered lines and covered branches (the amended AC8 clause, D-7), the in-memory negative control CONTROL-LINE and CONTROL-BRANCH reading NOT-LOWER=False | EVIDENCE: toolchain-pass.md, coverage-baseline.md, coverage-final.md (MEASUREMENT 3, comparison, negative-control and variance-table sections), p2-t7-msbuild-analyzers, p2-t7-msbuild-nullable; check-off P2-T19 +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, Bootstrap and Baseline Capture + +- [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/policy-compliance-order/SKILL.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/issue.md and FEATURE/research/2026-09-29T21-10-filesystem-wrapper-tests-open-repository-solution-file-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 minor-audit preconditions read-only and record them in FEATURE/evidence/baseline/p0-t2-mode-preconditions..md. Acceptance, all six required: FEATURE/issue.md contains the exact line `- Work Mode: minor-audit`; FEATURE/issue.md contains a heading line whose text is exactly `## Acceptance Criteria`; the box-state-independent inventory regex `^- \[[ x]\] AC[1-8]: ` matches exactly 8 lines of FEATURE/issue.md and every one of them begins `- [ ] ` (none is checked before execution starts); the AC3 line contains the literal `its parent directory` and the AC4 line contains the literal `no-op by construction` (the 2026-09-30 amendments that the AC3 Mutating-Call Inventory and the AC4 Member Forms gates rely on; an issue.md lacking either literal is `AC TEXT MISMATCH`: stop and report, do not edit issue.md); neither FEATURE/spec.md nor FEATURE/user-story.md exists (Glob over the feature folder); the feature folder contains exactly issue.md, this plan file and the research/ directory as its non-evidence content (a `.md` file directly under FEATURE/ other than issue.md and plan.2026-09-29T23-02.md is `UNEXPECTED FEATURE DOCUMENT`; a pre-existing FEATURE/evidence/ tree, for example the orchestrator's preflight-clearance record under FEATURE/evidence/other/, is evidence content, is recorded under `PRE-EXISTING-EVIDENCE:` as each such path or `NONE`, and is not a precondition failure). Any failure is `MODE PRECONDITION FAILED`: stop and report; the executor does not edit FEATURE/issue.md to repair it. + +- [x] [P0-T3] Record the working context, the diff anchor, 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 rev-parse origin/main` (`ORIGIN-MAIN-SHA:`); `git merge-base HEAD origin/main` (`MERGE-BASE:`); `git rev-list --count HEAD..origin/main` (`BEHIND-COUNT:`); `git rev-list --count origin/main..HEAD` (`AHEAD-COUNT:`; at execution time at least 1, because the orchestrator's preparation commit carrying the feature folder and the promoted record precedes the run); `git diff --name-only origin/main...HEAD` (`AHEAD-PATHS:`, the committed paths the branch carries beyond the merge base). Reconciliation rule, exactly one branch, decided by the recorded values: (r0) `BEHIND-COUNT: 0` (`ORIGIN-MAIN-SHA:` equals `MERGE-BASE:`): record `RECONCILE: NOT NEEDED`, `RECONCILE-EXIT: NOT APPLICABLE`. (r1) `BEHIND-COUNT:` greater than 0 and `AHEAD-COUNT: 0`: run `git merge --ff-only origin/main` once; record `RECONCILE: FAST-FORWARD`, `RECONCILE-EXIT:` and `RECONCILED-FROM:` (the `BASE-SHA:` value taken before the merge). (r2) `BEHIND-COUNT:` greater than 0, `AHEAD-COUNT:` greater than 0, and every `AHEAD-PATHS:` entry begins docs/features/ or .claude/agent-memory/: run `git merge --no-edit origin/main` once (a merge commit; `git rebase` and `git reset` are never run, because a rewrite of the preparation commits would invalidate the checkpoint and the promoted record the orchestrator committed); record `RECONCILE: MERGE`, `RECONCILE-EXIT:` and `RECONCILED-FROM:`; when `RECONCILE-EXIT:` is non-zero, run `git merge --abort` and stop with `ORIGIN MAIN ADVANCED`. (r3) `BEHIND-COUNT:` greater than 0 with any `AHEAD-PATHS:` entry outside those two prefixes: `ORIGIN MAIN ADVANCED`, stop and report; no merge, rebase or reset is run. After (r1) or (r2), run `git rev-parse HEAD`, `git rev-parse origin/main`, `git merge-base HEAD origin/main` and `git rev-list --count HEAD..origin/main` again and re-record `BASE-SHA:`, `ORIGIN-MAIN-SHA:`, `MERGE-BASE:` and `BEHIND-COUNT:` (the pre-reconciliation values are kept under `PRE-RECONCILE-BASE-SHA:`, `PRE-RECONCILE-MERGE-BASE:` and `PRE-RECONCILE-BEHIND-COUNT:`); a `RECONCILE-EXIT:` other than 0 or a re-recorded `BEHIND-COUNT:` other than 0 is `ORIGIN MAIN ADVANCED`: stop and report. Under every branch, write `ANCHOR-SHA:` as a copy of the final `ORIGIN-MAIN-SHA:` value; it is the value every two-dot worktree span of this plan substitutes for the bare token ANCHOR-SHA (Refs), so a later fetch by a sibling session that advances the shared refs/remotes/origin/main cannot move those gates. Then, after (r1) or (r2) only, run `git diff --name-only RECONCILED-FROM HEAD -- UtilitiesCS.Test/HelperClasses/PhysicalFileSystemAdapters_Tests.cs UtilitiesCS.Test/HelperClasses/DirectoryInfoWrapper_Tests.cs UtilitiesCS/HelperClasses/FileSystem/PhysicalDirectoryInfoAdapter.cs UtilitiesCS/HelperClasses/FileSystem/PhysicalFileInfoAdapter.cs UtilitiesCS/HelperClasses/FileSystem/DirectoryInfoWrapper.cs` (the executor substitutes the recorded `RECONCILED-FROM:` value) and record it as `UPSTREAM-TOUCHED-CENSUS-FILES:` (`NONE` when it prints nothing; `NOT APPLICABLE` under (r0)); a non-empty value is `CITED TREE ADVANCED`: stop and report, because the line citations of Target Source C and the Token Census Expectations describe the tree the merge base held, and P0-T12's token gate is only the second, count-level check of the same five files. A PreToolUse refusal of a merge span is `PRE-IMPLEMENTATION GATE BLOCKED`: stop and report the refusal text verbatim. Then `git diff --name-only origin/main...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), with `INHERITED-PROMOTION-RECORD:` naming docs/features/potential/promoted/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file.md when it appears in that union or `ABSENT` otherwise; `git status --porcelain -- UtilitiesCS UtilitiesCS.Test` (recorded verbatim as `SOURCE-PORCELAIN:`, `EMPTY` when nothing printed). Record `TOPLEVEL CONTAINS FEATURE:` as `YES` or `NO` by testing that FEATURE/issue.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`), `CHECKPOINT-PREPARATION-MODE:` (the `preparation_mode` value or `ABSENT`) and `CHECKPOINT-LIFECYCLE-READY:` (fact 20), and `PRE-IMPLEMENTATION GATE READY:` as `YES` only when the file exists, `CHECKPOINT-ISSUE-NUM:` is `940`, `CHECKPOINT-FEATURE-FOLDER:` begins docs/features/active/, `CHECKPOINT-ROUTE:` is not `ABSENT` and `CHECKPOINT-LIFECYCLE-READY:` is `true`; otherwise `NO`. Acceptance, all eight required: the abbreviated branch name equals bug/filesystem-wrapper-tests-open-repository-solution-file-940 (otherwise report `BRANCH MISMATCH` and stop; do not create or switch branches); `BASE-SHA:`, `ORIGIN-MAIN-SHA:` and `MERGE-BASE:` (the re-recorded values when a reconciliation ran) are each a 40-character hexadecimal value, `ORIGIN-MAIN-SHA:` equals `MERGE-BASE:` with `BEHIND-COUNT: 0` (otherwise report `ORIGIN MAIN ADVANCED` and stop, because every later `origin/main` anchor would then compare against a tree this branch does not contain), and `ANCHOR-SHA:` is present and equals `ORIGIN-MAIN-SHA:`; exactly one `RECONCILE:` line is present, `RECONCILE-EXIT:` reads `0` or `NOT APPLICABLE`, and `UPSTREAM-TOUCHED-CENSUS-FILES:` reads `NONE` or `NOT APPLICABLE`; `INHERITED-CLAUSE-A:` is present and lists neither Write Set path (a listed one is `WRITE SET ALREADY DIRTY`: stop and report); `SOURCE-PORCELAIN: EMPTY`; `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 six `CHECKPOINT-` values and stops at this task (a `CHECKPOINT-PREPARATION-MODE: true` value is recorded, not gated: the hook, not this plan, decides whether it admits the P1-T3 edit, and a refusal there is `PRE-IMPLEMENTATION GATE BLOCKED`); 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:` (scripts/vscode/TaskMaster.cli.runsettings), `PRE-EDIT-HASH-PFS:` and `PRE-EDIT-HASH-DIW:` (the two Write Set files) and `PRE-EDIT-HASH-PDA:`, `PRE-EDIT-HASH-PFA:` and `PRE-EDIT-HASH-DIWP:` (UtilitiesCS/HelperClasses/FileSystem/PhysicalDirectoryInfoAdapter.cs, PhysicalFileInfoAdapter.cs and DirectoryInfoWrapper.cs) as `Get-FileHash -Algorithm SHA256 -LiteralPath` values; 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 eight required: `CHANNEL: COMMAND`; the six hashes are each 64-character hexadecimal values; `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"` (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:`, `UCS_TEST_CSC_OUT_LINES:`, `UCS_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`; `UCS_TEST_CSC_OUT_LINES:` and `UCS_CSC_OUT_LINES:` are each at least 1 (the test project and its production project 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 P2-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 AC8 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 `UCS-TEST-DLL-EXISTS:` as whether UtilitiesCS.Test\bin\Debug\UtilitiesCS.Test.dll exists 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); `UCS-TEST-DLL-EXISTS: 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 `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:`, 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 18). 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 scoped run of the two classes, before any edit, with `CMD-VSTEST` using `FILTER-BOTH`, `NAMES-PRE` and `TASKID` `p0-t10`, and write FEATURE/evidence/baseline/test-run-baseline.md (fixed name) with `Timestamp:`, `Command:` (the runsettings file name, the isolation switch and the filter verbatim), `EXIT_CODE:`, `ExpectedExitCode:` equal to the observed value when it is non-zero (presentational), and an `Output Summary:` with `RUNSETTINGS-HASH-NOW:`, the `COUNTERS` line, `RESULT_COUNT:`, `SEQUENCE_FILES:`, every `RESULT` line, every `MESSAGE` line, and `BASELINE-FAILED-TWO-CLASSES:` listing every `Failed` test name or `NONE`. Acceptance, all four required: `RUNSETTINGS-HASH-NOW:` equals `RUNSETTINGS-HASH:`; the `COUNTERS` line reads `total=12` with `executed` 12 (the pre-edit population of fact 25; a different total is `STALE ASSEMBLY OR FILTER MISMATCH`: stop and report); the twelve `NAMES-PRE` names each appear in a `RESULT` line; `SEQUENCE_FILES: 0`. A non-empty failed set is recorded as `BASELINE NOT GREEN` for the two classes and the task completes (D-15: a pre-edit failure of these classes is the defect itself, and the rewrite does not depend on their passing). + +- [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). Under `COVERAGE-ROUTE: RUNNER` run `CMD-COVERAGE-RUNNER` with `STAGE` `baseline`; under `DIRECT` run `CMD-COVERAGE-DIRECT` with `STAGE` `baseline`; either payload is started as a background invocation and its captured stdout is polled until its final `TRX_PRESENT:` line appears (Long-running payloads convention; the run can exceed a foreground tool call's ten-minute limit); then, under either route and unless branch (d0) below applies, run `CMD-COVERAGE-POST` with `STAGE` `baseline`, `RAW` being 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 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), `THRESHOLD_MESSAGE:` (RUNNER), `LINE-FLOOR:` and `BRANCH-FLOOR:` as printed by `CMD-COVERAGE-POST`, the `First-party coverage:` line with its two percentages transcribed as `FIRST-PARTY-LINE-PERCENT:` and `FIRST-PARTY-BRANCH-PERCENT:`, the projection verbatim between `PROJECTION-BEGIN` and `PROJECTION-END`, the five summary lines verbatim between `SUMMARY-BEGIN` and `SUMMARY-END`, `FAILED-SET:`, and the three `FILE` lines that `CMD-FILE-COMPARE` would read for the baseline document, obtained by running that block with `STAGE` `baseline` (it then compares the baseline document with itself and every `FILE` line must read `NOT-LOWER=True` with a non-zero denominator, which is the positive control that the three files are instrumented). Branches, decided by the recorded values and checked in the order (d0), (c), (b), (a), (d): (d0) a run with `SEQUENCE_FILES:` greater than 0 (DIRECT) or with `TRX_PRESENT: False` 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` and do not re-run. (c) a runner exit non-zero with a `THRESHOLD_MESSAGE:`, or a `LINE-FLOOR: NOT MET` or `BRANCH-FLOOR: NOT MET` line printed by `CMD-COVERAGE-POST`: record `FLOOR: NOT MET` with the message and stop with `COVERAGE FLOOR BASELINE NOT MET`, because AC8 requires the coverage route to pass and this item changes no production line. (b) a non-zero exit with no floor failure whose `FAILED-SET:` is non-empty and whose every name is in `BASELINE-ADMISSIBLE` or in `NAMES-PRE` (D-12, D-15): record the set, record its `NAMES-PRE` subset as `BASELINE-DEFECT-CLASS-FAILURES:` (`NONE` when that subset is empty) and complete with no re-run; 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, recording `BASELINE-DEFECT-CLASS-FAILURES: NONE`. (d) any other outcome, in particular a non-zero exit with an empty `FAILED-SET:`, is `COVERAGE RUN ABORTED` with the (d0) handling. Acceptance, all seven required: the projection block contains a `package` element named `UtilitiesCS` with a `LINE` and a `BRANCH` counter; the `First-party coverage:` line is present with four integer counts and two percentages, and `FIRST-PARTY-LINE-PERCENT:` is at least 80 with `FIRST-PARTY-BRANCH-PERCENT:` at least 75; the summary block's first line begins `Test run outcome:`; `FAILED-SET:` is present; exactly one `BASELINE-DEFECT-CLASS-FAILURES:` line is present; the three `FILE` lines are present with non-zero denominators; the artifact contains no absolute path (the runner's own path-bearing lines are not transcribed). coverage\baseline-940.jacoco.xml and coverage\baseline-940.cobertura.xml remain on disk, git-ignored, for P2-T7. + +- [x] [P0-T12] Record the pre-edit census by running `CMD-CENSUS` once for each of `UtilitiesCS.Test/HelperClasses/PhysicalFileSystemAdapters_Tests.cs`, `UtilitiesCS.Test/HelperClasses/DirectoryInfoWrapper_Tests.cs`, UtilitiesCS/HelperClasses/FileSystem/PhysicalDirectoryInfoAdapter.cs, UtilitiesCS/HelperClasses/FileSystem/PhysicalFileInfoAdapter.cs and UtilitiesCS/HelperClasses/FileSystem/DirectoryInfoWrapper.cs, and write FEATURE/evidence/baseline/p0-t12-pre-edit-census..md transcribing every `TOKEN`, `LINES` and `SHA256` line per file. 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 for the gated tokens (in particular PFS `GetRepositoryRoot` 6, `GetSolutionFile` 3, `catch (IOException)` 2, `catch` 2, `[TestMethod]` 4, `AppDomain` 2, `TaskMaster.sln` at least 1, `Assembly.Location` 5, `FileShare.None` 3, `FileShare.ReadWrite` 5, `.MemberCount` 0, `Throw()` 5, `Throw()` 3, `new DirectoryInfo(` 2, `new FileInfo(` 2, every new identifier 0 (these counts include comment occurrences, fact 2); DIW `GetRepositoryRoot` 5, `TaskMaster.sln` 3, `AppDomain` 1, `File.Exists(` 1, `[TestMethod]` 8, `RootedFixturePath` 0, `new DirectoryInfo(` 1, `new FileInfo(` 0; PDA `!_directoryInfo.Exists` 0, `Array.Empty()` 0, `set { }` 0, `public void Delete() { }` 0, `_directoryInfo.FullName` 1; PFA `public long Length => 0;` 0, `set { }` 0, `public void MoveTo(string destFileName) { }` 0; DIWP `Array.Empty()` 0, `Enumerable.Empty()` 0, `return string.Empty;` 0); every `at least` token of the table has at least the value the `pre` column names; the five `SHA256` values equal the five `PRE-EDIT-HASH-` values of P0-T4; `LINES` for PFS and DIW are recorded (388 and 393 expected; a value differing by one is recorded as a terminator artefact). Any other 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 separate invocations in this order: `git diff --cached --name-only` (recorded verbatim as `PRE-STAGED:`, or `NONE`; a path another session staged is neither unstaged nor committed by this task); `git status --porcelain -- docs/features/potential` (recorded as `POTENTIAL-PORCELAIN:`, `EMPTY` when nothing printed; the promoted record docs/features/potential/promoted/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file.md was committed by the orchestrator's preparation commit before this run, so nothing under that tree is expected to be staged or modified); `git add -- docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940`; `git commit -m "docs(940): phase 0 baseline evidence for the file-system wrapper test fix" -- docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940` (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; the pathspec confines the commit to that tree whatever `PRE-STAGED:` listed); `git show --name-only --format= HEAD` (recorded verbatim as `PHASE0-COMMIT-PATHS:`); `git status --porcelain -- UtilitiesCS UtilitiesCS.Test`; `git rev-parse HEAD`. Write FEATURE/evidence/baseline/p0-t13-phase0-commit..md recording `PRE-STAGED:`, `POTENTIAL-PORCELAIN:`, `COMMIT-EXIT:`, `PHASE0-COMMIT-PATHS:`, `PHASE0-HEAD:` and the scoped source porcelain output verbatim (`EXIT_CODE:` is scoped to the source porcelain span). Acceptance, all five required: `COMMIT-EXIT: 0`; `POTENTIAL-PORCELAIN: EMPTY`; every `PHASE0-COMMIT-PATHS:` entry begins docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/ (a path outside that prefix, in particular one under docs/features/potential/, is `COMMIT SWEPT FOREIGN PATH`: stop and report); the scoped source 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 P1-T8. + +### Phase 1 — Constrained Implementation: Classification, Fail-Before Dossier, Test-Only Rewrite, Exception-Type Observation and Negative Controls + +- [x] [P1-T1] Classify the three additional root-walk sites (AC7) by re-reading, with the Read tool only, TaskMaster.Test/Ribbon/RibbonControllerTests.cs lines 296 to 311 and 422 to 437, TaskMaster.Test/Bootstrap/FSharpCoreHintPathAlignmentTests.cs lines 11 to 107 with its consumers at 156 and 181, UtilitiesCS.Test/EmailIntelligence/SortEmail_Tests.cs lines 186 to 296 and 393 to 415, and UtilitiesCS/EmailIntelligence/EmailParsingSorting/SortEmail.cs lines 888 to 897, and write FEATURE/evidence/other/root-walk-site-classification..md with `Timestamp:`, `Command:` (`Read tool over the four files; no command executed`), `EXIT_CODE: 0` (scoped to the read-only classification), an `Output Summary:` and one section per site. Each section carries `SITE:` (the repository-relative path with the line range re-read), `ROOT-USE:` (one sentence: what the located root is used for), `CLASS:` (exactly one of `legitimate repository-file read`, `same defect class`, `layout dependence without file-system I/O`), `RECOMMENDATION:` (`out of scope for #940; no change` for the first class and the third class; `out of scope for #940; follow-up handed to the orchestrator` for the second class) and `MODIFIED: NONE`. The expected classification, each to be confirmed or corrected from the re-read text rather than copied: RibbonControllerTests `legitimate repository-file read` (a source-as-specification read of TaskMaster/Ribbon/RibbonController.FolderTree.cs with `File.ReadAllText`, whose assertion is about the checked-in source text); FSharpCoreHintPathAlignmentTests `legitimate repository-file read` (a build-configuration alignment test that enumerates every project file by design, per its class remarks); SortEmail_Tests, two sections: the four consumers at 196, 223, 255 and 280 `layout dependence without file-system I/O` (the root is passed as a folder-path string to production code that only combines strings on those paths), and `TrySaveAttachmentAsync_WhenSaveSucceeds_ReturnsTrueAndCallsSaveAsFile` (237 to 249) `same defect class` (SortEmail.cs line 896 invokes `System.IO.Directory.CreateDirectory` on the repository root, a real creation API that is a no-op only because the root exists; the production method carries `ExcludeFromCodeCoverage` at 888). Acceptance, all four required: the artifact exists with exactly four `SITE:` sections covering the three files named by AC7; every `CLASS:` value is one of the three tokens; every section carries `MODIFIED: NONE`; the artifact names no path under docs/features/potential/ and instructs no issue creation (the executor creates neither; D-11). + +- [x] [P1-T2] 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 defect is decided by whether another process holds TaskMaster.sln with a share mode that excludes readers, or by whether the repository layout places a solution file above the test assembly, and a committed test can arrange neither without starting an external process or relocating the assembly, both prohibited by the unit-test policy); `## Alternative Proof` (the static reproduction: the pre-edit sites as re-recorded by P0-T12, namely the PFS root walk at 349 to 371 and `GetSolutionFile` at 373 to 377 with the real mutations of fact 2, and the DIW root walk at 375 to 391 with the enumeration assertions at 60 and 79; the recorded #906 failure of the same defect class in the #900 run, cited through the #931 dossier docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/evidence/regression-testing/fail-before-exception.2026-09-29T09-07.md; and the sentence that P1-T9 through P1-T30 later demonstrate deterministically that each rewritten test fails against a deliberately broken wrapper or adapter, 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 `P1-T9` and the literal `share mode` at least once each. + +- [x] [P1-T3] Replace the entire content of `UtilitiesCS.Test/HelperClasses/PhysicalFileSystemAdapters_Tests.cs` with exactly the Target Source A content (Read the file first, then Write it), then run `CMD-CENSUS` on it, `git diff --numstat HEAD -- UtilitiesCS.Test/HelperClasses/PhysicalFileSystemAdapters_Tests.cs` and `git status --porcelain -- UtilitiesCS.Test/HelperClasses/PhysicalFileSystemAdapters_Tests.cs`, and write FEATURE/evidence/regression-testing/p1-t3-pfs-census..md transcribing every `TOKEN`, `LINES` and `SHA256` line, the numstat line and the porcelain line. Acceptance, all seven required: `GetRepositoryRoot` 0, `GetSolutionFile` 0, `TaskMaster.sln` 0, `AppDomain` 0, `catch (IOException)` 0, `catch` 0, `FileShare.None` 0, `RootedFixturePath` 0, `using Moq;` 0; `[TestMethod]` 7, `using System.Linq;` 1, `__940_missing_` 1, `MissingOwnedPath(` at least 4, `Exists.Should().BeFalse()` at least 4, `Assembly.Location` at least 1, `FileShare.ReadWrite` at least 4; `new DirectoryInfo(` equals `new DirectoryInfo(MissingOwnedPath(` plus `new DirectoryInfo(RootedFixturePath)` and `new FileInfo(` equals `new FileInfo(MissingOwnedPath(` plus `new FileInfo(typeof(`; `Throw()` 20, `Throw()` 3 and `.MemberCount` 2; every `at least` member token of the Token Census Expectations table meets its bound; `Thread.Sleep`, `Task.Delay`, `[Timeout`, `DoNotParallelize`, `Retry`, `Path.GetTemp`, `File.Create`, `File.WriteAll`, `Directory.CreateDirectory` 0 each; `LINES` at most 500 and the porcelain line begins ` M`. A PreToolUse refusal of the file write is `PRE-IMPLEMENTATION GATE BLOCKED`: stop and report. + +- [x] [P1-T4] Apply the six Target Source B edits to `UtilitiesCS.Test/HelperClasses/DirectoryInfoWrapper_Tests.cs` with the Edit tool in the stated bottom-up order, then run `CMD-CENSUS` on it, `git diff --numstat HEAD -- UtilitiesCS.Test/HelperClasses/DirectoryInfoWrapper_Tests.cs` and `git status --porcelain -- UtilitiesCS.Test/HelperClasses/DirectoryInfoWrapper_Tests.cs`, and write FEATURE/evidence/regression-testing/p1-t4-diw-census..md transcribing every `TOKEN`, `LINES` and `SHA256` line, the numstat line and the porcelain line. Acceptance, all six required: `GetRepositoryRoot` 0, `TaskMaster.sln` 0, `AppDomain` 0, `File.Exists(` 0, `catch` 0, `GetSolutionFile` 0, `MissingOwnedPath(` 0; `[TestMethod]` 8, `RootedFixturePath` 3, `Assembly.Location` 1, `OwnedAssemblyFile` at least 1, `OwnedAssemblyDirectory` at least 1, `using Moq;` 1, `using System.Linq;` 1; `new DirectoryInfo(` 2 and equal to `new DirectoryInfo(RootedFixturePath)`, `new FileInfo(` 1 and equal to `new FileInfo(typeof(`; `.GetFiles(` at least 7, `.GetDirectories(` at least 7, `.EnumerateFileSystemInfos(` at least 7, `.ToString()` at least 4; the nine determinism tokens 0 each; the numstat line reads at least 11 added and at least 18 deleted, `LINES` at most 500 and the porcelain line begins ` M`. + +- [x] [P1-T5] Format and verify the two Write Set files with the pinned CSharpier. Inside one payload: capture the SHA-256 of `UtilitiesCS.Test/HelperClasses/PhysicalFileSystemAdapters_Tests.cs` and `UtilitiesCS.Test/HelperClasses/DirectoryInfoWrapper_Tests.cs`; run `dotnet tool run csharpier format` with those two paths as arguments; capture the two hashes again; run `dotnet tool run csharpier check` with the same two paths; then run `CMD-CENSUS` on each. Write FEATURE/evidence/regression-testing/p1-t5-csharpier-scoped..md with `Timestamp:`, `ITERATION:`, `Command:` (all three), `EXIT_CODE:` (of the `check`), and an `Output Summary:` recording the four hashes labelled per file and as before or after, `REWRITTEN:` as the number of files whose hash changed (the console line `Formatted 2 files in Xms.` is a processed-file count and is not this value), the `check` command's final summary line verbatim, `CENSUS-TOKENS-APPENDED:` (the `Throw()` literals P1-T7 pinned in earlier iterations, or `NONE`), `THROW-SUM:` (the sum of the `TOKEN` values of the census literals `Throw()`, `Throw()` and each literal appended by P1-T7; a census-token sum, not a scan for every `Throw<`; the one `Throw()` occurrence is not a census token and is excluded), and every `TOKEN` and `LINES` line per file. Acceptance, all four required: the `check` exits 0; every `post` value of the Token Census Expectations table holds for both files under the table's gate column (formatting re-wraps lines but every gated token is single-line by construction, D-14); `THROW-SUM: 23`; both `LINES` values are at most 500. If either `LINES` exceeds 500, stop and report `FILE SIZE LIMIT EXCEEDED` with the file and value; do not shorten comments to recover, because the comments carry the fixture rationale the AC3 argument cites. + +- [x] [P1-T6] Build the test project from the edited source with `CMD-BUILD` (`TASKID` `p1-t6` on the first iteration, `p1-t6-i` on iteration n of the P1-T7 pinning loop) and write FEATURE/evidence/regression-testing/p1-t6-build-after-fix..md with `Timestamp:`, `ITERATION:`, `Command:` (`msbuild UtilitiesCS.Test\UtilitiesCS.Test.csproj /t:Build /m /p:Configuration=Debug /p:Platform=AnyCPU`, resolved through vswhere, plus /nodeReuse:false), `EXIT_CODE:` (the printed `MSBUILD_EXIT_CODE:`) and an `Output Summary:` recording `CSC_OUT_LINES:`, `PROD_CSC_OUT_LINES:`, `ZERO_ERRORS_LINES:` and `DLL_ADVANCED:`. Acceptance, all three required: `EXIT_CODE: 0`; `CSC_OUT_LINES:` at least 1 and `DLL_ADVANCED: True` (the edited files were compiled into the assembly the next task loads); `ZERO_ERRORS_LINES:` at least 1. A compile error naming a Write Set file is `REWRITE DOES NOT COMPILE`: stop and report the error lines verbatim; the Target Source is corrected by a plan revision, not by the executor. + +- [x] [P1-T7] Run the exception-type observation of the rewritten classes (D-9) with `CMD-VSTEST` using `FILTER-BOTH`, `NAMES-BOTH` and `TASKID` `p1-t7` (`p1-t7-i` on iteration n), and write FEATURE/evidence/regression-testing/p1-t7-exception-type-observation..md with `Timestamp:`, `ITERATION:`, `Command:`, `EXIT_CODE:` (the printed `VSTEST_EXIT_CODE:`), `ExpectedExitCode:` equal to the observed value when it is non-zero and the task continues under the pinning branch, and an `Output Summary:` recording `RUNSETTINGS-HASH-NOW:`, the `COUNTERS` line, `RESULT_COUNT:`, `SEQUENCE_FILES:`, every `RESULT` line (the fifteen `NAMES-BOTH` names) and every `MESSAGE` line verbatim after the hygiene substitution. Branches, decided by the recorded `MESSAGE` lines and checked in the order (a), (c), (d), (f), (b), (e): (a) `failed` 0: record `EXCEPTION-TYPES: AS PREDICTED` and `PINNED-COUNT: 0`; complete. (b) every `MESSAGE` line contains `to be thrown, but found` and names, inside the angle brackets that follow `but found`, a type other than `System.Exception` and `System.SystemException`: for each such line, identify the one `Throw<...>()` assertion that produced it (the action name precedes the message's subject, and the artifact records the mapping as `PINNED: `), edit with the Edit tool only the generic type argument of that one assertion to the observed type's simple name (adding no `using` directive unless the type's namespace is not already imported, in which case add it and record `PINNED-USING: `), record `PINNED-COUNT:` as the number of edited assertions, then re-run P1-T5, P1-T6 (`TASKID` `p1-t6-i`) and this task (`p1-t7-i`) as the next iteration. A mismatch on an assertion not yet pinned, including one that an earlier failing assertion in the same method masked, takes branch (b) again. A mismatch on an assertion pinned in an earlier iteration is `EXCEPTION TYPE UNSTABLE`: stop and report. The loop is bounded at nine iterations; a would-be tenth is `PINNING LOOP EXHAUSTED`: stop and report. For each pinned type T, append the literal `Throw()` to the `CMD-CENSUS` token list for later P1-T5 iterations and for P2-T8, and record `PINNED-TOKEN Throw() = `. The pinned counts sum to `PINNED-COUNT:`, and the next iteration's P1-T5 census must show `THROW-SUM: 23` with `Throw()` and `Throw()` each reduced by the number of pinned assertions whose predicted type it was. When the observed type is FileNotFoundException or DirectoryNotFoundException, no literal is appended; that type's expected count rises by one per such pin and the predicted type's count falls by one, so THROW-SUM stays 23, and the `PINNED-TOKEN` line for that pin names the already-listed literal with its raised expected count. (f) a `MESSAGE` line containing `because GetObjectData adds values` is `GETOBJECTDATA ADDED NOTHING`: stop and report (a plan revision reclassifies `GetObjectData` under the AC4 no-op clause; the executor neither removes nor weakens the `MemberCount` assertion). (c) a `MESSAGE` line containing `but no exception was thrown` for any setter, `CopyTo`, `MoveTo` or `Replace` action is `SETTER DID NOT THROW`: stop and report (the throw-based verification shape for that member needs a plan revision; the executor neither removes the assertion nor widens it). (d) a `MESSAGE` line whose text after the first `" :: "` begins `Test method` and names `System.IO.IOException` for the `PhysicalFileInfoAdapter_PropertiesStreamsAndAccessors_MirrorFileInfo` method is `OPENREAD SHARE CONFLICT`: stop and report (D-9). (e) any other failing `MESSAGE` is `REWRITE FAILED`: stop and report it verbatim. Acceptance, all five required: `RUNSETTINGS-HASH-NOW:` equals `RUNSETTINGS-HASH:`; `total` 15 and `executed` 15 (a different total is `STALE ASSEMBLY OR FILTER MISMATCH`: stop and report); `SEQUENCE_FILES: 0`; the fifteen `RESULT` lines are present; the completing iteration records `failed` 0, every `RESULT` line `Passed`, and exactly one `EXCEPTION-TYPES:` line reading `AS PREDICTED` or `PINNED` with its `PINNED:` lines and, when `PINNED`, one `PINNED-TOKEN` line per pinned type whose counts sum to `PINNED-COUNT:`. + +- [x] [P1-T8] Commit the fix and the Phase 1 evidence so far. Run `git diff --cached --name-only` (`PRE-STAGED:`, verbatim or `NONE`) and `git status --porcelain -- docs/features/potential` (`POTENTIAL-PORCELAIN:`, `EMPTY` when nothing printed), then `git add -- UtilitiesCS.Test/HelperClasses/PhysicalFileSystemAdapters_Tests.cs UtilitiesCS.Test/HelperClasses/DirectoryInfoWrapper_Tests.cs docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940`, then `git commit -m "test(940): verify file-system wrapper delegation through owned fixtures instead of the repository solution file" -- UtilitiesCS.Test/HelperClasses/PhysicalFileSystemAdapters_Tests.cs UtilitiesCS.Test/HelperClasses/DirectoryInfoWrapper_Tests.cs docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940` (the pathspecs confine the commit to those paths whatever `PRE-STAGED:` listed; 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 -- UtilitiesCS.Test/HelperClasses/PhysicalFileSystemAdapters_Tests.cs UtilitiesCS.Test/HelperClasses/DirectoryInfoWrapper_Tests.cs` (recorded verbatim as `EOL-BEFORE:`). For each of the two 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 forward-slash form). This makes the working-tree bytes identical to the bytes every later `git checkout` of that path writes (fact 22). 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 -- UtilitiesCS.Test`, `git show --name-only --format= HEAD`, `git rev-parse HEAD`, and the SHA-256 of each of the two code files, so both `FIX-HASH-` anchors are taken after the restore. Write FEATURE/evidence/regression-testing/p1-t8-fix-commit..md recording `PRE-STAGED:`, `POTENTIAL-PORCELAIN:`, `COMMIT-EXIT:`, `EOL-BEFORE:`, `EOL-AFTER:`, `EOL-RESTORED:`, `FIX-HEAD:`, `FIX-HASH-PFS:`, `FIX-HASH-DIW:`, the `git show` path list verbatim, and `EXIT_CODE:` scoped to the `git diff --exit-code` span. Acceptance, all five required: `COMMIT-EXIT: 0`; `POTENTIAL-PORCELAIN: EMPTY`; `EXIT_CODE: 0`, proving the committed state equals the working tree for the test project; the `git show` list contains exactly the two code paths plus paths under docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/ and nothing else; every `EOL-AFTER:` entry reads i/lf (index column) and w/crlf (working-tree column), and each path not listed in `EOL-RESTORED:` has a hash equal to its P1-T5 after-format hash from the highest-`ITERATION:` P1-T5 artifact (a restored path differs from that hash 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. + +Control task shapes. The eleven control pairs P1-T9 through P1-T30 follow two fixed shapes, each instantiated with the row of the Control Table the task names. Shape M (mutate and observe, `[expect-fail]`): apply the row's mutation from Target Source C to the row's file with the Edit tool; run `CMD-CENSUS` on that file; run `CMD-BUILD` with the task id as `TASKID`; run `CMD-VSTEST` with the row's filter as `FILTERARG`, the row's `NAMES` list and the task id; write FEATURE/evidence/regression-testing/ (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 and pre-edit line, the census transition token with its mutated value, every other non-zero `TOKEN` line, `LINES`, `SHA256`, `MSBUILD_EXIT_CODE:`, `CSC_OUT_LINES:`, `PROD_CSC_OUT_LINES:`, `ZERO_ERRORS_LINES:`, `DLL_ADVANCED:`, the runsettings file name, the isolation switch, the filter verbatim, `RUNSETTINGS-HASH-NOW:`, `TRX_PRESENT:`, `SEQUENCE_FILES:`, the `COUNTERS` line, every `RESULT` line and every `MESSAGE` line verbatim after the hygiene substitution. Acceptance of shape M, all five required: the census transition shows the row's mutated value; `MSBUILD_EXIT_CODE: 0` with `CSC_OUT_LINES:` at least 1 and `DLL_ADVANCED: True`, and for a production-file row `PROD_CSC_OUT_LINES:` at least 1; `total` 1 and `failed` 1; the `MESSAGE` line contains the row's phrase; `RUNSETTINGS-HASH-NOW:` equals `RUNSETTINGS-HASH:`. A passing run, a `MESSAGE` lacking the phrase, or a `total` other than 1 is `MUTATION PREDICTION MISMATCH`: stop and report the text (D-6). Shape R (revert and confirm): run `git checkout -- ` (forward-slash pathspec; its exit code recorded as `CHECKOUT-EXIT:`, because a pathspec that matches nothing makes git exit non-zero and would otherwise leave the mutation in place while the `--exit-code` span below compared an untouched path vacuously), then `git diff --exit-code HEAD -- ` and `git status --porcelain -- `, `CMD-CENSUS` on the file, `CMD-BUILD` with the task id, and `CMD-VSTEST` with the same filter, names and the task id; append to the row's artifact a section `## Revert and confirming run` recording `CHECKOUT-EXIT:`, `REVERT-DIFF-EXIT:` (the `git diff --exit-code` exit), the porcelain output verbatim (`EMPTY` when it printed nothing), the census transition token with its restored value, `SHA256:` beside the file's anchor (`FIX-HASH-PFS:` or `FIX-HASH-DIW:` for a test file; the P0-T4 `PRE-EDIT-HASH-` value for a production file), the build counters, `CONFIRMING-RUN-EXIT:`, `RUNSETTINGS-HASH-NOW:`, the `COUNTERS` line and every `RESULT` line. Acceptance of shape R, all six required: `CHECKOUT-EXIT: 0`; `REVERT-DIFF-EXIT: 0`; the porcelain output is empty; the census transition shows the row's restored value and the SHA-256 equals the anchor; `CSC_OUT_LINES:` at least 1 with `DLL_ADVANCED: True` (and `PROD_CSC_OUT_LINES:` at least 1 for a production-file row: 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] [P1-T9] [expect-fail] Control C1, shape M: mutate UtilitiesCS/HelperClasses/FileSystem/PhysicalDirectoryInfoAdapter.cs line 34 (`Exists` inverted), run the C1 filter with `NAMES-PFS` and `TASKID` `p1-t9`, and write FEATURE/evidence/regression-testing/mutation-directory-exists-inverted.md. Predicted failure: `PhysicalDirectoryInfoAdapter_AccessorsAndNoOpCreation_MirrorOwnedDirectory` `Failed` with a `MESSAGE` containing `but found False`. Acceptance: shape M with `TOKEN !_directoryInfo.Exists = 1`. + +- [x] [P1-T10] Control C1, shape R: revert UtilitiesCS/HelperClasses/FileSystem/PhysicalDirectoryInfoAdapter.cs (`git checkout -- UtilitiesCS/HelperClasses/FileSystem/PhysicalDirectoryInfoAdapter.cs`), rebuild with `TASKID` `p1-t10`, re-run the C1 filter, and append to FEATURE/evidence/regression-testing/mutation-directory-exists-inverted.md. Acceptance: shape R with `TOKEN !_directoryInfo.Exists = 0` and the SHA-256 equal to `PRE-EDIT-HASH-PDA:`. + +- [x] [P1-T11] [expect-fail] Control C2, shape M: mutate UtilitiesCS/HelperClasses/FileSystem/PhysicalDirectoryInfoAdapter.cs lines 160 to 161 (`GetFiles()` returns an empty array), run the C2 filter with `NAMES-PFS` and `TASKID` `p1-t11`, and write FEATURE/evidence/regression-testing/mutation-directory-getfiles-empty.md. Predicted failure: `PhysicalDirectoryInfoAdapter_Enumeration_WrapsOwnedDirectoryEntries` `Failed` with a `MESSAGE` containing `to contain "UtilitiesCS.Test.dll"`. Acceptance: shape M with `TOKEN Array.Empty() = 1`. + +- [x] [P1-T12] Control C2, shape R: revert UtilitiesCS/HelperClasses/FileSystem/PhysicalDirectoryInfoAdapter.cs (`git checkout -- UtilitiesCS/HelperClasses/FileSystem/PhysicalDirectoryInfoAdapter.cs`), rebuild with `TASKID` `p1-t12`, re-run the C2 filter, and append to FEATURE/evidence/regression-testing/mutation-directory-getfiles-empty.md. Acceptance: shape R with `TOKEN Array.Empty() = 0` and the SHA-256 equal to `PRE-EDIT-HASH-PDA:`. + +- [x] [P1-T13] [expect-fail] Control C3, shape M: mutate UtilitiesCS/HelperClasses/FileSystem/PhysicalDirectoryInfoAdapter.cs line 25 (`CreationTime` setter emptied), run the C3 filter with `NAMES-PFS` and `TASKID` `p1-t13`, and write FEATURE/evidence/regression-testing/mutation-directory-creationtime-setter-noop.md. Predicted failure: `PhysicalDirectoryInfoAdapter_SettersOnMissingDirectory_ThrowWithoutCreatingEntries` `Failed` with a `MESSAGE` containing `but no exception was thrown`. Acceptance: shape M with `TOKEN set { } = 1`. + +- [x] [P1-T14] Control C3, shape R: revert UtilitiesCS/HelperClasses/FileSystem/PhysicalDirectoryInfoAdapter.cs (`git checkout -- UtilitiesCS/HelperClasses/FileSystem/PhysicalDirectoryInfoAdapter.cs`), rebuild with `TASKID` `p1-t14`, re-run the C3 filter, and append to FEATURE/evidence/regression-testing/mutation-directory-creationtime-setter-noop.md. Acceptance: shape R with `TOKEN set { } = 0` and the SHA-256 equal to `PRE-EDIT-HASH-PDA:`. + +- [x] [P1-T15] [expect-fail] Control C4, shape M: mutate UtilitiesCS/HelperClasses/FileSystem/PhysicalDirectoryInfoAdapter.cs line 87 (`Delete()` emptied), run the C4 filter with `NAMES-PFS` and `TASKID` `p1-t15`, and write FEATURE/evidence/regression-testing/mutation-directory-delete-noop.md. Predicted failure: `PhysicalDirectoryInfoAdapter_MissingDirectoryAndUnsupportedInfo_BranchesBehaveAsExpected` `Failed` with a `MESSAGE` containing `but no exception was thrown`. Acceptance: shape M with `TOKEN public void Delete() { } = 1`. + +- [x] [P1-T16] Control C4, shape R: revert UtilitiesCS/HelperClasses/FileSystem/PhysicalDirectoryInfoAdapter.cs (`git checkout -- UtilitiesCS/HelperClasses/FileSystem/PhysicalDirectoryInfoAdapter.cs`), rebuild with `TASKID` `p1-t16`, re-run the C4 filter, and append to FEATURE/evidence/regression-testing/mutation-directory-delete-noop.md. Acceptance: shape R with `TOKEN public void Delete() { } = 0` and the SHA-256 equal to `PRE-EDIT-HASH-PDA:`. + +- [x] [P1-T17] [expect-fail] Control C5, shape M: mutate UtilitiesCS/HelperClasses/FileSystem/PhysicalFileInfoAdapter.cs line 116 (`Length` returns 0), run the C5 filter with `NAMES-PFS` and `TASKID` `p1-t17`, and write FEATURE/evidence/regression-testing/mutation-file-length-zero.md. Predicted failure: `PhysicalFileInfoAdapter_PropertiesStreamsAndAccessors_MirrorFileInfo` `Failed` with a `MESSAGE` containing `to be greater than`. Acceptance: shape M with `TOKEN public long Length => 0; = 1`. + +- [x] [P1-T18] Control C5, shape R: revert UtilitiesCS/HelperClasses/FileSystem/PhysicalFileInfoAdapter.cs (`git checkout -- UtilitiesCS/HelperClasses/FileSystem/PhysicalFileInfoAdapter.cs`), rebuild with `TASKID` `p1-t18`, re-run the C5 filter, and append to FEATURE/evidence/regression-testing/mutation-file-length-zero.md. Acceptance: shape R with `TOKEN public long Length => 0; = 0` and the SHA-256 equal to `PRE-EDIT-HASH-PFA:`. + +- [x] [P1-T19] [expect-fail] Control C6, shape M: mutate UtilitiesCS/HelperClasses/FileSystem/PhysicalFileInfoAdapter.cs line 113 (`IsReadOnly` setter emptied), run the C6 filter with `NAMES-PFS` and `TASKID` `p1-t19`, and write FEATURE/evidence/regression-testing/mutation-file-isreadonly-setter-noop.md. Predicted failure: `PhysicalFileInfoAdapter_SettersOnMissingFile_ThrowWithoutCreatingFiles` `Failed` with a `MESSAGE` containing `but no exception was thrown`. Acceptance: shape M with `TOKEN set { } = 1`. + +- [x] [P1-T20] Control C6, shape R: revert UtilitiesCS/HelperClasses/FileSystem/PhysicalFileInfoAdapter.cs (`git checkout -- UtilitiesCS/HelperClasses/FileSystem/PhysicalFileInfoAdapter.cs`), rebuild with `TASKID` `p1-t20`, re-run the C6 filter, and append to FEATURE/evidence/regression-testing/mutation-file-isreadonly-setter-noop.md. Acceptance: shape R with `TOKEN set { } = 0` and the SHA-256 equal to `PRE-EDIT-HASH-PFA:`. + +- [x] [P1-T21] [expect-fail] Control C7, shape M: mutate UtilitiesCS/HelperClasses/FileSystem/PhysicalFileInfoAdapter.cs line 144 (`MoveTo` emptied), run the C7 filter with `NAMES-PFS` and `TASKID` `p1-t21`, and write FEATURE/evidence/regression-testing/mutation-file-moveto-noop.md. Predicted failure: `PhysicalFileInfoAdapter_MissingFileBranches_ThrowOrNoOpWithoutCreatingFiles` `Failed` with a `MESSAGE` containing `but no exception was thrown`. Acceptance: shape M with `TOKEN public void MoveTo(string destFileName) { } = 1`. + +- [x] [P1-T22] Control C7, shape R: revert UtilitiesCS/HelperClasses/FileSystem/PhysicalFileInfoAdapter.cs (`git checkout -- UtilitiesCS/HelperClasses/FileSystem/PhysicalFileInfoAdapter.cs`), rebuild with `TASKID` `p1-t22`, re-run the C7 filter, and append to FEATURE/evidence/regression-testing/mutation-file-moveto-noop.md. Acceptance: shape R with `TOKEN public void MoveTo(string destFileName) { } = 0` and the SHA-256 equal to `PRE-EDIT-HASH-PFA:`. + +- [x] [P1-T23] [expect-fail] Control C8, shape M: mutate UtilitiesCS/HelperClasses/FileSystem/PhysicalDirectoryInfoAdapter.cs line 64 (`Name` returns `FullName`), run the C8 filter with `NAMES-DIW` and `TASKID` `p1-t23`, and write FEATURE/evidence/regression-testing/mutation-directory-name-fullname.md. Predicted failure: `Properties_ShouldMirrorWrappedDirectoryInfo` `Failed` with a `MESSAGE` containing `"fixture"`. Acceptance: shape M with `TOKEN _directoryInfo.FullName = 2`. + +- [x] [P1-T24] Control C8, shape R: revert UtilitiesCS/HelperClasses/FileSystem/PhysicalDirectoryInfoAdapter.cs (`git checkout -- UtilitiesCS/HelperClasses/FileSystem/PhysicalDirectoryInfoAdapter.cs`), rebuild with `TASKID` `p1-t24`, re-run the C8 filter, and append to FEATURE/evidence/regression-testing/mutation-directory-name-fullname.md. Acceptance: shape R with `TOKEN _directoryInfo.FullName = 1` and the SHA-256 equal to `PRE-EDIT-HASH-PDA:`. + +- [x] [P1-T25] [expect-fail] Control C9, shape M: mutate UtilitiesCS/HelperClasses/FileSystem/DirectoryInfoWrapper.cs line 188 (`GetFiles()` returns an empty array), run the C9 filter with `NAMES-DIW` and `TASKID` `p1-t25`, and write FEATURE/evidence/regression-testing/mutation-wrapper-getfiles-empty.md. Predicted failure: `GetDirectoriesAndGetFiles_ShouldReturnWrappedEntries` `Failed` with a `MESSAGE` containing `not to be empty`. Acceptance: shape M with `TOKEN Array.Empty() = 1`. + +- [x] [P1-T26] Control C9, shape R: revert UtilitiesCS/HelperClasses/FileSystem/DirectoryInfoWrapper.cs (`git checkout -- UtilitiesCS/HelperClasses/FileSystem/DirectoryInfoWrapper.cs`), rebuild with `TASKID` `p1-t26`, re-run the C9 filter, and append to FEATURE/evidence/regression-testing/mutation-wrapper-getfiles-empty.md. Acceptance: shape R with `TOKEN Array.Empty() = 0` and the SHA-256 equal to `PRE-EDIT-HASH-DIWP:`. + +- [x] [P1-T27] [expect-fail] Control C10, shape M: mutate UtilitiesCS/HelperClasses/FileSystem/DirectoryInfoWrapper.cs line 145 (`EnumerateFileSystemInfos()` returns an empty sequence), run the C10 filter with `NAMES-DIW` and `TASKID` `p1-t27`, and write FEATURE/evidence/regression-testing/mutation-wrapper-enumeratefilesysteminfos-empty.md. Predicted failure: `EnumerateFileSystemInfos_ShouldWrapDirectoriesAndFiles` `Failed` with a `MESSAGE` containing `to contain "UtilitiesCS.Test.dll"`. Acceptance: shape M with `TOKEN Enumerable.Empty() = 1`. + +- [x] [P1-T28] Control C10, shape R: revert UtilitiesCS/HelperClasses/FileSystem/DirectoryInfoWrapper.cs (`git checkout -- UtilitiesCS/HelperClasses/FileSystem/DirectoryInfoWrapper.cs`), rebuild with `TASKID` `p1-t28`, re-run the C10 filter, and append to FEATURE/evidence/regression-testing/mutation-wrapper-enumeratefilesysteminfos-empty.md. Acceptance: shape R with `TOKEN Enumerable.Empty() = 0` and the SHA-256 equal to `PRE-EDIT-HASH-DIWP:`. + +- [x] [P1-T29] [expect-fail] Control C11, shape M: mutate UtilitiesCS/HelperClasses/FileSystem/DirectoryInfoWrapper.cs line 228 (`ToString()` returns the empty string), run the C11 filter with `NAMES-DIW` and `TASKID` `p1-t29`, and write FEATURE/evidence/regression-testing/mutation-wrapper-tostring-empty.md. Predicted failure: `ToString_ShouldDelegateToWrappedDirectoryInfo` `Failed` with a `MESSAGE` containing `"C:\Repo\fixture"`. Acceptance: shape M with `TOKEN return string.Empty; = 1`. + +- [x] [P1-T30] Control C11, shape R: revert UtilitiesCS/HelperClasses/FileSystem/DirectoryInfoWrapper.cs (`git checkout -- UtilitiesCS/HelperClasses/FileSystem/DirectoryInfoWrapper.cs`), rebuild with `TASKID` `p1-t30`, re-run the C11 filter, and append to FEATURE/evidence/regression-testing/mutation-wrapper-tostring-empty.md. Acceptance: shape R with `TOKEN return string.Empty; = 0` and the SHA-256 equal to `PRE-EDIT-HASH-DIWP:`. + +- [x] [P1-T31] Prove the tree is clean after the controls: run `git diff --exit-code HEAD -- UtilitiesCS UtilitiesCS.Test`, `git diff --exit-code ANCHOR-SHA -- UtilitiesCS` (the executor substitutes the P0-T3 `ANCHOR-SHA:` value, Refs), `git status --porcelain -- UtilitiesCS UtilitiesCS.Test`, `git rev-parse origin/main` (recorded as `ORIGIN-MAIN-NOW:`, an observation, not gated; a value differing from `ANCHOR-SHA:` records that a sibling session advanced the shared remote-tracking ref), and the SHA-256 of each of the five files (the two Write Set files and the three production files), and write FEATURE/evidence/regression-testing/p1-t31-post-control-clean-tree..md with `Timestamp:`, `Command:`, `EXIT_CODE:` (the first `git diff --exit-code` exit), `PRODUCTION-DIFF-EXIT:` (the second), `ORIGIN-MAIN-NOW:` and an `Output Summary:` recording the porcelain output verbatim (`EMPTY` when nothing printed) and the five hashes beside their anchors (`FIX-HASH-PFS:`, `FIX-HASH-DIW:`, `PRE-EDIT-HASH-PDA:`, `PRE-EDIT-HASH-PFA:`, `PRE-EDIT-HASH-DIWP:`). Acceptance, all four required: `EXIT_CODE: 0`; `PRODUCTION-DIFF-EXIT: 0` (the working tree of UtilitiesCS/ is byte-identical to the ANCHOR-SHA tree, the origin/main tip P0-T3 reconciled to); the porcelain output is empty; all five hashes equal their anchors. This is the mechanical proof, for AC6, that no temporary production edit and no control residue survives into Phase 2. + +### Phase 2 — Final QC Loop, Coverage Comparison, Footprint, Acceptance Check-Off and Commit + +The loop is P2-T1 through P2-T6 in order, followed by P2-T7 (which, since the coordinator ruling of 2026-09-30, carries the post-merge rebuilds and the gating MEASUREMENT 3) and P2-T8. When P2-T1 rewrites a Write Set file, the loop restarts from P2-T1 and P2-T9 records the iteration count; that formatter output is the only change to a Write Set file this phase admits. A failure of a P2-T7 post-merge rebuild is `POST-MERGE REBUILD NOT CLEAN` (the merged files are not Write Set files): stop and report. 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 2, other than the P2-T1 formatter output, is `WRITE SET CHANGED AFTER CONTROLS`: stop and request a plan revision, because the eleven negative controls of Phase 1 observed the committed P1-T8 state and an edited file would carry no fail-before evidence. + +- [x] [P2-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 `UtilitiesCS.Test/HelperClasses/PhysicalFileSystemAdapters_Tests.cs` and `UtilitiesCS.Test/HelperClasses/DirectoryInfoWrapper_Tests.cs` and the SHA-256 of the text printed by `git diff ANCHOR-SHA -- UtilitiesCS.Test` (the anchored patch against the P0-T3 `ANCHOR-SHA:` value, Refs, because the formatter can rewrite a file the plan already changed without changing a name list), then runs `git status --porcelain -- UtilitiesCS UtilitiesCS.Test`. Write FEATURE/evidence/qa-gates/p2-t1-csharpier-format..md with `Timestamp:`, `ITERATION:`, `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 four file hashes, each labelled with its file and as taken before or after the command (the two after-command values are the per-file after-format hashes P2-T8 and P2-T10 compare against), `REWRITTEN:` (the number of the two 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 the two Write Set paths or nothing (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. Fact 22 predicts `REWRITTEN: 0` because P1-T8 leaves both files at the checkout ending; the prediction is recorded as met or not met by this task's value, never assumed. + +- [x] [P2-T2] Run `dotnet tool run csharpier check .` from the worktree root and write FEATURE/evidence/qa-gates/p2-t2-csharpier-check..md with `Timestamp:`, `ITERATION:`, `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 recorded (0 is expected because this plan creates no file the formatter counts; the delta is an observation, not a gate, because the counter is repository-wide). A non-zero exit restarts the loop from P2-T1. + +- [x] [P2-T3] Run the analyzer gate with `CMD-REBUILD` using the analyzer `GATEARGS` and `TASKID` `p2-t3` and write FEATURE/evidence/qa-gates/p2-t3-msbuild-analyzers..md with the P0-T7 field set plus `ITERATION:` and `WARNINGS-DELTA:` (this run's `WARNINGS:` minus `ANALYZE-BASELINE-WARNINGS:`, an observation). Acceptance, all five required: `EXIT_CODE: 0`; `ERRORS: 0`; `SKIP_CORECOMPILE_LINES: 0`; `UCS_TEST_CSC_OUT_LINES:` and `UCS_CSC_OUT_LINES:` each at least 1; `WRITESET_DIAGNOSTIC_LINES: 0`. A diagnostic naming a Write Set file is not repaired in this phase (`WRITE SET CHANGED AFTER CONTROLS`): stop and request a plan revision quoting the diagnostic; any other failure is reported and stops. + +- [x] [P2-T4] Run the nullable gate with `CMD-REBUILD` using the nullable `GATEARGS` and `TASKID` `p2-t4` (no Nullable property override, no incremental Build target) and write FEATURE/evidence/qa-gates/p2-t4-msbuild-nullable..md with the P0-T8 field set plus `ITERATION:` and `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 P2-T3. + +- [x] [P2-T5] Run the two rewritten classes in the parallel regime as the measured run for AC1 through AC5 with `CMD-VSTEST` using `FILTER-BOTH`, `NAMES-BOTH` and `TASKID` `p2-t5`, and write FEATURE/evidence/regression-testing/test-run-final.md (fixed name) with `Timestamp:`, `ITERATION:`, `Command:` (naming scripts/vscode/TaskMaster.cli.runsettings, the isolation switch and the filter verbatim), `EXIT_CODE:` and an `Output Summary:` recording `RUNSETTINGS-HASH-NOW:`, the `COUNTERS` line, `RESULT_COUNT:`, `SEQUENCE_FILES:`, every `RESULT` line (the fifteen `NAMES-BOTH` names) and every `MESSAGE` line. Acceptance, all five required: `EXIT_CODE: 0`; `total` 15, `executed` 15 and `failed` 0; `SEQUENCE_FILES: 0`; the fifteen `RESULT` lines are present and each reads `Passed`; `RUNSETTINGS-HASH-NOW:` equals `RUNSETTINGS-HASH:`. A failing test is not repaired in this phase (`WRITE SET CHANGED AFTER CONTROLS`): stop and request a plan revision with the `MESSAGE` line. + +- [x] [P2-T6] 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`, started as a background invocation and polled per the Long-running payloads convention, then, unless branch (d0) below applies, `CMD-COVERAGE-POST` with `STAGE` `final`, `RAW` being the value the Command Reference rule derives from the run, recorded as `RAW:`) and write FEATURE/evidence/qa-gates/coverage-final.md (fixed name) with the P0-T11 field set plus `ITERATION:`, `MEASUREMENT: 1` and `NEWLY-FAILING:` (names in `FAILED-SET:` not in the P0-T11 `FAILED-SET:`, or `NONE`); the three `FILE` lines are produced by P2-T7 rather than here. 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) a run with `SEQUENCE_FILES:` greater than 0 (DIRECT) or `TRX_PRESENT: False` is `COVERAGE RUN ABORTED`: stop and report the collector log's last lines with absolute paths replaced, do not run `CMD-COVERAGE-POST`, 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 of either Write Set class is not repaired in this phase (`WRITE SET CHANGED AFTER CONTROLS`): stop and request a plan revision with the `Failed tests:` summary line; a non-empty `FAILED-SET:` whose every name is also in the P0-T11 `FAILED-SET:` records `NEWLY-FAILING: NONE`, `ExpectedExitCode:` equal to the observed exit code, and completes (D-12); a non-empty `FAILED-SET:` naming any other test is `AC8: NOT MET`: stop and report with the `Failed tests:` summary line and do not re-run; (c) a floor failure (a runner `THRESHOLD_MESSAGE:`, or a `LINE-FLOOR: NOT MET` or `BRANCH-FLOOR: NOT MET` line printed by `CMD-COVERAGE-POST`) is `AC8: 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:` equals its declared expectation (0, or the observed non-zero value under branch (b)); under `COVERAGE-ROUTE: DIRECT`, `SEQUENCE_FILES: 0`; the projection block contains the `UtilitiesCS` package element with `LINE` and `BRANCH` counters; the `First-party coverage:` line is present with `FIRST-PARTY-LINE-PERCENT:` at least 80 and `FIRST-PARTY-BRANCH-PERCENT:` at least 75; the summary block's first line begins `Test run outcome:`; `NEWLY-FAILING: NONE`; `LINE-FLOOR: MET` and `BRANCH-FLOOR: MET` as printed by `CMD-COVERAGE-POST`; the artifact contains no absolute path. coverage\final-940.jacoco.xml and coverage\final-940.cobertura.xml remain on disk for P2-T7. + +- [x] [P2-T7] Take the post-merge gating measurement for AC8 (the per-file lines-and-branches rule and the floors, coordinator ruling of 2026-09-30, D-7) and AC4 (D-8) and append its record to FEATURE/evidence/qa-gates/coverage-final.md. The MEASUREMENT 1 and MEASUREMENT 2 sections already in that file, their `### Outcome` section included, stay verbatim as the record of the superseded package-level comparison; this task appends the sections named below, in order, and never edits the text above them; every appended section that records a command carries its own `Timestamp:`, `Command:`, `EXIT_CODE:` and `Output Summary:`. Step (a), ruling record: append `## Coordinator ruling (2026-09-30)` stating that the `PACKAGE` and `REPO-LINE` not-lower comparison and the second-measurement rule were replaced by the per-file rule for the three changed files, covered lines and covered branches, plus the first-party floors, and that MEASUREMENT 3 below is the gating measurement taken on the merged head. Step (b), merge anchor facts, appended under `## Post-merge anchor` from these git spans: `git rev-parse HEAD` (`P2-T7-HEAD:`); `git merge-base --is-ancestor 40e587ce20bbd41cd6915707271faae937f1a8e2 HEAD` (`MERGE-COMMIT-IN-HEAD-EXIT:`, its exit code; 0 means the orchestrator's merge commit of origin/main is in HEAD's history); `git rev-parse 40e587ce20bbd41cd6915707271faae937f1a8e2^2` (`ANCHOR-SHA-2:`, the merge commit's second parent, the merged origin/main tip; Refs); `git merge-base HEAD origin/main` (`MERGE-BASE-NOW:`); `git rev-parse origin/main` (`ORIGIN-MAIN-NOW:`, an observation); `git diff --exit-code ANCHOR-SHA ANCHOR-SHA-2 -- UtilitiesCS UtilitiesCS.Test` (`MERGE-UCS-DIFF-EXIT:`; 0 means the two anchors agree on both source trees, which keeps every `PRE-EDIT-HASH-` and `FIX-HASH-` anchor, the Token Census Expectations and the Target Source C citations valid after the merge); `git diff --name-only ANCHOR-SHA ANCHOR-SHA-2 -- UtilitiesCS TaskMaster.runsettings scripts/vscode/TaskMaster.cli.runsettings coverage.config UtilitiesCS.Test/UtilitiesCS.Test.csproj TaskMaster.Test/Ribbon/RibbonControllerTests.cs TaskMaster.Test/Bootstrap/FSharpCoreHintPathAlignmentTests.cs UtilitiesCS.Test/EmailIntelligence/SortEmail_Tests.cs` (`MERGE-OUT-OF-SET-DELTA:`, verbatim or `NONE`; an observation explaining why P2-T10 anchors its two-dot spans to ANCHOR-SHA-2) with its companion `git status --porcelain -- UtilitiesCS UtilitiesCS.Test` (`SOURCE-PORCELAIN-P2-T7:`, verbatim or `EMPTY`, recorded). Step (c), foreign-process pre-step: run `CMD-FOREIGN-PROCESS-PROBE` and append its output under `## Foreign test-process probe`; while it prints a `FOREIGN-TEST-PROCESSES:` value greater than 0, re-invoke it (the cadence is the executor's; the payload carries no sleep) until it prints `FOREIGN-TEST-PROCESSES: 0`, recording `FOREIGN-PROBE-INVOCATIONS:` and the first and the last probe output verbatim; a probe still reporting a foreign process 120 minutes after the first invocation is `FOREIGN TEST RUN STALLED`: stop and report. No build or test command of this task starts before the last probe printed 0. Step (d), post-merge rebuilds: run `CMD-REBUILD` with the analyzer `GATEARGS` and `TASKID` `p2-t7a`, writing FEATURE/evidence/qa-gates/p2-t7-msbuild-analyzers..md with the P2-T3 field set plus `POST-MERGE: YES`, then `CMD-REBUILD` with the nullable `GATEARGS` and `TASKID` `p2-t7b` (no Nullable property override, no incremental Build target), writing FEATURE/evidence/qa-gates/p2-t7-msbuild-nullable..md with the P2-T4 field set plus `POST-MERGE: YES`; both carry `ITERATION: 1` and are P2-T7 sub-records that P2-T9 quotes as the analyzer and nullable lines of the final clean pass, because they are the latest run of those two commands. A failure of either rebuild is `POST-MERGE REBUILD NOT CLEAN`: stop and report the diagnostics with absolute paths replaced (the merged files are not Write Set files and are not repaired here). Step (e), MEASUREMENT 3: run the route P0-T9 fixed with `STAGE` `final3` (`CMD-COVERAGE-DIRECT` under `DIRECT`, `CMD-COVERAGE-RUNNER` under `RUNNER`, started as a background invocation and polled per the Long-running payloads convention; under `RUNNER` a run still in progress after 120 minutes is `COVERAGE RUN STALLED`: stop and report), then, unless branch (d0) applies, `CMD-COVERAGE-POST` with `STAGE` `final3` and the `RAW` value the Command Reference rule derives, and append `## Post-merge measurement (MEASUREMENT: 3, STAGE final3)` carrying the P2-T6 field set: `Timestamp:`, `MEASUREMENT: 3`, `Command:`, `EXIT_CODE:` (scoped to the collector or runner invocation), `ExpectedExitCode:` only under branch (b), `Output Summary:`, `COVERAGE-ROUTE:`, `RAW:`, `COLLECT_EXIT_CODE:` or `RUNNER_EXIT_CODE:`, `ASSEMBLY_COUNT:` with every `ASSEMBLY:` line or `DISCOVERED_LINE:`, `SEQUENCE_FILES:`, `TRX_PRESENT:`, `LINE-FLOOR:`, `BRANCH-FLOOR:`, the `First-party coverage:` line with `FIRST-PARTY-LINE-PERCENT:` and `FIRST-PARTY-BRANCH-PERCENT:`, `FAILED-SET:`, `NEWLY-FAILING:` (names in `FAILED-SET:` not in the P0-T11 `FAILED-SET:`, or `NONE`), the five summary lines between `SUMMARY-BEGIN` and `SUMMARY-END`, and the projection between `PROJECTION-BEGIN` and `PROJECTION-END`. Branches are exactly the P2-T6 branches (d0), (b), (c), (a), (d) with their stop conditions, with one difference: no branch re-runs the measurement. Step (f), comparisons: run `CMD-PACKAGE-COMPARE` with `STAGE` `final3` (observational), `CMD-FILE-COMPARE` with `STAGE` `final3` (the gate), `CMD-FILE-COMPARE` with `STAGE` `final` and again with `STAGE` `final2` (observational re-reads of the two documents still on disk, supplying the branch columns MEASUREMENT 1 and 2 did not record), and `CMD-UNTOUCHED-LINES` once, and append `## Comparison against coverage-baseline.md (MEASUREMENT: 3, STAGE final3)` carrying: the two `PACKAGE` lines and the `REPO-LINE` line for `final3` verbatim (observations; no acceptance reads their `NOT-LOWER=` field); the three `FILE` lines for `final3` verbatim (the gate lines, shape `FILE LINES baseline=/ final=/ BRANCHES baseline=/ final=/ NOT-LOWER=`); the three `FILE` lines for `final` and the three for `final2` verbatim under `OBSERVATIONAL-FILE-LINES:`; every `UNTOUCHED` line verbatim; `BASELINE-FIRST-PARTY:` and `FINAL-FIRST-PARTY:` (each must equal, figure for figure, the `First-party coverage:` line coverage-baseline.md or the MEASUREMENT 3 section transcribed from `CMD-COVERAGE-POST`, otherwise `FIRST-PARTY LINE MISMATCH`: stop and report); exactly one `COMPARABILITY-MEASUREMENT-3:` 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 totals were measured over different merged denominators; an observation, because the floors are read from the MEASUREMENT 3 line alone); and `CHANGED-PRODUCTION-LINES: 0` with the sentence that the P1-T8 `git show` path list contains no path under UtilitiesCS/, that P1-T31 recorded `PRODUCTION-DIFF-EXIT: 0` and that this task recorded `MERGE-UCS-DIFF-EXIT: 0`, so every changed line of this item is test code outside the coverage denominator and the policy's no-regression-on-changed-lines clause is discharged by the per-file rule over the three files whose coverage the rewritten tests carry. Step (g), negative control: run `CMD-FILE-COMPARE-CONTROL` with `STAGE` `final3` and append `## Per-file comparator negative control (in memory)` carrying its output verbatim: per file one `CONTROL-SELF`, one `CONTROL-LINE` and one `CONTROL-BRANCH` `FILE` line, plus `CONTROL-DOC-HASH-BEFORE:` and `CONTROL-DOC-HASH-AFTER:`. Step (h), variance table: append `## Two-measurement variance table (observation, not a gate)` as a Markdown table with the rows `baseline`, `measurement 1 (final)`, `measurement 2 (final2)` and `measurement 3 (final3, post-merge)` and the ten figure columns `UtilitiesCS lines`, `UtilitiesCS branches`, `first-party lines`, `first-party branches`, then `lines` and `branches` for each of PhysicalDirectoryInfoAdapter.cs, PhysicalFileInfoAdapter.cs and DirectoryInfoWrapper.cs, every cell `/` transcribed from the `PACKAGE`, `First-party coverage:` and `FILE` lines of this artifact and of coverage-baseline.md (the baseline row from coverage-baseline.md and from the `baseline=` halves of the `FILE` lines), followed by a second table with the rows `Interfaces\IWinForm\PropertyStore.cs`, `OutlookObjects\Table\OlTableExtensions.Etl.cs` and `EmailIntelligence\SubjectMap\SubjectMapSco.Orchestration.cs` and one `covered=` column per stage transcribed from the `UNTOUCHED` lines. Acceptance, all ten required: (1) `MERGE-COMMIT-IN-HEAD-EXIT: 0`, `MERGE-BASE-NOW:` equals `ANCHOR-SHA-2:` (a 40-character hexadecimal value), and `MERGE-UCS-DIFF-EXIT: 0` (a non-zero value is `CITED TREE ADVANCED`: stop and report the path list); (2) the last probe output before step (d) reads `FOREIGN-TEST-PROCESSES: 0` and `FOREIGN-PROBE-INVOCATIONS:` is recorded; (3) both post-merge rebuild records read `EXIT_CODE: 0`, `ERRORS: 0`, `SKIP_CORECOMPILE_LINES: 0`, both `_CSC_OUT_LINES:` at least 1 and `WRITESET_DIAGNOSTIC_LINES: 0`; (4) the MEASUREMENT 3 section reads `EXIT_CODE:` equal to its declared expectation, `SEQUENCE_FILES: 0` under `DIRECT`, `NEWLY-FAILING: NONE`, `LINE-FLOOR: MET`, `BRANCH-FLOOR: MET`, `FIRST-PARTY-LINE-PERCENT:` at least 80 and `FIRST-PARTY-BRANCH-PERCENT:` at least 75, a projection block containing the `UtilitiesCS` package with `LINE` and `BRANCH` counters, and a summary block whose first line begins `Test run outcome:`; (5) the three `FILE` lines for `final3` each read `NOT-LOWER=True` with non-zero LINES and BRANCHES denominators on both sides (a `FILE` line reading `NOT-LOWER=False` is `AC8: NOT MET`: stop and report with the three `FILE` lines and the variance tables quoted, and run no further measurement; a baseline LINES denominator of 0 is `FILE NOT INSTRUMENTED`: stop and report); (6) `BASELINE-FIRST-PARTY:` and `FINAL-FIRST-PARTY:` equal their transcribed lines and exactly one `COMPARABILITY-MEASUREMENT-3:` line is present; (7) `CHANGED-PRODUCTION-LINES: 0` is present with its three citations; (8) the control section reads `NOT-LOWER=True` on all three `CONTROL-SELF` lines and `NOT-LOWER=False` on all three `CONTROL-LINE` lines and all three `CONTROL-BRANCH` lines (any other value is `CONTROL DID NOT FAIL`: stop and report, because the comparator is then not shown able to fail), and `CONTROL-DOC-HASH-BEFORE:` equals `CONTROL-DOC-HASH-AFTER:`; (9) both variance tables are present, the first with four rows and ten figure columns and the second with three rows and one column per stage, every cell equal to the line it is transcribed from; (10) the appended text contains no absolute path. + +- [x] [P2-T8] Audit file size and the token table after the repository-wide format by running `CMD-CENSUS` on `UtilitiesCS.Test/HelperClasses/PhysicalFileSystemAdapters_Tests.cs` and `UtilitiesCS.Test/HelperClasses/DirectoryInfoWrapper_Tests.cs`, and write FEATURE/evidence/qa-gates/p2-t8-post-format-census..md transcribing every `TOKEN`, `LINES` and `SHA256` line per file. Acceptance, all four required: both `LINES` values are at most 500 (a larger value is `FILE SIZE LIMIT EXCEEDED`: stop and report rather than shortening comments); every `post` value of the Token Census Expectations table holds under its gate column, in particular for both files `GetRepositoryRoot` 0, `GetSolutionFile` 0, `TaskMaster.sln` 0, `AppDomain` 0, `catch` 0 and the nine determinism tokens 0, and `Assembly.Location` at least 1; the `new DirectoryInfo(` and `new FileInfo(` equalities hold for both files, with `Exists.Should().BeFalse()` at least 4 and `MissingOwnedPath(` at least 4 in PFS and `RootedFixturePath` 3 in DIW; each `SHA256` equals the after-format hash that the final P2-T1 iteration recorded for that file, and when every P2-T1 iteration of this run recorded `REWRITTEN: 0` that value also equals the file's `FIX-HASH-` anchor. + +- [x] [P2-T9] Close the toolchain loop and write FEATURE/evidence/qa-gates/toolchain-pass.md (fixed name) with `Timestamp:`, `Command:` (`reconciliation of P2-T1 through P2-T8`), `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 format .` (P2-T1) with its `REWRITTEN:` value, `dotnet tool run csharpier check .` (P2-T2), the analyzer rebuild (P2-T3 and its post-merge re-run, the P2-T7 record p2-t7-msbuild-analyzers..md, whose values are the ones quoted) with its `SKIP_CORECOMPILE_LINES:` value, the nullable rebuild (P2-T4 and its post-merge re-run, the P2-T7 record p2-t7-msbuild-nullable..md, quoted likewise) with its `SKIP_CORECOMPILE_LINES:` value, and the MSTest-with-coverage route (P2-T6 MEASUREMENT 1 and the gating post-merge MEASUREMENT 3 of P2-T7, whose values are the ones quoted) with `COVERAGE-ROUTE:` and its declared expectation; then the scoped run (P2-T5); `ITERATIONS:` (the number of times the loop started from P2-T1); one `EXPECTATION-MET:` line per task P2-T1 through P2-T8; and `LOOP: CLEAN PASS`. Clean means every step met its own declared expectation (P2-T6 and the P2-T7 MEASUREMENT 3 may declare a non-zero expectation under D-12 branch (b)). Acceptance, all four required: the five toolchain lines each record an exit code equal to the step's declared expectation, the analyzer, nullable and coverage lines naming the P2-T7 post-merge records they quote; both `SKIP_CORECOMPILE_LINES:` values are 0; eight `EXPECTATION-MET:` lines each read `YES`; `LOOP: CLEAN PASS` is present. If any expectation is not met because P2-T1 rewrote a file, the loop restarts from P2-T1 and this task is re-run after it; any other unmet expectation is the stop condition its task names; nothing is written until every expectation holds. + +- [x] [P2-T10] Verify the footprint and scope boundary (AC3, AC5, AC7 and the no-production-change invariant) and write FEATURE/evidence/qa-gates/p2-t10-scope-boundary..md. Run: `git diff --name-only origin/main...HEAD` (committed); `git status --porcelain --untracked-files=all` (its companion); `git diff --name-only origin/main...HEAD -- UtilitiesCS.Test`; `git diff --name-only origin/main...HEAD -- .claude`; `git diff --name-only origin/main...HEAD -- .claude config` (the committed diff of the two trees the executor never commits to); `git ls-files --error-unmatch -- UtilitiesCS TaskMaster.runsettings scripts/vscode/TaskMaster.cli.runsettings coverage.config UtilitiesCS.Test/UtilitiesCS.Test.csproj TaskMaster.Test/Ribbon/RibbonControllerTests.cs TaskMaster.Test/Bootstrap/FSharpCoreHintPathAlignmentTests.cs UtilitiesCS.Test/EmailIntelligence/SortEmail_Tests.cs UtilitiesCS/HelperClasses/FileSystem/PhysicalDirectoryInfoAdapter.cs UtilitiesCS/HelperClasses/FileSystem/PhysicalFileInfoAdapter.cs UtilitiesCS/HelperClasses/FileSystem/DirectoryInfoWrapper.cs` (recorded as `PATHSPEC-MATCH-EXIT:`; every pathspec the two `--exit-code` gates below name must match at least one tracked file, because a pathspec that matches nothing makes an `--exit-code` diff pass vacuously); `git diff --exit-code ANCHOR-SHA-2 -- UtilitiesCS TaskMaster.runsettings scripts/vscode/TaskMaster.cli.runsettings coverage.config UtilitiesCS.Test/UtilitiesCS.Test.csproj TaskMaster.Test/Ribbon/RibbonControllerTests.cs TaskMaster.Test/Bootstrap/FSharpCoreHintPathAlignmentTests.cs UtilitiesCS.Test/EmailIntelligence/SortEmail_Tests.cs` (working tree against the P2-T7 `ANCHOR-SHA-2:` value, the merged origin/main tip, Refs; anchored there rather than at ANCHOR-SHA because the merge may have changed one of these out-of-set paths on main's side, which `MERGE-OUT-OF-SET-DELTA:` of P2-T7 records; recorded as `OUT-OF-SET-DIFF-EXIT:`); the three directive-form spans `git diff --exit-code ANCHOR-SHA-2 -- UtilitiesCS/HelperClasses/FileSystem/PhysicalDirectoryInfoAdapter.cs`, `git diff --exit-code ANCHOR-SHA-2 -- UtilitiesCS/HelperClasses/FileSystem/PhysicalFileInfoAdapter.cs` and `git diff --exit-code ANCHOR-SHA-2 -- UtilitiesCS/HelperClasses/FileSystem/DirectoryInfoWrapper.cs` (recorded as `PDA-DIFF-EXIT:`, `PFA-DIFF-EXIT:`, `DIWP-DIFF-EXIT:`); `git rev-parse origin/main` (recorded as `ORIGIN-MAIN-NOW:`, an observation, not gated); `git merge-base HEAD origin/main` (recorded as `MERGE-BASE-NOW:`); `CMD-ADDED-LINES` (its `git diff -U0 ANCHOR-SHA-2` span takes the same substitution); the SHA-256 of the five files; and `RUNSETTINGS-HASH-NOW:`. Record the two full path lists verbatim; `INHERITED-AND-EXCLUDED:` (the paths removed by Clause A and Clause B, each listed with its clause; the Clause A set is the P0-T3 record unchanged, because the orchestrator's merge of origin/main adds nothing to the three-dot list: that range is anchored at the merge base, which `MERGE-BASE-NOW:` below shows is the merged origin/main tip, so every path main gained through the merge lies on the excluded side of the range and cannot enter the footprint); `THIS-ITEM-FOOTPRINT:` (the union of the first two lists minus those); `UCS-TEST-CHANGED:` (the third list); `CLAUDE-CHANGED:` (the fourth list); `COMMITTED-CLAUDE-OR-CONFIG-RAW:` (the fifth list verbatim) and `COMMITTED-CLAUDE-OR-CONFIG:` (the fifth list minus every path in `INHERITED-CLAUSE-A:` and every path beginning .claude/agent-memory/ (Clause B), or `NONE`); `INHERITED-PROMOTION-RECORD:` (as P0-T3 recorded it); `ORIGIN-MAIN-NOW:`; `MERGE-BASE-NOW:`; `PATHSPEC-MATCH-EXIT:`; the eleven `ADDED-` counts transcribed from `CMD-ADDED-LINES`; and the five hashes beside their anchors. Acceptance, all twelve required: `PATHSPEC-MATCH-EXIT: 0`; `MERGE-BASE-NOW:` equals the P2-T7 `ANCHOR-SHA-2:` value (a differing value is `MERGE BASE MOVED`: stop and report, because the three-dot lists are then anchored at a tree this plan did not verify); `THIS-ITEM-FOOTPRINT:` is exactly the two Write Set code paths plus paths under docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/ and nothing else; `UCS-TEST-CHANGED:` is exactly the two Write Set paths (so neither UtilitiesCS.Test/HelperClasses/FileInfoWrapper_Tests.cs nor UtilitiesCS.Test/EmailIntelligence/SortEmail_Tests.cs nor the project file changed); every path in `CLAUDE-CHANGED:` begins .claude/agent-memory/ (Clause B); `COMMITTED-CLAUDE-OR-CONFIG: NONE`; `OUT-OF-SET-DIFF-EXIT: 0`; `PDA-DIFF-EXIT: 0`, `PFA-DIFF-EXIT: 0` and `DIWP-DIFF-EXIT: 0` (the three production files are byte-identical to the ANCHOR-SHA-2 tree in the working tree, which by P2-T7's `MERGE-UCS-DIFF-EXIT: 0` is also the ANCHOR-SHA tree for these paths); the ten `ADDED-` counts other than `ADDED-LINE-COUNT:` are all 0 over an `ADDED-LINE-COUNT:` of at least 1; the three production hashes equal their `PRE-EDIT-HASH-` anchors and the two test-file hashes equal the P2-T1 after-format hashes; `RUNSETTINGS-HASH-NOW:` equals `RUNSETTINGS-HASH:`; the porcelain span is present as the name-listing diff's companion. + +- [x] [P2-T11] Sweep the feature folder, this plan included, for host identifiers and raw documents by running `CMD-SWEEP` and write FEATURE/evidence/qa-gates/p2-t11-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:`, `HYGIENE-PROFILE-PATTERN-MATCHES:` and `RAW-DOCUMENT-FILES:`; the tokens themselves are never written. Also run `git diff --name-only origin/main...HEAD` 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` (the three-dot range is anchored at the merged origin/main tip P2-T7 recorded as `ANCHOR-SHA-2:`, so a path main gained through the merge lies outside it). Acceptance, all three required: the six match counts (`ACCOUNT-TOKEN-MATCHES:`, `PROFILE-LEAF-MATCHES:`, `MACHINE-TOKEN-MATCHES:`, `WORKTREE-ROOT-MATCHES:`, `USERS-PATH-MATCHES:`, `HYGIENE-PROFILE-PATTERN-MATCHES:`) 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. + +Check-off protocol for P2-T12 through P2-T19: each task changes exactly one line of `docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/issue.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/p2-t-ac-checkoff..md; the task completes in either case. + +- [x] [P2-T12] Check off AC1 in FEATURE/issue.md. Evidence: P2-T8 census of both files (`GetRepositoryRoot` 0, `GetSolutionFile` 0, `TaskMaster.sln` 0, `AppDomain` 0) and P2-T5 (the fifteen tests Passed). + +- [x] [P2-T13] Check off AC2 in FEATURE/issue.md. Evidence: P2-T8 census of both files (`catch` 0, down from 2 in PFS at P0-T12) and P2-T10 (`ADDED-CATCH: 0`). + +- [x] [P2-T14] Check off AC3 in FEATURE/issue.md. Evidence: P2-T8 census (in PFS every `new DirectoryInfo(` is a `new DirectoryInfo(MissingOwnedPath(` construction and every `new FileInfo(` is a `new FileInfo(MissingOwnedPath(` or `new FileInfo(typeof(` construction, `Exists.Should().BeFalse()` at least 4, `MissingOwnedPath(` at least 4, `__940_missing_` 1, `GetRepositoryRoot` 0 and `TaskMaster.sln` 0; in DIW every `new DirectoryInfo(` is a `new DirectoryInfo(RootedFixturePath)` construction and the only `new FileInfo(` is the owned image), P2-T10 (`ADDED-ROOTWALK: 0`, `ADDED-TEMP: 0`) and P2-T5 (the four missing-path tests Passed, so every precondition held). In addition to the check-off protocol, this task always writes FEATURE/evidence/qa-gates/p2-t14-ac3-mutating-call-inventory..md with `Timestamp:`, `Command:` (`Read tool over the two formatted Write Set files; no command executed`), `EXIT_CODE: 0` (scoped to the read-only derivation), `MUTATING-CALL-INVENTORY:` followed by one `CALL: | | ` line per inventoried call site of both files (AC3 Mutating-Call Inventory section), `PFS-MUTATING-CALLS:`, `DIW-MUTATING-CALLS:`, one `CLASS-COUNT = ` line per class over PFS, and `NON-INVENTORIED:` (the four seam-routed write-mode opens). Acceptance for the inventory, all four required: `PFS-MUTATING-CALLS: 34` with `CLASS-COUNT` values `owned output directory` 4, `its parent` 2, `existing owned entry` 2, `missing owned path` 26 and `mock` 0; every DIW `CALL:` line has class `mock` and a method among the three Moq-based names, with `DIW-MUTATING-CALLS:` at least 7; every class is one of the five tokens; no `CALL:` line names the repository root, a tracked repository path or `TaskMaster.sln`. A deviation is `AC3: NOT MET`, recorded per the protocol. + +- [x] [P2-T15] Check off AC4 in FEATURE/issue.md. Evidence: P2-T8 census (every `at least` member token of the Token Census Expectations table meets its bound in its file, `.MemberCount` 2), P2-T7 (the three `FILE` lines of MEASUREMENT 3 `NOT-LOWER=True`, covered lines and covered branches, D-8) and P2-T5 (the fifteen tests Passed, which for the `no-op-no-throw` rows is the no-exception evidence AC4 requires). In addition to the check-off protocol, this task always writes FEATURE/evidence/qa-gates/p2-t15-ac4-form..md with `Timestamp:`, `Command:` (`Read tool over the two formatted Write Set files and test-run-final.md; no command executed`), `EXIT_CODE: 0`, and one `FORM: | | | ` line per row of the AC4 Member Forms table (fifteen), each followed by an `EVIDENCE:` line naming the test's `RESULT = Passed` line of test-run-final.md and at least one P2-T8 census token for the group (for the two `no-op-no-throw` rows the `.Create(`, `.CreateSubdirectory(`, `.Refresh()` and `.SetAccessControl(` counts; for the `GetObjectData` members the `.MemberCount` count). Acceptance for the forms, all four required: exactly fifteen `FORM:` lines; every form is one of the four tokens `Moq`, `owned read-only fixture`, `non-existent-path outcome`, `no-op-no-throw`; every named test reads `Passed` in test-run-final.md; the members AC4 names for the no-op clause (`Create`, `Refresh`, `SetAccessControl`) each appear in a `no-op-no-throw` row. A deviation is `AC4: NOT MET`, recorded per the protocol. + +- [x] [P2-T16] Check off AC5 in FEATURE/issue.md. Evidence: P2-T8 census (`Thread.Sleep`, `Task.Delay`, `[Timeout`, `DoNotParallelize`, `Retry`, `Path.GetTemp`, `File.Create`, `File.WriteAll`, `Directory.CreateDirectory` 0 in both files) and P2-T10 (`ADDED-DONOTPARALLELIZE`, `ADDED-THREAD-SLEEP`, `ADDED-TASK-DELAY`, `ADDED-TIMEOUT`, `ADDED-RETRY`, `ADDED-WORKERS`, `ADDED-SCOPE` and `ADDED-TEMP` all 0; `OUT-OF-SET-DIFF-EXIT: 0`, so neither runsettings file changed). + +- [x] [P2-T17] Check off AC6 in FEATURE/issue.md. Evidence: the eleven FEATURE/evidence/regression-testing/mutation-*.md records of the Control Table (each with a failing mutated run carrying the predicted phrase, the mutated file and line, the filter, the runsettings name and the isolation switch, a revert with `REVERT-DIFF-EXIT: 0`, empty porcelain and the anchor hash, and a confirming pass) and P1-T31 (no control residue before the final QC pass). + +- [x] [P2-T18] Check off AC7 in FEATURE/issue.md. Evidence: FEATURE/evidence/other/root-walk-site-classification..md (P1-T1: four `SITE:` sections covering the three named files, each with a `CLASS:` token and `MODIFIED: NONE`) and P2-T10 (`OUT-OF-SET-DIFF-EXIT: 0`, whose pathspec names the three files, and `UCS-TEST-CHANGED:` excluding SortEmail_Tests.cs). + +- [x] [P2-T19] Check off AC8 in FEATURE/issue.md (the AC8 text as amended on 2026-09-30, D-7). Evidence: FEATURE/evidence/qa-gates/toolchain-pass.md (P2-T9: `LOOP: CLEAN PASS`, every step at its declared expectation, both `SKIP_CORECOMPILE_LINES:` 0), FEATURE/evidence/qa-gates/p2-t7-msbuild-analyzers..md and p2-t7-msbuild-nullable..md (the post-merge rebuilds clean), FEATURE/evidence/qa-gates/coverage-final.md MEASUREMENT 3 section (P2-T7: `NEWLY-FAILING: NONE`, both floors `MET`, `FIRST-PARTY-LINE-PERCENT:` at least 80 and `FIRST-PARTY-BRANCH-PERCENT:` at least 75) and its MEASUREMENT 3 comparison section (P2-T7: the three `FILE` lines for `final3` `NOT-LOWER=True`, covered lines and covered branches, which is the amended AC8 clause) with the negative-control section reading `NOT-LOWER=False` on every `CONTROL-LINE` and `CONTROL-BRANCH` line. The `PACKAGE` and `REPO-LINE` lines are not evidence for this check-off. + +- [x] [P2-T20] 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; run `git diff --cached --name-only` (`PRE-STAGED:`, verbatim or `NONE`) and `git status --porcelain -- docs/features/potential` (`POTENTIAL-PORCELAIN:`, `EMPTY` when nothing printed); (2) run `git add -- UtilitiesCS.Test docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940`; then run one payload whose statements after the `Set-Location` are `New-Item -ItemType Directory -Path "coverage\logs" -Force | Out-Null`, `$global:LASTEXITCODE = 0`, `& pwsh -NoProfile -File (Join-Path (Get-Location).Path "scripts\hygiene\Test-RepositoryHygiene.ps1") 2>&1 | Tee-Object -FilePath "coverage\logs\p2-t20.hygiene.log"` and `Write-Output ("HYGIENE_GUARD_EXIT: " + $LASTEXITCODE)` (the guard enumerates the index through `git ls-files`, so the paths just staged are in its scope and their working-tree content is what it reads, fact 16), recording `HYGIENE_GUARD_EXIT:`, the `HYGIENE Findings=` line verbatim, `HYGIENE-ITEM-FINDINGS:` (every `HYGIENE raw-document`, `HYGIENE profile-path` or `HYGIENE unreadable` line whose path begins docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/ or is one of the two Write Set paths, or `NONE`) and `HYGIENE-INHERITED-FINDINGS:` (every other finding line, or `NONE`; such a line names a tracked file this item did not change and is reported to the orchestrator in the final message, not repaired here); a `HYGIENE-ITEM-FINDINGS:` other than `NONE` is `HYGIENE GUARD FINDINGS`: stop and report before committing (the staged evidence is not committed); then run `git commit -m "docs(940): evidence, acceptance check-off and plan state for the file-system wrapper test fix" -- UtilitiesCS.Test docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940` (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; the pathspecs confine the commit to those trees whatever `PRE-STAGED:` listed), recording `COMMIT-1-EXIT:`; (3) write FEATURE/evidence/qa-gates/p2-t20-closure..md with `Timestamp:`, `Command:` (the git spans and the guard payload of this task), `PRE-STAGED:`, `POTENTIAL-PORCELAIN:`, `HYGIENE_GUARD_EXIT:`, the `HYGIENE Findings=` line, `HYGIENE-ITEM-FINDINGS:`, `HYGIENE-INHERITED-FINDINGS:`, `COMMIT-1-EXIT:`, `HEAD-AFTER-COMMIT:` (`git rev-parse HEAD`), the confirming footprint list `git diff --name-only origin/main...HEAD -- UtilitiesCS UtilitiesCS.Test` (it must list exactly the two Write Set code paths; the step-2 `git add` span is its companion), `git diff --exit-code origin/main...HEAD -- UtilitiesCS` (recorded as `COMMITTED-PRODUCTION-DIFF-EXIT:`), `git merge-base HEAD origin/main` (recorded as `MERGE-BASE-NOW:`; it must equal the P2-T7 `ANCHOR-SHA-2:` value, so that the two three-dot spans above are anchored at the merged origin/main tip and a merged sibling path cannot appear in the footprint list; a differing value is `MERGE BASE MOVED`: stop and report), the pointer to the P1-T1 classification record for the orchestrator's follow-up decision, and the acceptance-criteria status summary in the exact form the acceptance-criteria-tracking skill requires (`Source:` FEATURE/issue.md, `Total AC items: 8`, `Checked off (delivered): ` counted from the `- [x] AC` lines on disk, `Remaining (unchecked): <8 minus n>`, `Items remaining:` listing any unchecked criterion text or `none`); (4) tick every remaining unchecked checkbox in `docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/plan.2026-09-29T23-02.md`, including this task's own; (5) run `git add -- docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940` then `git commit --amend --no-edit -- docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940`, recording `COMMIT-2-EXIT:` in the executor's final message (the artifact cannot record it); (6) run `git status --porcelain -- UtilitiesCS UtilitiesCS.Test docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940` 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 value, which the step-8 amend supersedes) to the closure record; (8) run `git add -- docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940` then `git commit --amend --no-edit -- docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940` 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. Paths under .claude/agent-memory/ are outside every span above by pathspec and are not this task's residual. Acceptance, all eight required: `COMMIT-1-EXIT: 0`; `MERGE-BASE-NOW:` equals the P2-T7 `ANCHOR-SHA-2:` value; `POTENTIAL-PORCELAIN: EMPTY`; `HYGIENE-ITEM-FINDINGS: NONE`; the confirming footprint list is exactly the two Write Set code paths and `COMMITTED-PRODUCTION-DIFF-EXIT: 0`; the status summary reports `Total AC items: 8` and its `Checked off` count equals the number of `- [x] AC` lines in FEATURE/issue.md; `POST-AMEND-PORCELAIN: EMPTY`; the executor's final message reports the plan path, `FINAL-HEAD:`, `COMMIT-2-EXIT:`, `COMMIT-3-EXIT:`, the status summary and the P1-T1 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-29-filesystem-wrapper-tests-open-repository-solution-file-940/policy-audit.2026-09-30T12-00.md b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/policy-audit.2026-09-30T12-00.md new file mode 100644 index 000000000..e0500c2fc --- /dev/null +++ b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/policy-audit.2026-09-30T12-00.md @@ -0,0 +1,256 @@ +# Policy Compliance Audit: filesystem-wrapper-tests-open-repository-solution-file (Issue #940) + +- Component: `UtilitiesCS.Test/HelperClasses/PhysicalFileSystemAdapters_Tests.cs`, `UtilitiesCS.Test/HelperClasses/DirectoryInfoWrapper_Tests.cs` (test-only change; no production file changed) +- Branch: `bug/filesystem-wrapper-tests-open-repository-solution-file-940` +- Head under review: `bd71b8160e281054657280af8a7c54eeffe5c563` (read from the worktree's loose ref) +- Base: `origin/main` at `66afa6372fd82fc1ffd7c81f85a1ad65eebc5817`, merged into the branch at `40e587ce20bbd41cd6915707271faae937f1a8e2` (the merge changed no path under `UtilitiesCS/` or `UtilitiesCS.Test/`; P2-T7 `MERGE-UCS-DIFF-EXIT: 0`) +- Work mode: `minor-audit` (issue.md line 12); AC source is the `## Acceptance Criteria` section of issue.md only +- Review timestamp label: `2026-09-30T12-00` (assigned by the caller; later than every executor label, the last of which is `11-33`; the final commit's reflog epoch `1790782454` decodes to 2026-09-30T15:34:14Z, 11:34:14 local at UTC-4) +- Reviewer tooling: Read, Grep and Glob only; no shell was used in this review (caller instruction). Every git fact below is read from the worktree's git files (`HEAD`, the loose ref, `logs/HEAD`) and from the executor's verbatim `git diff --name-only origin/main...HEAD` listing in `evidence/qa-gates/p2-t10-scope-boundary.2026-09-30T11-27.md`. +- Blocking findings: 0. Non-blocking findings: 7 (CR-1 to CR-7 in `code-review.2026-09-30T12-00.md`). Gaps and exceptions: 3 (section 8). + +## Executive Summary + +- Overall verdict: **PASS**. Blocking count: **0**. +- The branch rewrites two MSTest classes so that no test locates the repository root, references `TaskMaster.sln`, catches `IOException`, creates a temporary file, or mutates a tracked repository entry. Every mutating call is directed at a Moq mock, at a path under the test assembly's own output directory that is asserted absent beforehand, or at an owned entry on which the call is a no-op by construction. Verified by reading both files in full and by the executor's token census (`evidence/qa-gates/p2-t8-post-format-census.2026-09-30T11-25.md`). +- C# is the only language with changed files. C# coverage verdict: **PASS**. First-party coverage on the merged head (MEASUREMENT 3): 85.33% lines (56092/65736) and 79.73% branches (13597/17054), against a baseline of 85.32% lines (56084/65736) and 79.73% branches (13597/17054). The three production files whose coverage the rewritten tests carry read not-lower on covered lines and covered branches; the reviewer re-read the class nodes of the two Cobertura documents on disk and confirmed the executor's figures. +- The C# toolchain passed in order: CSharpier check exit 0 (P2-T2, 1625 files), analyzer rebuild exit 0 with `SKIP_CORECOMPILE_LINES: 0` (post-merge P2-T7a), TreatWarningsAsErrors rebuild exit 0 (post-merge P2-T7b), MSTest with coverage 7327/7327 passed (MEASUREMENT 3). The CSharpier check was not re-run after the merge; the two changed files are byte-identical to the checked state (hashes equal), and the merged files arrive from origin/main whose CI format check runs on the PR. Recorded as a non-blocking gap (G-1). +- Eleven negative controls (one production-file mutation per rewritten test) each produced the predicted failing assertion and were reverted to the anchored hash; the pre-final tree was shown clean (P1-T31). +- AC8 is evaluated as amended by the coordinator ruling of 2026-09-30 (per-file lines-and-branches no-regression plus first-party floors). The amendment is treated as authorized per the caller's instruction and is recorded in issue.md line 73 and the plan's Revision Log entry A. + +## Rejected Scope Narrowing + +No scope narrowing was detected in the caller prompt. The following caller statements were examined and found to be accurate descriptions of the branch diff rather than narrowing: + +- "Code under review (the only source files changed against origin/main): `UtilitiesCS.Test/HelperClasses/PhysicalFileSystemAdapters_Tests.cs` and `UtilitiesCS.Test/HelperClasses/DirectoryInfoWrapper_Tests.cs`" - confirmed against the executor's verbatim `git diff --name-only origin/main...HEAD` list (46 paths: the two `.cs` files, 43 paths under the feature folder, one path under `docs/features/potential/promoted/`). +- "This item writes no PowerShell file, so no PowerShell gate applies" - confirmed: zero `.ps1`, `.psm1` or `.psd1` paths in the diff. +- "Reduced (minor-audit) review" - this is the work mode recorded in issue.md, not a diff narrowing; the audit scope remains the full branch diff against the resolved base. + +The full diff (all 46 paths) was audited: the two C# test files for policy compliance; the 43 feature-folder documents for evidence-location, hygiene and raw-document compliance; the promoted potential record as inherited content. + +## Evidence Location Compliance + +- Files in the branch diff under `artifacts/baselines/`, `artifacts/qa/`, `artifacts/evidence/` or `artifacts/coverage/`: **none** (0 of 46 diff paths begin with `artifacts/`; `artifacts/` is also gitignored in this repository, so no such path can be tracked). +- Every evidence artifact of this item lives under `docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/evidence//` with `` in `baseline`, `regression-testing`, `qa-gates`, `other` (41 artifacts; each carries `Timestamp:`, `Command:`, `EXIT_CODE:` and `Output Summary:`; spot-checked on 30 of them). +- `validate_evidence_locations.py --root .` was not executed (no shell in this review); the manual inspection of the verbatim diff list is the substitute and found zero violations. +- `EVIDENCE_LOCATION_OVERRIDE_REJECTED`: none; the caller supplied no non-canonical evidence path. +- Raw tool documents: none committed. `evidence/qa-gates/p2-t11-hygiene-sweep.2026-09-30T11-29.md` records `RAW-DOCUMENT-FILES: 0` and `RAW-EXTENSION-PATHS: NONE`; the Cobertura and TRX documents remain under the git-ignored `coverage/` tree. The committed coverage evidence is the JaCoCo package projection plus the one-line first-party summary plus the TRX-derived summary, which is the form CLAUDE.md "Committed Test Evidence Format" requires. + +## 1. General Unit Test Policy Compliance + +### 1.1 Core Principles (UT1) + +| Principle | Verdict | Evidence | +| --- | --- | --- | +| Independence | PASS | Fixtures are the running host's own loaded assembly image (`Assembly.Location`), its directory and its parent; missing paths carry a `__940_missing_` prefix and are asserted absent before every call. No test writes state another test reads. | +| Isolation | PASS (with note CR-2) | Each test targets one wrapper or adapter type; several tests assert many members in one method (pre-existing shape); FluentAssertions messages name the failing member expression. | +| Fast execution | PASS | Scoped run of the fifteen tests: `Test Run Successful`, no hang document (`test-run-final.md`); full suite collector `Total time: 54.7679 Seconds` (MEASUREMENT 3). | +| Determinism | PASS | Four full-suite runs (baseline, final, final2, final3) each passed 7323/7326/7326/7327 with `FAILED-SET: (empty)`; the fifteen scoped tests passed under the parallel regime (Workers 0, Scope ClassLevel) at P1-T7 and P2-T5. No `Thread.Sleep`, `Task.Delay`, `[Timeout`, `DoNotParallelize` or retry token in either file (P2-T8 census; confirmed by reading). | +| Readability | PASS | Descriptive names; each fixture block and each non-obvious act carries a why-comment. | + +### 1.2 Coverage and Scenarios (UT2) + +### Coverage Evidence Checklist + +- C# baseline coverage artifact: `coverage/baseline-940.cobertura.xml` (git-ignored, on disk in the item worktree; root epoch 1790767237 = 2026-09-30T11:20:37Z) with its committed projection `evidence/baseline/coverage-baseline.md` +- C# post-change coverage artifact: `coverage/final3-940.cobertura.xml` (git-ignored, on disk; root epoch 1790781715 = 2026-09-30T15:21:55Z) with its committed projection in `evidence/qa-gates/coverage-final.md` (MEASUREMENT 3) +- 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 (56084/65736) / 79.73% branches (13597/17054). Post-change: 85.33% lines (56092/65736) / 79.73% branches (13597/17054). Change: +0.01% lines (+8 covered lines, denominator unchanged) / +0.00% branches (0). New/changed-code coverage: 98.62%. Disposition: PASS. Evidence: `evidence/baseline/coverage-baseline.md`; `evidence/qa-gates/coverage-final.md` sections "Post-merge measurement (MEASUREMENT: 3, STAGE final3)" and "Comparison against coverage-baseline.md (MEASUREMENT: 3, STAGE final3)"; reviewer re-read of the `` root and the three `` nodes of `coverage/baseline-940.cobertura.xml` and `coverage/final3-940.cobertura.xml`. +- 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 + +The C# "New/changed-code coverage" figure is the aggregate over the three production files whose coverage the rewritten tests carry, because this item changes zero executable production lines (P2-T7 `CHANGED-PRODUCTION-LINES: 0`; P2-T10 `PDA-DIFF-EXIT: 0`, `PFA-DIFF-EXIT: 0`, `DIWP-DIFF-EXIT: 0`): PhysicalDirectoryInfoAdapter.cs 91/91, PhysicalFileInfoAdapter.cs 71/75, DirectoryInfoWrapper.cs 123/123, total 285/289 = 98.62% lines; branches 45/58 = 77.59% (36/42, 6/12, 3/4), each file unchanged from baseline on branches. + +C# coverage against the two floor statements in force: + +- Repository rules (`.claude/rules/general-unit-test.md`, `.claude/rules/quality-tiers.md`): line >= 85%, branch >= 75%. First-party 85.33% lines and 79.73% branches: PASS on both. The line margin is 0.33 points; this is a pre-existing repository property (baseline 85.32%), not an effect of this change. +- CLAUDE.md UT2 (maintainer-settled 2026-09-11, issue #563): line >= 80%, branch >= 75%. PASS on both. +- New modules, classes or methods (>= 90% target): none added; the two changed files are test code outside the denominator. +- No regression on changed lines: no production line changed; the per-file rule over the three carried files holds (covered lines 81 -> 91, 69 -> 71, 123 -> 123; covered branches 36 -> 36, 6 -> 6, 3 -> 3). + +C# canonical artifact location note: the item worktree has no `artifacts/csharp/coverage.xml`; the session checkout carries a stale `artifacts/csharp/coverage.xml` (root epoch 1788642282 = 2026-09-05, line-rate 84.83%, branch-rate 79.14%) that belongs to an earlier branch. As C# coverage evidence for this branch that stale document is rated FAIL and was not used; it is superseded by the two on-disk Cobertura documents named in the checklist and their committed projections, which are the artifacts this audit reads. The disposition is non-blocking because the feature-folder evidence and the on-disk documents carry every figure the audit needs and were independently re-read. + +**Coverage Metrics by Language:** + +| Language | Files Changed | Tests | Test Result | Baseline Coverage | Post-Change Coverage | New Code Coverage | +| --- | --- | --- | --- | --- | --- | --- | +| C# | 2 (test-only) | 15 scoped; 7327 full suite | 15/15 passed; 7327/7327 passed | 85.32% lines / 79.73% branches | 85.33% lines / 79.73% branches | 98.62% lines (285/289 across the three carried production files) | +| TypeScript | 0 | 0 | N/A - zero files changed | N/A | N/A | N/A | +| PowerShell | 0 | 0 | N/A - zero files changed | N/A | N/A | N/A | +| Python | 0 | 0 | N/A - zero files changed | N/A | N/A | N/A | + +### 1.3 Scenario Completeness + +| Scenario class | Verdict | Evidence | +| --- | --- | --- | +| Positive flows | PASS | Accessor mirroring, enumeration wrapping, delegation through Moq strict mocks, delegate-seam sentinel identity. | +| Negative flows | PASS | Null constructor argument; every setter, `Delete`, `MoveTo`, `CopyTo`, `Replace` on a missing owned path throws the wrapped BCL exception type; `WrapFileSystemInfo` on an unsupported `FileSystemInfo` subtype throws `ArgumentException`. | +| Edge cases | PASS | `FileInfo.Delete()` on a missing file is asserted as the documented no-op; `Create`, `CreateSubdirectory`, `Refresh`, `SetAccessControl` on existing owned entries complete without exception. | +| Error handling | PASS | Exception types asserted with `Should().Throw()`; no catch blocks. | +| Concurrency | PASS | Runs under the repository's parallel runsettings (hash `98EF03A8...` unchanged from P0-T4); no serialization attribute introduced. | +| State transitions | PASS | Moq `SetupSet` callbacks verify setter propagation on the wrapper. | + +### 1.4 Test Structure (UT3) and External Dependencies (UT4) + +- Arrange-Act-Assert: PASS for the seven rewritten PFS tests and the four rewritten DIW tests (explicit `// Arrange`, `// Act`, `// Assert` markers; guard preconditions sit in Arrange). The three Moq-based DIW tests, untouched by this diff, carry no markers (pre-existing; CR-3). +- Clear failure messages: PASS; FluentAssertions throughout; the `MemberCount` assertions carry a `because` clause to disambiguate from the `Length` assertion. +- External dependencies: PASS; no network, database, process or external service. File-system access is limited to read-only opens of the loaded assembly image with `FileShare.ReadWrite` or `FileShare.Read`, enumeration of the owned output directory and its parent, and the no-op and missing-path calls enumerated in `evidence/qa-gates/p2-t14-ac3-mutating-call-inventory.2026-09-30T11-31.md` (34 PFS call sites, 16 DIW call sites, 0 naming a repository path). +- Temporary files: PASS; none created (`Path.GetTemp`, `File.Create`, `File.WriteAll`, `Directory.CreateDirectory` all 0; every missing-path call throws before touching the disk or is a documented no-op; P2-T10 `ADDED-TEMP: 0`). +- Test file location: the files pre-exist under the repository's `UtilitiesCS.Test/` project layout and were neither created nor moved; the `tests/` mirror rule is not engaged by this change. +- Determinism infrastructure: no clock or RNG read; fixed `DateTime` literals only. + +## 2. General Code Change Policy Compliance + +| Rule | Verdict | Evidence | +| --- | --- | --- | +| Bugfix workflow (regression test first) | PASS with dossier | A committed failing test is structurally impossible (an external process holding `TaskMaster.sln` or a relocated assembly would be required); `evidence/regression-testing/fail-before-exception.2026-09-30T07-28.md` records `WhyFailingRunImpossible`, the static reproduction from the pre-edit census (`GetRepositoryRoot` 6 and 5 occurrences, `TaskMaster.sln` 13 and 3, `catch (IOException)` 2), the #906/#931 precedent, and the eleven deterministic negative controls as the observed-failing evidence. | +| Minimal targeted fix | PASS | Diff footprint is exactly the two test files plus feature-folder documents (`p2-t10-scope-boundary`: `THIS-ITEM-FOOTPRINT`, `UCS-TEST-CHANGED` = the two paths, `OUT-OF-SET-DIFF-EXIT: 0`). | +| Design principles | PASS | Fixtures reduced to three static properties; no new abstraction; the pre-existing internal delegate seam of `PhysicalFileInfoAdapter` is reused for write-mode members. | +| File size limit (500 lines, tests included) | PASS | PFS 446 lines (baseline 388), DIW 394 lines (baseline 393) (`p2-t8-post-format-census`; reviewer read both files to their final line). | +| Error handling (no swallowing) | PASS | `catch` token 0 in both files (baseline PFS 2). | +| Naming and comments | PASS | `PascalCase`/`camelCase` observed; comments explain why (owned-fixture rationale, sharing modes, seam routing). | +| Dependencies | PASS | No new package; `Moq`, `FluentAssertions`, MSTest already referenced. | +| Mandatory toolchain loop | PASS | `evidence/qa-gates/toolchain-pass.md`: `ITERATIONS: 1`, `LOOP: CLEAN PASS`, eight `EXPECTATION-MET: YES` lines. See section 7 and G-1 for the post-merge format-check caveat. | +| Supporting documents updated | PASS | issue.md AC check-off (8/8), plan check-off (64/64 tasks), Revision Log entries A and B. | + +## 3. Language-Specific Code Change Policy Compliance + +C# (`.claude/rules/csharp.md`, CLAUDE.md C#1 to C#7): + +| Rule | Verdict | Evidence | +| --- | --- | --- | +| CSharpier via `dotnet tool run` | PASS | P2-T1 `dotnet tool run csharpier format .` exit 0, `REWRITTEN: 0`; P2-T2 `dotnet tool run csharpier check .` exit 0, `Checked 1625 files in 7661ms.` | +| Analyzer rebuild (`/t:Rebuild`, analyzers enforced) | PASS | `p2-t7-msbuild-analyzers.2026-09-30T11-19.md`: exit 0, `SKIP_CORECOMPILE_LINES: 0`, `UCS_TEST_CSC_OUT_LINES: 2`, `UCS_CSC_OUT_LINES: 2`, `WARNINGS: 0`, `ERRORS: 0`, `WRITESET_DIAGNOSTIC_LINES: 0` on head `d3f01551` (post-merge). | +| Nullable / TreatWarningsAsErrors rebuild (no `/p:Nullable=enable`, `/t:Rebuild`) | PASS | `p2-t7-msbuild-nullable.2026-09-30T11-20.md`: same field set, exit 0, zero warnings, zero errors. | +| Null safety in touched code | PASS | Test files are nullable-oblivious (no `#nullable enable`); the single `!` on the reflection lookup is in test code and guarded by the immediate `Invoke`. | +| No breaking public API change | PASS | No production file changed (three production hashes equal their `PRE-EDIT-HASH-` anchors). | +| Deterministic test rules (no PATH/cwd/profile dependence) | PASS | Fixture root is `Assembly.Location`; no working-directory assumption; no repository walk. | +| DI seams | PASS | Write-mode opens routed through the existing internal injectable-delegate seam (`PhysicalFileInfoAdapter` internal constructor) with test-owned sentinel streams. | +| Analyzer stack and suppressions | PASS | No `#pragma`, `SuppressMessage` or `.editorconfig` change in the diff. | +| Prohibited behaviors (sleeps, retries, weakened assertions) | PASS | None present; assertions strengthened (delegation now asserted by reference identity and by thrown type). | + +TypeScript, Python, PowerShell: no changed files on this branch; the language-specific code-change policies are not engaged. + +## 4. Language-Specific Unit Test Policy Compliance + +C# Unit Test Policy (CUT1 to CUT3): + +| Rule | Verdict | Evidence | +| --- | --- | --- | +| MSTest framework | PASS | `[TestClass]` and `[TestMethod]` from `Microsoft.VisualStudio.TestTools.UnitTesting`; 7 + 8 test methods. | +| Moq for mocks | PASS | DIW uses `Mock`, `Mock`, `Mock` with `MockBehavior.Strict`; PFS exercises the physical adapters against owned fixtures and the delegate seam, which AC4 admits as "a test-owned read-only fixture". | +| FluentAssertions | PASS | Every assertion uses `.Should()`; no MSTest `Assert` call. | +| Toolchain command selection (CUT3) | PASS | Commands match CLAUDE.md character for character (section 7); the coverage route is the DIRECT `dotnet-coverage collect` wrapper around `vstest.console.exe` with the Code Coverage collector withheld, as the runner script does. | + +## 5. Test Coverage Detail + +Per-file coverage of the three production files whose coverage the rewritten tests carry (covered/valid; reviewer-verified against the `` node `line-rate` and `branch-rate` attributes of both Cobertura documents): + +| File | Baseline lines | Post-change lines | Baseline branches | Post-change branches | Verdict | +| --- | --- | --- | --- | --- | --- | +| `UtilitiesCS/HelperClasses/FileSystem/PhysicalDirectoryInfoAdapter.cs` | 81/91 (line-rate 0.89011) | 91/91 (line-rate 1) | 36/42 (0.857143) | 36/42 (0.857143) | PASS (not lower; +10 lines) | +| `UtilitiesCS/HelperClasses/FileSystem/PhysicalFileInfoAdapter.cs` | 69/75 (0.92) | 71/75 (0.946667) | 6/12 (0.5) | 6/12 (0.5) | PASS (not lower; +2 lines) | +| `UtilitiesCS/HelperClasses/FileSystem/DirectoryInfoWrapper.cs` | 123/123 (1) | 123/123 (1) | 3/4 (0.75) | 3/4 (0.75) | PASS (equal) | + +Observations: + +- The ten newly covered PDA lines recorded by the executor's read-only diagnostic are 30, 31, 42, 43, 48, 49, 54, 55, 60 and 61 (the getter and setter bodies of `CreationTimeUtc`, `LastAccessTime`, `LastAccessTimeUtc`, `LastWriteTime` and `LastWriteTimeUtc`); the two newly covered PFA lines are 52 and 53 (the `Attributes` getter and setter). These correspond to the missing-path setter assertions and the UTC accessor assertions the rewrite added (`coverage-final.md`, "Diagnostic observation"; the same twelve lines on MEASUREMENT 1 and 2). +- PFA branch coverage 6/12 (50%) is unchanged and pre-existing. The plan's revision-3 re-derivation reads the `.ctor` branch at line 27 as `50% (1/2)`; the twelve branches appear to be the six null-coalescing throw expressions of the two constructors, whose throw sides no test exercises. Below the 75% figure at file level, but not a regression and not a changed line; recorded as follow-up F-2 in the code review, non-blocking. +- Run-to-run variance in files this item does not touch (`PropertyStore.cs`, `OlTableExtensions.Etl.cs`, `SubjectMapSco.Orchestration.cs`; up to 6 lines between identical runs of the same tree) is documented in `coverage-final.md` "Two-measurement variance table" and motivated the coordinator's per-file ruling. The reviewer accepts the ruling's reasoning: the rewritten tests cannot reach those files, and MEASUREMENT 3 on the merged head restored the baseline figures for two of the three. +- Denominators are unchanged (65736 first-party lines, 17054 branches; `COMPARABILITY-MEASUREMENT-3: A`), so the +8 line delta is attributable to the two changed test files (10 + 2 gained in the carried files, minus the 4-line `SubjectMapSco.Orchestration.cs` variance recorded in `UNTOUCHED final3`). + +## 6. Test Execution Metrics + +| Run | Command route | Total | Passed | Failed | Evidence | +| --- | --- | --- | --- | --- | --- | +| Baseline scoped (two classes, pre-edit) | `vstest.console.exe` + CLI runsettings + `/InIsolation` | 12 | 12 | 0 | `evidence/baseline/test-run-baseline.md` | +| Baseline full suite (P0-T11) | DIRECT `dotnet-coverage collect` around vstest, 9 assemblies | 7323 | 7323 | 0 | `evidence/baseline/coverage-baseline.md` | +| Post-fix scoped (P1-T7, P2-T5) | as baseline scoped | 15 | 15 | 0 | `p1-t7-exception-type-observation`, `test-run-final.md` | +| Final full suite MEASUREMENT 1 / 2 (pre-merge) | DIRECT | 7326 / 7326 | 7326 / 7326 | 0 / 0 | `coverage-final.md` | +| Final full suite MEASUREMENT 3 (post-merge head, gating) | DIRECT | 7327 | 7327 | 0 | `coverage-final.md` | +| Negative controls C1 to C11 (mutated / reverted) | single-test vstest | 1 each | 0 / 1 each | 1 / 0 each | `evidence/regression-testing/mutation-*.md` | + +Test count delta: +3 on the pre-merge tree (PFS grew from 4 to 7 test methods; DIW unchanged at 8), +1 more from the merged origin/main tree. `NEWLY-FAILING: NONE` on every full-suite run. Four UtilitiesCS.Test shell-icon classes and `OSBrowser_Tests` were excluded locally by the plan's fixed filter (an environmental hang on this workstation; CI runs them), as at #944. + +## 7. Code Quality Checks + +| Check | Command | Result | +| --- | --- | --- | +| Format check | `dotnet tool run csharpier check .` | exit 0, 1625 files (P2-T2, pre-merge tree; both changed files unchanged since: SHA-256 `C88A785C...` and `6650B332...` equal at P1-T5, P1-T8, P2-T1, P2-T8, P2-T10) | +| Analyzer rebuild | `msbuild TaskMaster.sln /t:Rebuild /m /p:Configuration=Debug "/p:Platform=Any CPU" /p:EnableNETAnalyzers=true /p:EnforceCodeStyleInBuild=true` | exit 0, 0 warnings, 0 errors, CoreCompile ran (post-merge) | +| Nullable rebuild | `msbuild TaskMaster.sln /t:Rebuild /m /p:Configuration=Debug "/p:Platform=Any CPU" /p:TreatWarningsAsErrors=true` | exit 0, 0 warnings, 0 errors, CoreCompile ran (post-merge) | +| Confidentiality masking scan | `CMD-SWEEP` over the feature folder (P2-T11, P2-T20) and `scripts/hygiene/Test-RepositoryHygiene.ps1` | 0 account, profile, machine, worktree-root or user-path matches over 48 and 51 files; `HYGIENE Findings=0`; reviewer Grep of the feature folder for drive-letter user paths found only the `C:\Repo\fixture` test literal | +| Suppression scan (added lines) | Grep of the diff for `#pragma`, `SuppressMessage`, `[ExcludeFromCodeCoverage]` | 0 in the two changed files | +| Workflow change scan | diff list inspection | no `.github/` path in the diff | +| Added-line prohibition census | `CMD-ADDED-LINES` (P2-T10) | 276 added lines; `DoNotParallelize`, `Thread.Sleep`, `Task.Delay`, `Timeout`, `Retry`, `Workers`, `Scope`, temp-file, `catch`, root-walk counts all 0 | +| Evidence timestamp fidelity | reflog epochs vs `Timestamp:` labels vs Cobertura root epochs | consistent to the minute at UTC-4: baseline collector end 07:20:37 (epoch 1790767237) vs label `07-22`; fix commit 07:34:59 vs `07-35`; MEASUREMENT 3 collector 11:21:55 vs `11-22`; closure commit 11:33:13 vs `11-33` | + +## 8. Gaps and Exceptions + +- G-1 (non-blocking): the CSharpier check was not re-run on the merged tree. The coordinator ruling scoped the post-merge re-run to the two rebuilds and the coverage route; the two changed files are byte-identical to the state P2-T2 checked, and the merged files come from origin/main. Disposition: CI `_format-check.yml` on the PR head is the closing gate; the orchestrator should confirm it before merge. +- G-2 (non-blocking): PR context artifacts (`artifacts/pr_context.summary.txt`, `artifacts/pr_context.appendix.txt`) are absent from the item worktree and the session checkout's copies belong to another branch (#936). The reviewer could not regenerate them without a shell. Scope was derived from the executor's verbatim three-dot diff list (P2-T10, P2-T11, P2-T20) and the caller's branch facts, all mutually consistent with the worktree reflog. The orchestrator's pr-author step regenerates the artifacts before `gh pr create`. +- G-3 (non-blocking): `validate_evidence_locations.py` was not executed (no shell); manual inspection of the 46-path diff list found no `artifacts/` path. The script run remains owed to the orchestrator's PR preflight. + +No exception to the unit-test policy was claimed by the change and none is needed. + +## 9. Summary of Changes + +- `UtilitiesCS.Test/HelperClasses/PhysicalFileSystemAdapters_Tests.cs` (+215 / -193 at the fix commit; 388 -> 446 lines): removed `GetRepositoryRoot`, `GetSolutionFile`, two `catch (IOException)` blocks and every mutating call on the repository root or on `TaskMaster.sln`; added three static owned-fixture properties and rewrote the four tests into seven (accessor mirroring with no-op creation; enumeration wrapping; directory setters on a missing path; missing-directory and unsupported-info branches; file properties, streams and accessors through the delegate seam; file setters on a missing path; missing-file branches). +- `UtilitiesCS.Test/HelperClasses/DirectoryInfoWrapper_Tests.cs` (+32 / -31; 393 -> 394 lines): removed `GetRepositoryRoot`; retargeted the properties and `ToString` tests to the rooted test-owned literal `C:\Repo\fixture` and the two enumeration tests to the owned output directory and its parent; the constructor test and the three Moq-based tests are unchanged. +- Feature folder: 41 evidence artifacts, issue.md AC check-off and the AC8 amendment note, plan revision 1.3. +- No production file, project file, runsettings, `coverage.config` or workflow changed. + +## 10. Compliance Verdict + +- Overall: **PASS**. +- Blocking findings: **0**. +- Non-blocking findings: 7 (code review CR-1 to CR-7) plus 3 gaps (G-1 to G-3). +- Remediation required: **NO**. No `remediation-inputs` artifact is produced. +- PR-time gates owed to the orchestrator: CI format check on the merged head (G-1); PR context regeneration (G-2); `validate_evidence_locations.py` (G-3); follow-up promotion of the SortEmail_Tests `TrySaveAttachmentAsync` root-walk site (AC7 section 4, `same defect class`). + +## Appendix A: Test Inventory + +`UtilitiesCS.Test/HelperClasses/PhysicalFileSystemAdapters_Tests.cs` (7 tests; all Passed at P2-T5): + +1. `PhysicalDirectoryInfoAdapter_AccessorsAndNoOpCreation_MirrorOwnedDirectory` - getters, `GetAccessControl` (both overloads), `GetObjectData`, `ToString`, `Create`, `Create(security)`, `CreateSubdirectory` (both), `Refresh`, `SetAccessControl` on the owned directory and its parent. Control C1. +2. `PhysicalDirectoryInfoAdapter_Enumeration_WrapsOwnedDirectoryEntries` - the 18 enumeration overloads; wrapper types asserted. Control C2. +3. `PhysicalDirectoryInfoAdapter_SettersOnMissingDirectory_ThrowWithoutCreatingEntries` - seven setters throw `FileNotFoundException`; `Exists` stays false. Control C3. +4. `PhysicalDirectoryInfoAdapter_MissingDirectoryAndUnsupportedInfo_BranchesBehaveAsExpected` - `Delete`, `Delete(true)`, `MoveTo` throw `DirectoryNotFoundException`; `WrapFileSystemInfo` throws `ArgumentException` for an unsupported subtype. Control C4. +5. `PhysicalFileInfoAdapter_PropertiesStreamsAndAccessors_MirrorFileInfo` - getters, read-only opens, seam-routed `AppendText`/`Open(mode)`/`Open(mode, access)`/`OpenWrite`, `GetAccessControl`, `GetObjectData`, `Refresh`, `SetAccessControl`, `ToString`. Control C5. +6. `PhysicalFileInfoAdapter_SettersOnMissingFile_ThrowWithoutCreatingFiles` - eight setters throw `FileNotFoundException`. Control C6. +7. `PhysicalFileInfoAdapter_MissingFileBranches_ThrowOrNoOpWithoutCreatingFiles` - `Delete` no-op; `CopyTo` (both), `MoveTo`, `Replace` (both) throw `FileNotFoundException`. Control C7. + +`UtilitiesCS.Test/HelperClasses/DirectoryInfoWrapper_Tests.cs` (8 tests; all Passed at P2-T5): + +1. `Constructor_WhenDirectoryInfoIsNull_ThrowsArgumentNullException` (unchanged). +2. `Properties_ShouldMirrorWrappedDirectoryInfo` (retargeted to the rooted literal). Control C8. +3. `GetDirectoriesAndGetFiles_ShouldReturnWrappedEntries` (retargeted to the owned directory and its parent). Control C9. +4. `EnumerateFileSystemInfos_ShouldWrapDirectoriesAndFiles` (retargeted). Control C10. +5. `ToString_ShouldDelegateToWrappedDirectoryInfo` (retargeted). Control C11. +6. `PropertyDelegates_ShouldMirrorMockedIDirectoryInfo` (unchanged, Moq). +7. `EnumerationAndArrayMethods_ShouldDelegateToWrappedIDirectoryInfo` (unchanged, Moq). +8. `LifecycleAndAccessControlMethods_ShouldDelegateToWrappedIDirectoryInfo` (unchanged, Moq). + +Rewritten tests: 11 of 15; negative controls: 11 of 11, one per rewritten test. + +## Appendix B: Toolchain Commands Reference + +1. `dotnet tool run csharpier format .` then `dotnet tool run csharpier check .` (P2-T1, P2-T2) +2. `msbuild TaskMaster.sln /t:Rebuild /m /p:Configuration=Debug "/p:Platform=Any CPU" /p:EnableNETAnalyzers=true /p:EnforceCodeStyleInBuild=true` (P2-T3; post-merge P2-T7a) +3. `msbuild TaskMaster.sln /t:Rebuild /m /p:Configuration=Debug "/p:Platform=Any CPU" /p:TreatWarningsAsErrors=true` (P2-T4; post-merge P2-T7b) +4. `dotnet-coverage collect --output coverage\final3-940.cobertura.xml --output-format cobertura --settings coverage\effective-coverage-940.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\940\final3" "/Logger:trx;LogFileName=final3-940.trx" "/Blame:CollectHangDump;TestTimeout=4min;HangDumpType=None"` followed by the runner post-processing helpers (`ConvertTo-KoverageCoberturaXml`, threshold assertions, `Get-CoberturaFirstPartyCoverageReport`, `ConvertTo-JacocoPackageProjection`, `Get-TrxRunSummary`) (P0-T11 baseline, P2-T6 MEASUREMENT 1 and 2, P2-T7 MEASUREMENT 3) +5. Scoped: `vstest.console.exe UtilitiesCS.Test\bin\Debug\UtilitiesCS.Test.dll "/Settings:scripts\vscode\TaskMaster.cli.runsettings" /InIsolation "/TestCaseFilter:FullyQualifiedName~HelperClasses.PhysicalFileSystemAdapters_Tests|FullyQualifiedName~HelperClasses.DirectoryInfoWrapper_Tests" ...` (P0-T10, P1-T7, P2-T5) and the single-test filters of the eleven controls + +Template provenance: the MCP policy-audit template asset was not resolvable in this session (no MCP call available to the reviewer); the canonical heading set of `policy-audit-template-usage` was reproduced by hand, and the instruction block is omitted. diff --git a/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/research/2026-09-29T21-10-filesystem-wrapper-tests-open-repository-solution-file-research.md b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/research/2026-09-29T21-10-filesystem-wrapper-tests-open-repository-solution-file-research.md new file mode 100644 index 000000000..ccf3ce2dc --- /dev/null +++ b/docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/research/2026-09-29T21-10-filesystem-wrapper-tests-open-repository-solution-file-research.md @@ -0,0 +1,217 @@ +# Research: file-system wrapper tests open the repository solution file (Issue #940) + +- Timestamp: 2026-09-29T21-10 +- Issue: #940 (minor-audit bug; sibling of #931 / PR #939) +- Branch: bug/filesystem-wrapper-tests-open-repository-solution-file-940 (cut from origin/main) +- Requirements source: `docs/features/active/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file-940/issue.md` +- Evidence basis: every file:line below was re-read in the item worktree with the Read, Grep and Glob tools on 2026-09-29. No command was executed: the Bash tool was disabled for this session ("Bash is disabled for this session, in subagents as well as here"), so the `git log` / `git show` steps the delegation named could not run. The #931 diff is reconstructed from the committed evidence of that feature (its fix-commit record, negative-control records, plan facts and the current content of `FileInfoWrapper_Tests.cs`), which is sufficient because #931 changed no production file (its P4-T11 gate and `post-merge-coverage-final.md` both record `CHANGED-PRODUCTION-LINES: 0`). +- Marks: `[V]` verified by reading the worktree; `[E]` taken from committed #931 evidence; `[P]` predicted from documented .NET Framework 4.8 behaviour and not observed in this session (the executor must observe it before pinning an assertion). + +## 0. Summary + +Both test classes locate the repository root by walking up from the test assembly until `TaskMaster.sln` is found, then run the production adapters against the real repository root directory and the real solution file. The runs are not merely reads: the current code sets timestamps and attributes on the repository root and on `TaskMaster.sln`, calls `Create`/`CreateSubdirectory`/`SetAccessControl` on the repository root, names `TaskMaster.sln` as the overwrite target of `CopyTo(..., overwrite: true)` and `Replace(...)` from a missing source, and swallows `IOException` around the setters. Eight of the twelve test methods in the two files are affected; the other four already use Moq through the internal `IDirectoryInfo` constructor seam and are unaffected. + +Recommendation: a test-only rewrite (no production change) that (a) verifies path computations against a rooted literal that is not expected to exist, following #931 decision D-14; (b) verifies read-only adapter delegation against fixtures the test process owns, namely the running test assembly image (`typeof(T).Assembly.Location`), its containing directory and that directory's parent; (c) verifies every mutating member either through its documented throw on a missing path under the owned directory (delegation is observable, nothing on disk changes) or through a call that is a no-op by construction on an existing owned entry; (d) keeps the four already-seamed `PhysicalFileInfoAdapter` stream members on the existing internal delegate constructor with sentinel streams, exactly as today; and (e) deletes both `catch (IOException)` blocks and both `GetRepositoryRoot` helpers plus `GetSolutionFile`. No `[DoNotParallelize]`, retry, sleep or temporary file is introduced. Each rewritten test gets one negative control (a temporary, reverted mutation of the wrapper or adapter) recorded in the #931 M1/M4 format. + +## 1. Inventory of affected test methods + +### 1.1 `UtilitiesCS.Test/HelperClasses/PhysicalFileSystemAdapters_Tests.cs` (389 lines) `[V]` + +Helpers: `GetRepositoryRoot()` at lines 349-371 (walk-up from `Assembly.Location` at 354-356, `TaskMaster.sln` probe at 361, `Should().NotBeNull()` at 367-369); `GetSolutionFile()` at lines 373-377 (builds a `FileInfo` over `TaskMaster.sln`, line 376). `UnsupportedInfo` at 379-386 is a pure in-memory `FileSystemInfo` subclass and is not affected. + +| # | Test method (line) | Root/solution touch | Members verified | Real file-system mutation performed today | `IOException` swallow | +|---|---|---|---|---|---| +| T1 | `PhysicalDirectoryInfoAdapter_PropertiesEnumerationAndAccessors_MirrorDirectoryInfo` (16) | `GetRepositoryRoot()` at 19; repository root is the adapter target; solution file asserted in enumerations at 110-114, 116, 126-129 | `PhysicalDirectoryInfoAdapter`: `Attributes` get/set (29), 6 timestamp get/set (36-41), `Create()` (48), `Create(DirectorySecurity)` (49), `CreateSubdirectory` x2 (51-55), 9 `Enumerate*` overloads (56-73), 9 `Get*` overloads (74-88), `GetAccessControl()` (21) and `(sections)` (89), `GetObjectData` (90), `Refresh` (91), `SetAccessControl` (92), `ToString` (93), `Exists`/`FullName`/`Name`/`Parent`/`Root` (96-100) | Yes. `Attributes` setter (29) and the six timestamp setters (36-41) write metadata of the repository root directory (same values, but each is a Win32 write); `Create()`/`Create(security)` (48-49) and `CreateSubdirectory("UtilitiesCS", ...)` (51-55) are creation APIs invoked on the repository root (no-ops only because the entries exist); `SetAccessControl(security)` (92) hands the root's DACL back to the OS | Yes: `try { ...setters... } catch (IOException) { }` at 34-46 | +| T2 | `PhysicalDirectoryInfoAdapter_MissingDirectoryAndUnsupportedInfo_BranchesBehaveAsExpected` (138) | `GetRepositoryRoot().FullName` at 142 and 155 (missing path and `MoveTo` destination are children of the repository root) | `Delete()`, `Delete(true)`, `MoveTo` on a missing directory (152-155); `WrapFileSystemInfo` unsupported branch by reflection (146-149, 156-157) | Delete/Move are mutation APIs; they throw before mutating because the source does not exist. If a directory named `__missing_physical_directory_adapter__` ever existed under the repository root it would be deleted or moved | No | +| T3 | `PhysicalFileInfoAdapter_PropertiesStreamsAndAccessors_MirrorFileInfo` (170) | `GetSolutionFile()` at 173; the adapter targets `TaskMaster.sln`; read opens at 213-234 | `PhysicalFileInfoAdapter`: 6 timestamp setters (188-193), `IsReadOnly` set (200), `Open(mode, access, share)` (214-218), `OpenRead()` (225), `OpenText()` (231), seamed `AppendText`/`Open(mode)`/`Open(mode, access)`/`OpenWrite` through the internal constructor (276-282, asserted 301-307), `GetAccessControl()` (175) and `(sections)` (284), `GetObjectData` (285), `Refresh` (286), `SetAccessControl` (287), `ToString` (288), `Exists`/`Extension`/`FullName`/`Name`/`Directory`/`DirectoryName`/`Length` (291-297) | Yes. The six timestamp setters and `IsReadOnly = IsReadOnly` (200) write metadata and attributes of `TaskMaster.sln`; `SetAccessControl` (287) hands the file's DACL back. The comment at 182-185 says the setters target "the bin-dir copy of the test DLL"; the code targets the solution file, so the comment is stale | Yes: `try { ...setters... } catch (IOException) { }` at 186-198 | +| T4 | `PhysicalFileInfoAdapter_MissingFileBranches_ThrowOrNoOpWithoutCreatingFiles` (314) | `GetRepositoryRoot()` at 317, `GetSolutionFile()` at 318; the solution file is the destination of `CopyTo` (324-325) and `Replace` (328-338) | `Delete()` (323), `CopyTo` x2, `MoveTo`, `Replace` x2 on a missing file | Mutation APIs whose target is the real solution file: `CopyTo(solution.FullName, overwrite: true)` (325) and `Replace(solution.FullName, ...)` (329-338) would overwrite or replace `TaskMaster.sln` if the source ever existed; `Delete()` (323) is a real `File.Delete` on a repository-root path | No | + +### 1.2 `UtilitiesCS.Test/HelperClasses/DirectoryInfoWrapper_Tests.cs` (394 lines) `[V]` + +Helper: `GetRepositoryRoot()` at lines 375-391 (walk-up from `AppDomain.CurrentDomain.BaseDirectory` at 377, `TaskMaster.sln` probe at 381, `Should().NotBeNull()` at 387-389). + +| # | Test method (line) | Root/solution touch | Members verified (all through the public `DirectoryInfoWrapper(DirectoryInfo)` constructor, so through `PhysicalDirectoryInfoAdapter`) | Real mutation | `IOException` swallow | +|---|---|---|---|---|---| +| D1 | `Properties_ShouldMirrorWrappedDirectoryInfo` (27) | `GetRepositoryRoot()` at 30 | `Exists`, `FullName`, `Name`, `Extension`, `Parent.FullName`, `Root.FullName` (34-39) | No | No | +| D2 | `GetDirectoriesAndGetFiles_ShouldReturnWrappedEntries` (43) | `GetRepositoryRoot()` at 46; asserts `UtilitiesCS` directory (56) and `TaskMaster.sln` file (60) | `GetDirectories()`, `GetFiles()` and the wrapping types (54-60) | No | No | +| D3 | `EnumerateFileSystemInfos_ShouldWrapDirectoriesAndFiles` (64) | `GetRepositoryRoot()` at 67; asserts `UtilitiesCS` (76) and `TaskMaster.sln` (79) | `EnumerateFileSystemInfos()` wrapping (71-79) | No | No | +| D4 | `ToString_ShouldDelegateToWrappedDirectoryInfo` (83) | `GetRepositoryRoot()` at 86 | `ToString()` (90-93) | No | No | + +Unaffected (already Moq-based through the internal `DirectoryInfoWrapper(IDirectoryInfo)` constructor): `Constructor_WhenDirectoryInfoIsNull_ThrowsArgumentNullException` (17), `PropertyDelegates_ShouldMirrorMockedIDirectoryInfo` (97), `EnumerationAndArrayMethods_ShouldDelegateToWrappedIDirectoryInfo` (187), `LifecycleAndAccessControlMethods_ShouldDelegateToWrappedIDirectoryInfo` (332). `[V]` + +### 1.3 Repository-wide census of the same pattern `[V]` + +Grep `TaskMaster\.sln|GetRepositoryRoot|FindRepositoryRoot` over `*.cs`: the two files above; `TaskMaster.Test/Ribbon/RibbonControllerTests.cs` (301, 422-437); `TaskMaster.Test/Bootstrap/FSharpCoreHintPathAlignmentTests.cs` (27, 47-67, 88, 156, 181) and its consumer `TaskMaster.Test/Bootstrap/FSharpCoreDeployedIdentityTests.cs` (79); `UtilitiesCS.Test/EmailIntelligence/SortEmail_Tests.cs` (196, 223, 241, 255, 280, 393-414); and three sites issue.md does not name, which use a fixed three-level `Parent` walk plus a `README.md` probe instead of the solution file: `TaskMaster.Test/OutlookObjects/Store/StoresWrapperTests.cs` (33, 317-328), `TaskMaster.Test/AppGlobals/ApplicationGlobalsTests.cs` (194, 365-376), `UtilitiesCS.Test/OutlookObjects/Store/StoresWrapperTests.cs` (248, 294-305). All are classified in section 5. + +## 2. Production types under test and their seams `[V]` + +### 2.1 Types + +- `UtilitiesCS/HelperClasses/FileSystem/PhysicalDirectoryInfoAdapter.cs` (218 lines, `#nullable enable`): `internal sealed class PhysicalDirectoryInfoAdapter(DirectoryInfo directoryInfo) : IDirectoryInfo` (line 11, primary constructor, null guard at 13-14). Every member delegates straight to the wrapped `System.IO.DirectoryInfo`; enumeration members wrap results in `DirectoryInfoWrapper`/`FileInfoWrapper` (91-188) and `WrapFileSystemInfo` (202-215) throws `ArgumentException` for an unsupported `FileSystemInfo` subtype. `Parent` (70) and `Root` (72) construct wrappers eagerly. There is no injectable seam of any kind. +- `UtilitiesCS/HelperClasses/FileSystem/PhysicalFileInfoAdapter.cs` (184 lines, `#nullable enable`): `internal sealed class PhysicalFileInfoAdapter : IFileInfo`. Public constructor `(FileInfo)` at 25-32 binds four delegate fields to the real `FileInfo`; internal constructor `(FileInfo, Func appendText, Func openByMode, Func openByModeAndAccess, Func openWrite)` at 34-48 is the only seam. Seamed members: `AppendText` (118), `Open(FileMode)` (146), `Open(FileMode, FileAccess)` (148-149), `OpenWrite` (158). Unseamed: everything else, including `Open(mode, access, share)` (151-152), `OpenRead` (154), `OpenText` (156), all setters, `CopyTo`/`Replace` (which wrap results in `FileInfoWrapper`, 120-124 and 162-176), `Create` (126), `CreateText` (128), `Decrypt`/`Encrypt` (130, 134). +- `UtilitiesCS/HelperClasses/FileSystem/DirectoryInfoWrapper.cs` (247 lines): public constructor `(DirectoryInfo)` at 15-20 wraps a `PhysicalDirectoryInfoAdapter`; internal constructor `(IDirectoryInfo)` at 22-26 is the Moq seam. +- `UtilitiesCS/HelperClasses/FileSystem/FileInfoWrapper.cs` (217 lines): public constructor `(FileInfo)` at 14-19 wraps a `PhysicalFileInfoAdapter`; internal constructor `(IFileInfo)` at 21-24 is the Moq seam; explicit cast to `DirectoryInfoWrapper` at 211-214. +- Interfaces (public): `UtilitiesCS/Interfaces/IHelperClasses/IFileSystemInfo.cs` (11 properties, `Delete`, `GetObjectData`, `Refresh`), `IDirectoryInfo.cs` (adds `Parent`, `Root`, `Create` x2, `CreateSubdirectory` x2, `Delete(bool)`, 9 `Enumerate*`, 9 `Get*`, `GetAccessControl` x2, `MoveTo`, `SetAccessControl`, `ToString`), `IFileInfo.cs` (adds `Directory`, `DirectoryName`, `IsReadOnly`, `Length`, `AppendText`, `CopyTo` x2, `Create`, `CreateText`, `Decrypt`, `Encrypt`, `GetAccessControl` x2, `MoveTo`, `Open` x3, `OpenRead`, `OpenText`, `OpenWrite`, `Replace` x2, `SetAccessControl`, `ToString`). `IFileInfo.OpenRead()` (line 26) returns the concrete `FileStream`, so a `MemoryStream` cannot stand in through the seam (the #931 research recorded the same constraint). +- `InternalsVisibleTo`: `UtilitiesCS/Properties/AssemblyInfo.cs` lines 18-20 grant `DynamicProxyGenAssembly2`, `UtilitiesCS.Test` and `ToDoModel.Test`. The internal adapters and internal constructors are therefore reachable from `UtilitiesCS.Test` without reflection. `[V]` +- Production consumers of the public constructors: `UtilitiesCS/HelperClasses/FileSystem/MyFileSystemInfo.cs` 35 and 42, `UtilitiesCS/EmailIntelligence/OlFolderTools/FilterOlFolders/OSBrowser.cs` 131. No production code constructs the adapters directly, so the adapters are exercised only through the wrappers' public constructors and the two test files. `[V]` + +### 2.2 What can be verified how + +| Verification channel | Applies to | Basis | +|---|---|---| +| Moq over `IDirectoryInfo`/`IFileInfo` (internal wrapper constructors) | Every member of `DirectoryInfoWrapper` and `FileInfoWrapper` | Already done: `DirectoryInfoWrapper_Tests.cs` 97-373, `FileInfoWrapper_Tests.cs` 102-355. Nothing new is needed here; the four affected `DirectoryInfoWrapper_Tests` methods only add the public-constructor path (wrapper + adapter) on top | +| Not Moq-able | Every member of both adapters | `System.IO.DirectoryInfo` and `FileInfo` are sealed BCL types; the adapters hold them as concrete fields (`PhysicalDirectoryInfoAdapter.cs` 13, `PhysicalFileInfoAdapter.cs` 11). Moq cannot substitute them | +| Existing internal delegate seam with sentinel streams | `PhysicalFileInfoAdapter.AppendText`, `Open(mode)`, `Open(mode, access)`, `OpenWrite` | Already done at `PhysicalFileSystemAdapters_Tests.cs` 251-282 and 301-307; unchanged by this item | +| Test-owned read-only fixture, existing entry: the running test assembly image `typeof(PhysicalFileSystemAdapters_Tests).Assembly.Location`, its directory, and that directory's parent | Getters that need an existing entry (`Exists == true`, `Length > 0`), all 18 enumeration members, `GetAccessControl` x2, `Open(Open, Read, ReadWrite)`, `OpenRead`, `OpenText`, `Create()`/`Create(security)` (no-op on an existing directory), `CreateSubdirectory(existingName[, security])` (no-op), `SetAccessControl(unmodified security)` (no-op at the Win32 level `[P]`), `GetObjectData`, `Refresh`, `ToString` | The image is loaded by the running host, so it exists for the whole run and cannot be deleted; `Assembly.Location` is the physical output path `\UtilitiesCS.Test\bin\Debug\UtilitiesCS.Test.dll` (`[E]` #931 M4 record, `Name` field of the sentinel stream; `[V]` csproj `OutputPath` `bin\Debug\` at `UtilitiesCS.Test/UtilitiesCS.Test.csproj` line 51). The read-only `FileShare.ReadWrite` open of the image is the shape #931 ratified (its spec line 96: cannot be denied by another process's handle in any observed mode) and already executes seven times across the two files (`FileInfoWrapper_Tests.cs` 68-73, 207-242; `PhysicalFileSystemAdapters_Tests.cs` 258-275) | +| Test-owned missing entry: a name under the owned directory that nothing creates, e.g. `Path.Combine(ownedDirectory.FullName, "__940_missing_directory")` | Every mutating member whose BCL implementation throws before touching disk when the entry is missing: `Attributes`/timestamp/`IsReadOnly` setters, `Delete`, `Delete(true)`, `MoveTo`, `CopyTo` x2, `Replace` x2 | Delegation is observable (a non-delegating adapter would not throw) and nothing on disk changes. Parent directory exists by construction, which is what selects `ERROR_FILE_NOT_FOUND` (`FileNotFoundException`) over `ERROR_PATH_NOT_FOUND` (`DirectoryNotFoundException`) in `__Error.WinIOError` `[P]`. Precondition assertion `Exists.Should().BeFalse()` before Act makes a stale entry a loud failure instead of a deletion | +| Rooted literal not expected to exist, e.g. `C:\Repo\fixture` | Pure path computations: `FullName`, `Name`, `Extension`, `Parent.FullName`, `Root.FullName`, `DirectoryName`, `ToString`, `GetObjectData`, `Refresh`, and `Exists` mirrored against the BCL object | #931 D-14 precedent (`FileInfoWrapper_Tests.cs` 15-18, 34-44, 50-58, 91-98). Outcome identical whether or not the path exists because every assertion mirrors the BCL object | +| Would need a new seam | None | With the channels above every member the two files exercise today remains exercised. A seam over `PhysicalDirectoryInfoAdapter` was evaluated and rejected (section 4.1) | + +Members of `PhysicalFileInfoAdapter` that no test exercises today and that this item does not add (pre-existing gap, out of scope): `Create()` (126), `CreateText()` (128), `Decrypt()` (130), `Encrypt()` (134), and the success paths of `CopyTo`, `MoveTo`, `Replace`. Each would create, encrypt or move a real file. `[V]` + +## 3. How #931 remedied `FileInfoWrapper_Tests.cs` `[E]`/`[V]` + +- Scope: #931 (plan `docs/features/active/2026-09-28-tests-depend-on-uncontrolled-environment-931/plan.2026-09-28T20-01.md`, Write Set lines 57-64) rewrote exactly four tests in `FileInfoWrapper_Tests.cs` and removed its `GetSolutionFile` helper; every production file, including `FileInfoWrapper.cs` "whose internal seam at lines 21 to 24 is used as is", was outside the Write Set (plan line 79). Fix commit `2956820e` (P2-T10 record) lists the six test-side paths only. The result is the current file, 357 lines. +- Pattern 1, rooted literal (D-14, plan line 130): `private const string FixturePath = @"C:\Repo\fixture.sln";` (`FileInfoWrapper_Tests.cs` 15-18). The three metadata tests (`Properties_ShouldMirrorWrappedFileInfo` 31-44, `ExplicitDirectoryCast_ShouldReturnWrappedContainingDirectory` 47-58, `ToString_ShouldDelegateToWrappedFileInfo` 88-99) assert path computations and `Exists` mirrored against the BCL `FileInfo`, so no handle opens and the outcome does not depend on the machine. +- Pattern 2, sentinel stream through the Moq seam: `OpenRead_ShouldReturnReadableStreamForWrappedFile` (61-85) opens `typeof(FileInfoWrapper_Tests).Assembly.Location` read-only with `FileShare.ReadWrite`, sets `Mock(MockBehavior.Strict).Setup(x => x.OpenRead()).Returns(sentinel)`, constructs the wrapper through `FileInfoWrapper(IFileInfo)` and asserts `BeSameAs(sentinel)`, `CanRead` and `Length > 0`. The comment (63-67) records why a `MemoryStream` cannot be used (concrete `FileStream` return type). +- No production seam was added; the delegate seam in `PhysicalFileInfoAdapter` (34-48) pre-dates #931 and is used unchanged by `PhysicalFileSystemAdapters_Tests.cs` 276-282. +- Negative control M4 (`evidence/regression-testing/mutation-openread-sentinel.md`): a second `decoy` stream was inserted with the Edit tool and `.Returns(sentinel)` changed to `.Returns(decoy)`; the token census (`CMD-CENSUS`) confirmed the mutation; the scoped build (`msbuild UtilitiesCS.Test\UtilitiesCS.Test.csproj /t:Build /m /p:Configuration=Debug /p:Platform=AnyCPU /nodeReuse:false`) and the scoped run (`vstest.console.exe ... "/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"`) recorded `EXIT_CODE: 1`, `ExpectedExitCode: 1`, `COUNTERS total=1 executed=1 passed=0 failed=1` and the message `Expected stream to refer to System.IO.FileStream ...`; the revert was `git checkout -- `, proven by `git diff --exit-code HEAD -- ` exit 0, empty `git status --porcelain -- `, and SHA-256 equality with `FIX-HASH-FIW` recorded by the fix commit; the confirming run recorded `passed=1`. Production-side controls M1/M2 used the same shape with a temporary Edit of a production file, reverted the same way and hashed against the Phase 0 SHA-256 (plan line 52). The prediction rule was D-6: any other failing assertion or a passing control is `MUTATION PREDICTION MISMATCH`, stop and report, never adjust the test. +- Evidence recording: Markdown projections only under `FEATURE/evidence/{baseline,regression-testing,qa-gates}/`; TRX and Cobertura stay under the git-ignored `coverage\` tree; absolute paths replaced by `` (plan lines 30-31, 53). Coverage was compared per package (D-7) from the JaCoCo projection: UtilitiesCS LINE and BRANCH rates must not be lower than the item's own baseline; a lower reading triggers exactly one identical re-measurement. +- Run route facts recorded by #931 that carry over: `STALL-PROBE: REPRODUCES` on this workstation (the four shell-icon classes stall), so the coverage route was `DIRECT` with the four-class exclusion appended (`post-merge-coverage-final.md` lines 6-7); the runner script hard-codes its filter and cannot apply the exclusion. + +## 4. Candidate approaches and recommendation + +### 4.1 Approaches compared + +| | A. Test-only rewrite on owned fixtures + rooted literal + throw-based mutator verification | B. Add an injectable-delegate seam to `PhysicalDirectoryInfoAdapter` (and widen the `PhysicalFileInfoAdapter` seam to setters/`OpenRead`/`OpenText`) | C. Keep the repository root but tolerate contention | +|---|---|---|---| +| Production files | 0 | 2 (`PhysicalDirectoryInfoAdapter.cs` would need ~10 delegate fields and a second constructor; `PhysicalFileInfoAdapter.cs` ~9 more) | 0 | +| Removes layout dependence | Yes: every path derives from `Assembly.Location` or is a literal | Yes | No | +| Removes the `IOException` swallow | Yes | Yes | Only by retrying, which is prohibited | +| Coverage of the adapters' real delegation lines | Unchanged: every member exercised today stays exercised through the real BCL object | Reduced: seamed members would be covered only through sentinel delegates; the default-binding lambdas in the public constructor would go uncovered unless a second real-file test is kept, which reintroduces the problem | Unchanged | +| Alignment with #931 | Same two patterns (D-14 literal, owned-image sentinel) and the same "no production change" outcome | #931 deliberately left `OpenRead` unseamed and used the existing seam as is | Contradicts #931 | +| Residual risk | `[P]` exception types for setters on a missing path must be observed once; `FileShare.Read` opens of the loaded image (`OpenRead`/`OpenText`) carry the same residual #931 accepted for the sentinel | Larger diff, new public surface inside an `internal` type, no production consumer | Fails the issue's expected behaviour | + +Selected: A. Rejected alternatives: B adds production surface without a consumer and lowers real-delegation coverage; C leaves the defect in place and would need a retry or serialization, both prohibited. + +### 4.2 Fixture helpers (test-only, private to each class; no csproj edit) + +`UtilitiesCS.Test.csproj` uses explicit `Compile Include` entries (lines 232-234 register the three wrapper test files), so adding a shared fixture file would require a csproj edit. Two private static members per class are sufficient and match the #931 style (a private const): + +```csharp +// Owned fixtures (issue #940). The running host's own loaded assembly image exists for the +// whole run and its physical path is bin\\ (csproj OutputPath), so its +// directory and that directory's parent are at least two levels deep and owned by this test +// process. No repository file and no temporary file is involved. +private static FileInfo OwnedAssemblyFile => + new FileInfo(typeof(PhysicalFileSystemAdapters_Tests).Assembly.Location); +private static DirectoryInfo OwnedAssemblyDirectory => OwnedAssemblyFile.Directory; +private static string MissingOwnedPath(string suffix) => + Path.Combine(OwnedAssemblyDirectory.FullName, "__940_missing_" + suffix); +private const string RootedFixturePath = @"C:\Repo\fixture"; +``` + +Use `Assembly.Location` rather than `AppDomain.CurrentDomain.BaseDirectory`: `DirectoryInfoWrapper_Tests.cs` 377 uses the latter, and `PhysicalFileSystemAdapters_Tests.cs` 351-353 already records why it can point at the vstest host directory. A `[V]` grep over `UtilitiesCS.Test` for `Path.GetTempPath|Path.GetTempFileName|File.WriteAll|File.Create(|Directory.CreateDirectory` returned no matches, so no other test writes into the output directory during a parallel run; enumeration assertions nonetheless use `Contain`/`ContainSingle` on named entries rather than exact set equality. + +### 4.3 Remedy per test method + +**T1 `PhysicalDirectoryInfoAdapter_PropertiesEnumerationAndAccessors_MirrorDirectoryInfo`** (16-135). Split into three focused tests (UT1 isolation; also keeps each Arrange readable): + +- T1a, read-only accessors over `OwnedAssemblyDirectory`: `Exists` true, `FullName`/`Name`/`Parent.FullName`/`Root.FullName` mirrored, `GetAccessControl()` and `GetAccessControl(AccessControlSections.Access)` not null, `GetObjectData`, `Refresh`, `ToString` mirrored. `SetAccessControl(security)` where `security` is the unmodified object returned by `GetAccessControl()` (as today) or `new DirectorySecurity()`: expected to persist nothing because no section is marked modified `[P]`; either way the target is the owned output directory, not the repository. `Create()` and `Create(directory.GetAccessControl())` on the existing owned directory: `Directory.CreateDirectory` returns without a Win32 create call when the path exists `[P]`, and the same calls run today against the repository root without effect. `CreateSubdirectory(OwnedAssemblyDirectory.Name)` and the security overload invoked on `new PhysicalDirectoryInfoAdapter(OwnedAssemblyDirectory.Parent)`: the subdirectory exists, so nothing is created, and the returned wrapper's `FullName` must equal `OwnedAssemblyDirectory.FullName`, which makes the delegation and the wrapping observable. +- T1b, enumeration: files over `OwnedAssemblyDirectory` with pattern `OwnedAssemblyFile.Name` (exact name, `ContainSingle`) and `*` / no pattern (`Contain`); directories over `OwnedAssemblyDirectory.Parent` with pattern `OwnedAssemblyDirectory.Name` (`ContainSingle`) and no pattern (`Contain`); `EnumerateFileSystemInfos`/`GetFileSystemInfos` on the owned directory `Contain` a `FileInfoWrapper` named `OwnedAssemblyFile.Name` and on the parent `Contain` a `DirectoryInfoWrapper` named `OwnedAssemblyDirectory.Name`. All 18 overloads stay exercised; the `SearchOption.TopDirectoryOnly` overloads use the same names. The parent is used for directory assertions because the presence of subdirectories inside `bin\Debug` (MSTest adapter satellite folders) is not guaranteed by construction, whereas the parent containing the DLL directory is. +- T1c, mutators on a missing owned directory (`MissingOwnedPath("directory")`): precondition `adapter.Exists.Should().BeFalse()`; then each of `Attributes`, `CreationTime`, `CreationTimeUtc`, `LastAccessTime`, `LastAccessTimeUtc`, `LastWriteTime`, `LastWriteTimeUtc` setters wrapped in an `Action` and asserted to throw. Predicted type `FileNotFoundException` `[P]` (`FileSystemInfo` setters route to `Directory.Set*Time`, which opens a handle on the path, and `File.SetAttributes`; both map `ERROR_FILE_NOT_FOUND` to `FileNotFoundException` when the parent exists). Pin the exact type only after the executor's first observation; until then the plan states the observation task explicitly. Both `catch (IOException)` blocks are deleted. + +**T2 `PhysicalDirectoryInfoAdapter_MissingDirectoryAndUnsupportedInfo_BranchesBehaveAsExpected`** (138-167): replace the two `GetRepositoryRoot().FullName` roots (142, 155) with `MissingOwnedPath("delete")` and `MissingOwnedPath("moved")`; add the `Exists` precondition; keep `Throw()` for `Delete()`, `Delete(true)`, `MoveTo` (today's passing assertions show `Directory.Delete`/`Directory.Move` report `DirectoryNotFoundException` for a missing source under an existing parent `[V]`); keep the reflection branch unchanged. + +**T3 `PhysicalFileInfoAdapter_PropertiesStreamsAndAccessors_MirrorFileInfo`** (170-311): `file = OwnedAssemblyFile`. Getters: `Exists` true, `Extension` `.dll`, `Length > 0`, `FullName`/`Name`/`Directory.FullName`/`DirectoryName` mirrored. Streams unchanged in shape but over the owned image: `Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite)` (identical Win32 request to the ratified sentinel), `OpenRead()` and `OpenText()` (request `FileShare.Read`; compatible with the loader's mapping of the image, which is the same access `File.ReadAllBytes(Assembly.Location)` performs `[P]`). Seam section (251-282, 301-307) unchanged except that `file` is the owned image; the delegates never touch it. `GetAccessControl` x2, `GetObjectData`, `Refresh`, `ToString`, `SetAccessControl(unmodified)` over the owned image. Setters move to a separate test T3b over `new PhysicalFileInfoAdapter(new FileInfo(MissingOwnedPath("file.txt")))` with the `Exists` precondition and one `Action` per setter (`Attributes`, six timestamps, `IsReadOnly`), predicted `FileNotFoundException` `[P]` (`File.Set*Time` opens the file for write; `IsReadOnly` set reads `Attributes` without throwing and then calls `File.SetAttributes`). The `try`/`catch (IOException)` at 186-198 and the stale comment at 182-185 are deleted. + +**T4 `PhysicalFileInfoAdapter_MissingFileBranches_ThrowOrNoOpWithoutCreatingFiles`** (314-347): source `MissingOwnedPath("source.txt")` with the `Exists` precondition; every destination and backup is another `MissingOwnedPath(...)` so that no existing file is a target under any outcome. `Delete()` `NotThrow` (`File.Delete` of a missing file under an existing parent is a no-op `[V]` by today's assertion). `CopyTo` x2 and `MoveTo`: `FileNotFoundException` (today's assertions with an existing parent `[V]`; `File.Move` checks source existence explicitly, `CopyFile` fails on the source first). `Replace` x2: today's assertions use an existing destination and a missing source; with a missing destination the predicted result is still `FileNotFoundException` (`ReplaceFile` reports `ERROR_FILE_NOT_FOUND` for either missing file) `[P]`; observe before pinning. Remove `GetRepositoryRoot`, `GetSolutionFile` and the `using System.Reflection` only if no longer used (T2 still uses reflection, so it stays). + +**D1 `Properties_ShouldMirrorWrappedDirectoryInfo`** (27-40) and **D4 `ToString_ShouldDelegateToWrappedDirectoryInfo`** (83-94): `directory = new DirectoryInfo(RootedFixturePath)` (`C:\Repo\fixture`, not a filesystem root so `Parent` is non-null); assertions unchanged (they mirror the BCL object, so `Exists` is compared, not asserted true). + +**D2 `GetDirectoriesAndGetFiles_ShouldReturnWrappedEntries`** (43-61): two wrappers, `new DirectoryInfoWrapper(OwnedAssemblyDirectory)` for files (`NotBeEmpty`, `OnlyContain(item => item is FileInfoWrapper)`, names `Contain(OwnedAssemblyFile.Name)`) and `new DirectoryInfoWrapper(OwnedAssemblyDirectory.Parent)` for directories (`NotBeEmpty`, `OnlyContain(item => item is DirectoryInfoWrapper)`, names `Contain(OwnedAssemblyDirectory.Name)`). + +**D3 `EnumerateFileSystemInfos_ShouldWrapDirectoriesAndFiles`** (64-80): same two wrappers; `Contain(item => item is FileInfoWrapper && item.Name == OwnedAssemblyFile.Name)` on the owned directory and `Contain(item => item is DirectoryInfoWrapper && item.Name == OwnedAssemblyDirectory.Name)` on its parent. Delete `GetRepositoryRoot` (375-391). + +### 4.4 Negative controls (one per rewritten test, #931 M1/M4 shape) + +Each control: Edit-tool mutation, token census, scoped build, scoped run with `ExpectedExitCode: 1`, `git checkout -- ` revert proven by `git diff --exit-code HEAD -- `, empty `git status --porcelain -- ` and SHA-256 equality with the fix-commit hash (test file) or the Phase 0 hash (production file), then a confirming passing run. Suggested mutations (the planner may substitute any single-member mutation that the test's assertion reaches; a passing control is `MUTATION PREDICTION MISMATCH`): + +| Test | Mutation (temporary, reverted) | Assertion predicted to fail | +|---|---|---| +| T1a | `PhysicalDirectoryInfoAdapter.Exists => !_directoryInfo.Exists` | `adapter.Exists.Should().BeTrue()` | +| T1b | `PhysicalDirectoryInfoAdapter.GetFiles() => Array.Empty()` | `files.Should().Contain(...)` | +| T1c | `PhysicalDirectoryInfoAdapter.CreationTime` setter body removed | `Throw<...>()` on the `CreationTime` action | +| T2 | `PhysicalDirectoryInfoAdapter.Delete()` body removed | `delete.Should().Throw()` | +| T3 | `PhysicalFileInfoAdapter.Length => 0` | `adapter.Length.Should().BeGreaterThan(0)` | +| T3b | `PhysicalFileInfoAdapter.IsReadOnly` setter body removed | `Throw<...>()` on the `IsReadOnly` action | +| T4 | `PhysicalFileInfoAdapter.MoveTo` body removed | `move.Should().Throw()` | +| D1 | `PhysicalDirectoryInfoAdapter.Name => _directoryInfo.FullName` | `wrapper.Name.Should().Be(directory.Name)` | +| D2 | `DirectoryInfoWrapper.GetFiles() => Array.Empty()` | `files.Should().NotBeEmpty()` | +| D3 | `DirectoryInfoWrapper.EnumerateFileSystemInfos() => Enumerable.Empty()` | `Contain(item => item is FileInfoWrapper && ...)` | +| D4 | `DirectoryInfoWrapper.ToString() => string.Empty` | `result.Should().Be(directory.ToString())` | + +All production mutations are reverted before the fix is committed as final; the final diff contains no production change (mirror of #931 D-10/P4-T11). + +## 5. Triage of the other root-walk sites `[V]` + +Default per the delegation: classify only; none is in scope for #940. + +| Site | What the root is used for | Class | Recommendation | +|---|---|---|---| +| `TaskMaster.Test/Ribbon/RibbonControllerTests.cs` 297-311, helper 422-437 | `File.ReadAllText` of the production source `TaskMaster/Ribbon/RibbonController.FolderTree.cs` and substring assertions (source-as-specification guard) | Legitimate repository-file read: the assertion is about the checked-in source text; `File.ReadAllText` opens with `FileShare.Read`, which admits every other read-sharing handle. Layout-dependent by design | Out of scope. If desired later, the walk-up could throw the same `InvalidOperationException` it already throws (435-436), which it does; nothing to fix | +| `TaskMaster.Test/Bootstrap/FSharpCoreHintPathAlignmentTests.cs` 47-67 (`FindRepositoryRoot`), 83-135 (`DiscoverFSharpCoreHintPaths`), consumers at 88, 156, 181; `FSharpCoreDeployedIdentityTests.cs` 76-85 | Enumerates every `*.csproj` under the root (skipping dot-directories, `packages`, `bin`, `obj`, `node_modules`) and reads HintPaths; the deployed-identity test opens `\bin\Debug\FSharp.Core.dll` read-only | Legitimate build-configuration alignment test (issue #895 family); reads the repository by intent. The class remarks (15-20) state the design | Out of scope. Not the #940 defect class | +| `UtilitiesCS.Test/EmailIntelligence/SortEmail_Tests.cs` 196, 223, 255, 280 (`GetAttachmentsInfo`, `GetAttachmentsInfoAsync`, `SaveMessageAsMsgAsync`, `SaveMessageAsMSG`), helper 393-414 | The root is passed as a folder-path string to production code that, on these paths, only combines strings: `GetAttachmentsInfo` (`SortEmail.cs` 637-659) constructs `AttachmentHelper`s and filters; `AdjustForMaxPath` (`AttachmentHelper.cs` 216-234) is pure; `SaveAs` is a Moq strict setup | Layout dependence without file-system I/O on those four call sites: replacing the walk-up with a rooted literal would be a safe hygiene change | Out of scope; candidate for a low-priority follow-up | +| `UtilitiesCS.Test/EmailIntelligence/SortEmail_Tests.cs` 237-249 (`TrySaveAttachmentAsync_WhenSaveSucceeds_ReturnsTrueAndCallsSaveAsFile`) | `SortEmail.TrySaveAttachmentAsync` (`SortEmail.cs` 889-899) calls `System.IO.Directory.CreateDirectory(Path.GetDirectoryName(filePathSave))` at 896 on the repository root before the mocked `SaveAsFile` | Same defect class as #940: a real creation API on the repository root (no-op only because the root exists; a rooted literal here would create a directory). The production method is `[ExcludeFromCodeCoverage]` (888) | Out of scope for #940; recommend a follow-up potential entry that gives `TrySaveAttachmentAsync` a directory-creation seam or asserts through the owned output directory | +| `TaskMaster.Test/OutlookObjects/Store/StoresWrapperTests.cs` 31-39 and 317-328; `TaskMaster.Test/AppGlobals/ApplicationGlobalsTests.cs` 192-199 and 365-376; `UtilitiesCS.Test/OutlookObjects/Store/StoresWrapperTests.cs` 246-254 and 294-305 (not named in issue.md) | `File.ReadAllText` of production source (`StoresWrapper.cs`, `ApplicationGlobals.cs`) for method-body regex assertions; the root is `Assembly.Location` walked up exactly three `Parent` levels with a `README.md` probe | Legitimate source-as-specification reads, same class as the Ribbon site. The fixed three-level walk is more brittle than the solution-file walk (it assumes `bin\Debug` depth) but is not the #940 defect | Out of scope; note only | + +## 6. Test-run command routes `[V]`/`[E]` + +### 6.1 Scoped run of the two classes (direct vstest, #931 `CMD-VSTEST` shape) + +- Build the test project only (rebuilds `UtilitiesCS` through the ProjectReference when a production file changed): `msbuild UtilitiesCS.Test\UtilitiesCS.Test.csproj /t:Build /m /p:Configuration=Debug /p:Platform=AnyCPU /nodeReuse:false` (MSBuild resolved through vswhere: `-latest -requires Microsoft.Component.MSBuild -find "MSBuild\**\Bin\MSBuild.exe"`). `/nodeReuse:false` is #931 D-16: resident worker nodes were the process that held the solution file in the #900 run. +- Run: `vstest.console.exe UtilitiesCS.Test\bin\Debug\UtilitiesCS.Test.dll "/Settings:TaskMaster.runsettings" /InIsolation "/TestCaseFilter:FullyQualifiedName~HelperClasses.PhysicalFileSystemAdapters_Tests|FullyQualifiedName~HelperClasses.DirectoryInfoWrapper_Tests" "/ResultsDirectory:coverage\test-results\940\" "/Logger:trx;LogFileName=.trx" "/Blame:CollectHangDump;TestTimeout=4min;HangDumpType=None"` (vstest resolved through vswhere `-latest -products * -find "Common7\IDE\Extensions\TestPlatform\vstest.console.exe"`). `TaskMaster.runsettings` carries `Workers` 0 and `Scope` ClassLevel (lines 4-7) plus a Code Coverage collector (9-29) that is recorded, not gated; `scripts/vscode/TaskMaster.cli.runsettings` (9 lines) carries the same parallel settings and no collector. Expected `total` is 12 before any split (4 + 8, section 8); after the recommended split of T1 and T3 it is 15. A `total` that differs from the expected value is a task failure, because vstest exits 0 on a zero-match filter. `/TestCaseFilter:` and `/Tests:` are mutually exclusive on the vstest command line; use the filter form only. +- Per-test filters for negative controls: `FullyQualifiedName~` (one method each; `total` 1). + +### 6.2 Full coverage run + +- Canonical route: `scripts\vscode\Invoke-MSTestWithCoverage.ps1` (parameters `SearchRoot`, `Configuration`, `CoverageOutput`, `NoExecute` only, lines 1-13). It hard-codes `/TestCaseFilter:TestCategory!=LiveOutlook` (line 91), `/InIsolation` (90), the CLI runsettings (33, 89), results directory `coverage\test-results` and log `mstest-coverage-run.trx` (297-298, not overridable from the command line), discovers `*.Test.dll` under `bin\\` excluding `obj`, `ref` and `.claude` paths (348-355), post-processes the Cobertura in place (400-402), asserts the 80/75 floors only on an unscoped run (`SearchRoot` omitted; 284-291, 406-409), prints the first-party summary (410), writes the JaCoCo projection (415-423) and the TRX summary (430-447). Invoke it by absolute script path from inside a `pwsh -NoProfile -Command` payload whose first statement is `Set-Location -LiteralPath ""`; `pwsh -WorkingDirectory` does not resolve the `-File` path. +- Known local exclusion: the four `UtilitiesCS.Test` classes `HelperClasses.ShellUtilities_Tests`, `HelperClasses.ShellUtilitiesStatic_Tests`, `HelperClasses.SysImageListHelperTests`, `EmailIntelligence.OSBrowser_Tests` stall a local vstest run on this workstation (#931 fact 16; `STALL-PROBE: REPRODUCES` in the #931 run). The runner offers no filter extension point, so #931 used the `DIRECT` route: the runner's own functions dot-sourced by absolute path, `dotnet-coverage collect --output coverage\.cobertura.xml --output-format cobertura --settings -- 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\\" "/Logger:trx;LogFileName=.trx" "/Blame:CollectHangDump;TestTimeout=4min;HangDumpType=None"`, followed by the same post-processing helpers (`ConvertTo-KoverageCoberturaXml`, threshold assertions, `Get-CoberturaFirstPartyCoverageReport`, `ConvertTo-JacocoPackageProjection`, `Assert-JacocoProjectionReconciliation`, `Get-TrxRunSummary`/`Format-TrxRunSummary`) (`post-merge-coverage-final.md` lines 6-8). A Phase 0 stall probe should re-record `STALL-PROBE` and select the route, as #931 P0-T9 did. `DictionaryExtensions_Tests.TryAddValuesAsync_UpdatesExistingValue` is a known intermittent failure (#780) and is not retried. +- Committed forms: JaCoCo package projection, one-line first-party summary and TRX-derived summary only; raw Cobertura and TRX stay under `coverage\` (CLAUDE.md "Committed Test Evidence Format"). +- Package comparison: the most recent recorded UtilitiesCS figures are `LINE 38811/43423`, `BRANCH 9413/11271` (`post-merge-coverage-final.md`, post-merge of #931 at HEAD `55a50e92`); this item must take its own baseline in Phase 0 rather than reuse them, because the collector shows run-to-run variance of a few lines on unchanged code (same document, lines 163-173). + +### 6.3 Feasibility of `pwsh` for the executor + +- This research session had no Bash tool at all, so nothing above was executed here. The #931 plan (line 13) required a later, non-isolated executor with `pwsh` available and probed the channel in P0-T4, stopping with `CHANNEL UNAVAILABLE` if refused; that run completed, so the channel exists for a non-isolated executor from the item worktree. The delegation's BASH DISCIPLINE admits single commands whose first token is `git`, `pwsh` or `poetry`, which matches the `pwsh -NoProfile -Command ''` shape #931 used (outer single quotes, first statement `Set-Location -LiteralPath ""`, no single quote inside the payload, string literals double-quoted). +- The plan should therefore state the same session requirement and the same P0 channel probe, and must not assume `pwsh` from an isolated agent. + +## 7. Risks and open questions + +1. `[P]` Exception types on a missing owned path. `FileNotFoundException` is predicted for the `Attributes`/timestamp/`IsReadOnly` setters and for `Replace` with both files missing; `DirectoryNotFoundException` and `FileNotFoundException` for `Delete`/`MoveTo`/`CopyTo` are already observed by today's passing assertions with an existing parent. The plan must include a fail-before observation task that runs the rewritten tests once, records each thrown type from the TRX message, and only then pins the assertion. A mismatch is corrected by pinning the observed derived type, never by widening to `Exception` or by catching. +2. `[E]`/`[P]` `Assembly.Location` under the two coverage routes. The DIRECT route resolved it to `\UtilitiesCS.Test\bin\Debug\UtilitiesCS.Test.dll` (#931 M4) and the post-merge DIRECT coverage run passed all 7323 tests with `FileInfoWrapper_Tests` relying on it. The RUNNER route is the same `dotnet-coverage collect -- vstest.console.exe ... /InIsolation` invocation with a different filter, and MSTest does not shadow-copy, so the same path is expected; not separately observed. +3. `[P]` `OpenRead()`/`OpenText()` request `FileShare.Read` against the loaded image. The loader's mapping of the image holds read/execute access with read sharing, and `FILE_EXECUTE` is treated as read for sharing compatibility, so the open is expected to succeed; the residual (a third-party process holding the test DLL with `FileShare.None`) is the same residual #931 accepted for the sentinel. If the executor observes an `IOException` here, the remedy is to route `OpenRead`/`OpenText` through the existing seam pattern (a production change of one file) rather than to catch. +4. `[P]` `SetAccessControl` with an unmodified security object is expected to perform no Win32 write (`NativeObjectSecurity.Persist` returns when no section is marked for persistence). If it does write, the target is the test's own output entry, not a repository file; the current tests perform the same call on the repository root without failure. +5. Stale entries under the owned directory. `MissingOwnedPath` names are chosen so nothing in the build creates them, and every mutating test asserts `Exists.Should().BeFalse()` before Act so a stale entry fails loudly instead of being deleted or moved. Names must be unique per test so parallel class execution cannot collide (both classes run concurrently under `ClassLevel`; neither creates anything). +6. Parent of the owned directory. `OutputPath` is `bin\Debug\` (csproj line 51; `bin\x86\Debug\` for x86 at 66/70), so the owned directory is at least two levels deep and `Parent` is non-null under every configured build; the assumption is stated in the fixture comment. +7. File size. `PhysicalFileSystemAdapters_Tests.cs` is 389 lines; splitting T1 into three tests and T3 into two adds test scaffolding. The rewrite removes 29 lines of helpers (349-377), both `try`/`catch` blocks and the stale comments, so the file is expected to stay under 500, but the 500-line gate (#931 P4-T9 shape) must run after the repository-wide format. `DirectoryInfoWrapper_Tests.cs` (394) shrinks. +8. Coverage. Every adapter member exercised today stays exercised, so the UtilitiesCS package rate is expected unchanged; the per-package not-lower comparison (D-7) against this item's own Phase 0 baseline is still required, with the one identical re-measurement rule for collector variance. Pre-existing uncovered adapter members (`Create`, `CreateText`, `Decrypt`, `Encrypt`, success paths of `CopyTo`/`MoveTo`/`Replace`) are unchanged and out of scope. +9. `IOException` swallow removal is complete only if no `catch` remains in either file; a post-edit census token `catch (IOException)` = 0 per file and `GetRepositoryRoot` = 0, `GetSolutionFile` = 0, `TaskMaster.sln` = 0 should gate the fix (mirror of #931 P2-T6 tokens). +10. Open question for the orchestrator: whether to file the `SortEmail_Tests` `TrySaveAttachmentAsync` site (section 5, `Directory.CreateDirectory` on the repository root) as a follow-up potential entry now, given that it is the only other site in the same defect class. + +## 8. Numeric Derivation Evidence + +Claim: 12 test methods across the two files; 8 affected (call a root/solution helper), 4 unaffected. Claim: 2 `IOException` swallow sites. + +- Complete Family: `[TestMethod]`-attributed methods in `UtilitiesCS.Test/HelperClasses/PhysicalFileSystemAdapters_Tests.cs` and `UtilitiesCS.Test/HelperClasses/DirectoryInfoWrapper_Tests.cs`. +- Exhaustive Search Scope: both files in full (Read of every line; Grep over the `HelperClasses` directory with a glob naming exactly the two files). +- Inclusion Rules: a method carrying `[TestMethod]`; "affected" when its body (or a helper it calls) invokes `GetRepositoryRoot()` or `GetSolutionFile()` or names `TaskMaster.sln`. +- Exclusion Rules: private helpers, nested classes (`UnsupportedInfo`), `[TestClass]` declarations. +- Primary Search Strategy: Read tool, both files, `[TestMethod]` attribute lines. Primary Member Set: Physical 15, 137, 169, 313; DIW 16, 26, 42, 63, 82, 96, 186, 331. Primary Count: 12. Affected subset by reading each body: Physical T1 (19), T2 (142, 155), T3 (173 via `GetSolutionFile`), T4 (317, 318); DIW D1 (30), D2 (46), D3 (67), D4 (86). Affected count: 8. Swallow sites by reading: 43 and 195. Count: 2. +- Cross-check Search Strategy: Grep `^\s*public (async )?(void|Task) \w+\(` with the two-file glob (method declarations, independent of the attribute), plus Grep `\[TestMethod\]` in count mode, plus the repository-wide Grep `TaskMaster\.sln|GetRepositoryRoot|FindRepositoryRoot` mapped to enclosing methods. Cross-check Member Set: declarations at DIW 17, 27, 43, 64, 83, 97, 187, 332 and Physical 16, 138, 170, 314 (12); count mode reported `PhysicalFileSystemAdapters_Tests.cs:4`, `DirectoryInfoWrapper_Tests.cs:8`, 12 total; helper call lines DIW 30, 46, 67, 86 and Physical 19, 142, 155, 173, 317, 318, 375-376 map to 4 + 4 distinct test methods. Cross-check Count: 12 total, 8 affected. Swallow cross-check: the #931 spec line 65 and handoff record name lines 43 and 195 (`[E]`), 2. +- Member-set Comparison: the normalized sets (method names) are identical between primary and cross-check for the population (12) and the affected subset (8); the swallow-site line numbers agree (2). Assertion proposed. + +## 9. Testing implications for the plan + +- Bugfix workflow order: Phase 0 baseline (scoped run of the two classes recording pass/fail and, separately, the stall probe and a coverage baseline), Phase 1 fail-before dossier (the defect cannot be reproduced deterministically without holding the solution file from another process, which is prohibited; the exception-type observation run of the rewritten tests doubles as the pre-pin evidence), Phase 2 rewrite (test files only), fix commit, Phase 3 negative controls (section 4.4, each reverted and hash-proven), Phase 4 full toolchain (csharpier format/check, analyzers rebuild, nullable rebuild, coverage route with the stall exclusion), package comparison, hygiene sweep (no absolute paths, no TRX/XML committed), follow-up handoff. +- Determinism checks the plan should gate on added lines: no `Thread.Sleep`, `Task.Delay`, `[Timeout`, `DoNotParallelize`, `Retry`, `Path.GetTemp`, `File.Create`, `File.WriteAll`, `Directory.CreateDirectory` in either test file; `catch (IOException)` = 0; `GetRepositoryRoot` = 0; `GetSolutionFile` = 0; `TaskMaster.sln` = 0; `Assembly.Location` present. +- Both classes remain parallel-safe under `Workers=0`/`ClassLevel`: they only read the owned output directory and its parent, and their mutating calls target entries that do not exist (asserted) or are no-ops on existing owned entries. diff --git a/docs/features/potential/promoted/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file.md b/docs/features/potential/promoted/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file.md new file mode 100644 index 000000000..76b847d25 --- /dev/null +++ b/docs/features/potential/promoted/2026-09-29-filesystem-wrapper-tests-open-repository-solution-file.md @@ -0,0 +1,63 @@ +# filesystem-wrapper-tests-open-repository-solution-file (Issue #940) + +- Date captured: 2026-09-29 +- Author: Dan Moisan +- Status: Promoted -> docs/features/active/filesystem-wrapper-tests-open-repository-solution-file/ (Issue #940) + +> Automation note: Keep the section headings below unchanged; the promotion tooling maps each of them into the GitHub bug issue template. + +- Issue: #940 +- Issue URL: https://github.com/drmoisan/TaskMaster/issues/940 +- Last Updated: 2026-09-30 +## Summary + +Several UtilitiesCS.Test file-system wrapper tests locate the repository root at run time and open or enumerate the real `TaskMaster.sln`. This is the same defect class that #931 (PR #939) fixed in `FileInfoWrapper_Tests`: the outcome of a unit test depends on repository layout and on whether another process holds the solution file open. + +## Environment + +- OS/version: Windows 11 (local) and windows-latest (CI) +- Python version: n/a (C# / MSTest, .NET Framework 4.8) +- Command/flags used: `Invoke-MSTestWithCoverage.ps1` (parallel run, Workers=0, Scope=ClassLevel) +- Data source or fixture: the repository's own `TaskMaster.sln` + +## Steps to Reproduce + +1. Hold `TaskMaster.sln` open with a share mode that excludes readers (for example a resident MSBuild node or an IDE). +2. Run `UtilitiesCS.Test` with the standard coverage route. +3. Alternatively, run the test assembly from a location whose ancestors contain no `TaskMaster.sln`. + +## Expected Behavior + +The wrapper tests verify wrapper delegation through test-owned fixtures or mocks, independent of repository layout and of other processes. + +## Actual Behavior + +- `UtilitiesCS.Test/HelperClasses/PhysicalFileSystemAdapters_Tests.cs` walks up to the repository root (line ~361), builds a `FileInfo` over `TaskMaster.sln` (line ~376), asserts it is enumerated (lines 110-129), and swallows `IOException` in places, which hides the contention instead of removing it. +- `UtilitiesCS.Test/HelperClasses/DirectoryInfoWrapper_Tests.cs` asserts that `TaskMaster.sln` is enumerated from the repository root (lines 60 and 79, root walk at line ~381). + +## Logs / Screenshots + +- [ ] Attached minimal logs or screenshot +- Snippet: see the PR #939 body, section Follow-ups, items 1 and 2. + +## Impact / Severity + +- [ ] Blocker +- [ ] High +- [x] Medium +- [ ] Low + +## Suspected Cause / Notes + +A unit test depends on environment state it does not control, which is the root cause recorded for #931. Other root-walk sites to triage under the same fix: `TaskMaster.Test/Ribbon/RibbonControllerTests.cs:429`, `TaskMaster.Test/Bootstrap/FSharpCoreHintPathAlignmentTests.cs:27`, and `UtilitiesCS.Test/EmailIntelligence/SortEmail_Tests.cs:405`. Some of these may legitimately read repository files, for example build-configuration alignment tests. Classify each one before changing it. Source: `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`. + +## Proposed Fix / Validation Ideas + +- [ ] Reuse the #931 pattern: exercise delegation through the internal `IFileInfo`/`IDirectoryInfo` seams with Moq, or through a test-owned stream opened with `FileShare.ReadWrite`. Use no temporary files. +- [ ] Remove the `IOException` swallowing. +- [ ] Negative control: show that each rewritten test fails against a deliberately broken wrapper. + +## Next Step + +- [x] Promote to GitHub issue (bug-report template) +- [ ] Move to active fix folder / branch