diff --git a/UtilitiesCS.Test/Extensions/DfDeedleEtlTimeoutTests.cs b/UtilitiesCS.Test/Extensions/DfDeedleEtlTimeoutTests.cs
new file mode 100644
index 000000000..97a235cea
--- /dev/null
+++ b/UtilitiesCS.Test/Extensions/DfDeedleEtlTimeoutTests.cs
@@ -0,0 +1,228 @@
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using Deedle;
+using FluentAssertions;
+using Microsoft.Extensions.Time.Testing;
+using Microsoft.Office.Interop.Outlook;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using UtilitiesCS;
+using UtilitiesCS.Test.TestHelpers;
+using Outlook = Microsoft.Office.Interop.Outlook;
+
+namespace UtilitiesCS.Test.Extensions
+{
+ ///
+ /// Deadline-expiry regression tests for .
+ /// The ETL deadline is placed under an injected clock, so no test here waits on wall time.
+ ///
+ [TestClass]
+ // Not parallelized: this class drives a real Task.Run gate and shares the DfDeedle logger,
+ // exactly as DfDeedleQfcColumnTimeoutTests does.
+ [DoNotParallelize]
+ public class DfDeedleEtlTimeoutTests
+ {
+ /// A ProgressTracker whose Report overrides do nothing, so progress reporting
+ /// cannot influence timing or produce output during the test.
+ private sealed class SilentProgressTracker : ProgressTracker
+ {
+ public SilentProgressTracker()
+ : base(new CancellationTokenSource()) { }
+
+ public override void Report((int Value, string JobName) report) { }
+
+ public override void Report(double value) { }
+
+ public override void Report(double value, string jobName) { }
+ }
+
+ private static ProgressTracker CreateProgressTracker()
+ {
+ return new ProgressTracker(new SilentProgressTracker(), allocation: 100, startingAt: 0);
+ }
+
+ ///
+ /// Builds a folder whose UserDefinedProperties collection carries a Triage entry, so
+ /// EnsureTriageColumnExists returns true and no modal-dialog seam is reached.
+ ///
+ private static Mock BuildFolderWithTriageUdp()
+ {
+ var udp = new Mock(MockBehavior.Loose);
+ udp.SetupGet(p => p.Name).Returns("Triage");
+
+ var udpList = new List { udp.Object };
+ var udps = new Mock(MockBehavior.Loose);
+ udps.Setup(u => u.GetEnumerator()).Returns(udpList.GetEnumerator());
+
+ var folder = new Mock(MockBehavior.Loose);
+ folder.SetupGet(f => f.UserDefinedProperties).Returns(udps.Object);
+ folder.SetupGet(f => f.StoreID).Returns("store-1");
+ folder.SetupGet(f => f.Name).Returns("Inbox");
+ return folder;
+ }
+
+ ///
+ /// Builds a strict one-row Table behind a strict Explorer. The two supplied actions are
+ /// the test-owned gates: the first runs inside Columns.Add("SentOn"), which is the first
+ /// call AddQfcColumns makes, and the second runs inside GetNextRow, which is the first
+ /// call the ETL row reader makes. Blocking on them makes the arming order deterministic.
+ ///
+ private static Mock BuildExplorer(
+ Mock folder,
+ System.Action onAddSentOnColumn,
+ System.Action onGetNextRow
+ )
+ {
+ var row = new Mock(MockBehavior.Strict);
+ row.Setup(x => x.GetValues())
+ .Returns(new object[] { "entry-1", "IPM.Note", "2024-01-01", "conv-raw", "A" });
+ row.Setup(x => x.BinaryToString(4)).Returns("conv-1");
+
+ var columns = new Mock(MockBehavior.Loose);
+ var table = new Mock(MockBehavior.Strict);
+ table.SetupGet(x => x.Columns).Returns(columns.Object);
+ table.Setup(x => x.MoveToStart());
+ table.Setup(x => x.GetRowCount()).Returns(1);
+
+ var currentRow = 0;
+ table.Setup(x => x.EndOfTable).Returns(() => currentRow >= 1);
+ table
+ .Setup(x => x.GetNextRow())
+ .Returns(() =>
+ {
+ onGetNextRow();
+ currentRow++;
+ return row.Object;
+ });
+
+ var columnNames = new[]
+ {
+ "EntryID",
+ "MessageClass",
+ "SentOn",
+ "ConversationId",
+ "Triage",
+ };
+ columns.Setup(x => x.Count).Returns(columnNames.Length);
+ for (var index = 0; index < columnNames.Length; index++)
+ {
+ var column = new Mock(MockBehavior.Strict);
+ column.SetupGet(x => x.Name).Returns(columnNames[index]);
+ columns.Setup(x => x[index + 1]).Returns(column.Object);
+ }
+
+ columns.Setup(x => x.Add("SentOn")).Callback(onAddSentOnColumn).Returns((Column)null);
+
+ var tableView = new Mock(MockBehavior.Strict);
+ tableView.Setup(x => x.GetTable()).Returns(table.Object);
+
+ var explorer = new Mock(MockBehavior.Strict);
+ explorer.SetupGet(x => x.CurrentView).Returns(tableView.Object);
+ explorer.SetupGet(x => x.CurrentFolder).Returns(folder.Object);
+ return explorer;
+ }
+
+ ///
+ /// When the ETL row-read deadline expires, EtlAsync swallows the TimeoutException and
+ /// returns a null data array. GetEmailDataInViewAsync must name the folder in an
+ /// InvalidOperationException rather than dereferencing the null snapshot.
+ /// The clock is an so it is advanced only after
+ /// the deadline it is meant to expire has actually been armed.
+ ///
+ [TestMethod]
+ public async Task GetEmailDataInViewAsync_EtlDeadlineExpires_ThrowsInvalidOperationNamingFolder()
+ {
+ // Arrange: two test-owned gates make the timer-arming order deterministic. Without
+ // them the number of Armed signals before the ETL deadline depends on a pool race.
+ var gateA = new ManualResetEventSlim(false);
+ var gateB = new ManualResetEventSlim(false);
+ var barrier = new ArmingBarrierTimeProvider(new FakeTimeProvider());
+ var explorer = BuildExplorer(BuildFolderWithTriageUdp(), gateA.Wait, gateB.Wait);
+ var progress = CreateProgressTracker();
+
+ try
+ {
+ // Act
+ Task > call = DfDeedle.GetEmailDataInViewAsync(
+ explorer.Object,
+ CancellationToken.None,
+ new CancellationTokenSource(),
+ progress,
+ timeProvider: barrier
+ );
+
+ // Timer 1: the 3000 ms column-add deadline, deterministic because the adder is
+ // blocked on gate A and so the work task cannot complete first.
+ await barrier.Armed;
+ barrier.ReArm();
+ gateA.Set();
+
+ // Timer 2: the 250 ms ETL hop deadline, deterministic because the row read is
+ // blocked on gate B. Advancing before this timer exists would hang the test.
+ await barrier.Armed;
+ barrier.Advance(250);
+
+ // Assert
+ await FluentActions
+ .Awaiting(() => call)
+ .Should()
+ .ThrowAsync()
+ .WithMessage("*Inbox*");
+ }
+ finally
+ {
+ // Release both gates so the orphaned Task.Run bodies complete.
+ gateA.Set();
+ gateB.Set();
+ }
+ }
+
+ ///
+ /// The deterministic green path: every deadline on the call chain is armed on a clock
+ /// that never moves, so no deadline can fire regardless of host load.
+ ///
+ [TestMethod]
+ public async Task GetEmailDataInViewAsync_ClockNeverAdvances_ReturnsOneRowFrame()
+ {
+ // Arrange: no gate is engaged, so the production path runs to completion.
+ var explorer = BuildExplorer(BuildFolderWithTriageUdp(), () => { }, () => { });
+ var progress = CreateProgressTracker();
+
+ // Act
+ Frame result = await DfDeedle.GetEmailDataInViewAsync(
+ explorer.Object,
+ CancellationToken.None,
+ new CancellationTokenSource(),
+ progress,
+ timeProvider: new FakeTimeProvider()
+ );
+
+ // Assert
+ result.RowCount.Should().Be(1);
+ result
+ .ColumnKeys.Should()
+ .Contain(new[] { "EntryId", "MessageClass", "ConversationId" });
+ }
+
+ ///
+ /// The default-delegate path of the synchronous GetEmailDataInView. Supplying no etl
+ /// argument runs the production DefaultTableEtl over the table, which is what every
+ /// existing caller does now that the TableEtlInvoker static has been removed.
+ ///
+ [TestMethod]
+ public void GetEmailDataInView_NoEtlArgument_UsesProductionDefaultDelegate()
+ {
+ // Arrange
+ var explorer = BuildExplorer(BuildFolderWithTriageUdp(), () => { }, () => { });
+
+ // Act
+ Frame result = DfDeedle.GetEmailDataInView(explorer.Object);
+
+ // Assert
+ result.Should().NotBeNull();
+ result.RowCount.Should().Be(1);
+ }
+ }
+}
diff --git a/UtilitiesCS.Test/Extensions/DfDeedleQfcColumnTimeoutTests.cs b/UtilitiesCS.Test/Extensions/DfDeedleQfcColumnTimeoutTests.cs
index 0e28f6e4b..a96ff378e 100644
--- a/UtilitiesCS.Test/Extensions/DfDeedleQfcColumnTimeoutTests.cs
+++ b/UtilitiesCS.Test/Extensions/DfDeedleQfcColumnTimeoutTests.cs
@@ -10,6 +10,7 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using UtilitiesCS.OutlookObjects.Fields;
+using UtilitiesCS.Test.TestHelpers;
namespace UtilitiesCS.Test.Extensions
{
@@ -36,54 +37,6 @@ public class DfDeedleQfcColumnTimeoutTests
"LastModificationTime",
};
- ///
- /// A forwarding every member to an inner
- /// unchanged and, after forwarding CreateTimer ,
- /// completing a signal that the production loop has armed its next deadline.
- ///
- ///
- /// Forwarding keeps timer ownership with the inner provider. Consecutive Advance
- /// calls are prohibited: each deadline is armed only after the previous proxy faults, so
- /// advancing past a deadline the loop has not created hangs the test. TrySetResult
- /// is used because the loop can arm one more timer than a test drives.
- ///
- private sealed class ArmingBarrierTimeProvider : TimeProvider
- {
- private readonly FakeTimeProvider _inner;
- private volatile TaskCompletionSource _armed = NewSignal();
-
- internal ArmingBarrierTimeProvider(FakeTimeProvider inner) => _inner = inner;
-
- internal Task Armed => _armed.Task;
-
- internal void ReArm() => _armed = NewSignal();
-
- internal void Advance(int ms) => _inner.Advance(TimeSpan.FromMilliseconds(ms));
-
- public override DateTimeOffset GetUtcNow() => _inner.GetUtcNow();
-
- public override long GetTimestamp() => _inner.GetTimestamp();
-
- public override TimeZoneInfo LocalTimeZone => _inner.LocalTimeZone;
- public override long TimestampFrequency => _inner.TimestampFrequency;
-
- public override ITimer CreateTimer(
- TimerCallback callback,
- object state,
- TimeSpan dueTime,
- TimeSpan period
- )
- {
- var timer = _inner.CreateTimer(callback, state, dueTime, period);
- _armed.TrySetResult(true);
- return timer;
- }
-
- // Continuations run asynchronously so a signal never resumes a test inline.
- internal static TaskCompletionSource NewSignal() =>
- new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
- }
-
///
/// Injectable column adder that counts invocations, signals entry, then blocks on the gate
/// so a deadline is the only thing able to end the attempt. The gate is a delegate.
diff --git a/UtilitiesCS.Test/Extensions/DfDeedle_COM_Tests.cs b/UtilitiesCS.Test/Extensions/DfDeedle_COM_Tests.cs
index 533e4f44e..d7e6fa1cd 100644
--- a/UtilitiesCS.Test/Extensions/DfDeedle_COM_Tests.cs
+++ b/UtilitiesCS.Test/Extensions/DfDeedle_COM_Tests.cs
@@ -8,6 +8,7 @@
using System.Threading.Tasks;
using Deedle;
using FluentAssertions;
+using Microsoft.Extensions.Time.Testing;
using Microsoft.Office.Interop.Outlook;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
@@ -22,14 +23,16 @@ namespace UtilitiesCS.Test.Extensions
///
/// Purpose:
/// Covers the COM-interface paths (HasUserDefinedProperty, EnsureTriageColumnExists,
- /// AddQfcColumns), the testability-seam paths (GetEmailDataFromTable,
- /// GetEmailDataInView via TableEtlInvoker, AddQfcColumnsAsync), the storage-folder
- /// factory paths (FromDefaultFolder), and the pure Deedle extension methods
+ /// AddQfcColumns), the injected-ETL paths (GetEmailDataFromTable, GetEmailDataInView
+ /// via its optional etl parameter, AddQfcColumnsAsync), the storage-folder factory
+ /// paths (FromDefaultFolder), and the pure Deedle extension methods
/// (PrintToLog, DropFirstN, Exclude, GetDuplicateEntriesByColumn).
///
/// Usage:
- /// Every test that modifies a static seam saves/restores the original value to
- /// prevent side effects across the test run.
+ /// The two ETL delegates are ordinary optional parameters, so no test in this class
+ /// writes process-wide state to inject ETL data.
+ /// MessageBoxInvoker is the one remaining static seam: every test that swaps it
+ /// restores it in a finally block, and it has no reader on any parallel-phase path.
///
[TestClass]
public class DfDeedle_COM_Tests
@@ -364,7 +367,7 @@ public void GetEmailDataFromTable_OneRow_ReturnsFrameWithExpectedFields()
}
// ----------------------------------------------------------------
- // GetEmailDataInView (using TableEtlInvoker seam)
+ // GetEmailDataInView (using the optional etl parameter)
// ----------------------------------------------------------------
[TestMethod]
@@ -396,22 +399,15 @@ public void GetEmailDataInView_WithInjectedEtlResult_ReturnsPopulatedFrame()
mockExplorer.SetupGet(e => e.CurrentView).Returns(mockTableView.Object);
mockExplorer.SetupGet(e => e.CurrentFolder).Returns(folderWithTriage.Object);
- var originalEtl = DfDeedle.TableEtlInvoker;
- DfDeedle.TableEtlInvoker = _ => (injectedData, injectedColInfo);
-
- try
- {
- // Act
- Frame df = DfDeedle.GetEmailDataInView(mockExplorer.Object);
+ // Act
+ Frame df = DfDeedle.GetEmailDataInView(
+ mockExplorer.Object,
+ etl: _ => (injectedData, injectedColInfo)
+ );
- // Assert
- df.Should().NotBeNull();
- df.RowCount.Should().Be(1);
- }
- finally
- {
- DfDeedle.TableEtlInvoker = originalEtl;
- }
+ // Assert
+ df.Should().NotBeNull();
+ df.RowCount.Should().Be(1);
}
///
@@ -478,7 +474,8 @@ public async Task GetEmailDataInViewAsync_SeparatesTableSnapshotFromDataFrameTra
explorer.Object,
CancellationToken.None,
new CancellationTokenSource(),
- progress
+ progress,
+ timeProvider: new FakeTimeProvider()
);
result.RowCount.Should().Be(1);
@@ -722,7 +719,7 @@ public void GetDuplicateEntriesByColumn_ReturnsDuplicateValues()
}
// ----------------------------------------------------------------
- // FromDefaultFolder(Store) — non-null table path (StoreTableEtlInvoker seam)
+ // FromDefaultFolder(Store) — non-null table path (optional etl parameter)
// ----------------------------------------------------------------
[TestMethod]
@@ -732,7 +729,7 @@ public void FromDefaultFolder_Store_WithInjectedEtlResult_ReturnsPopulatedFrame(
// and mock folder.GetTable() (the COM method on MAPIFolder) to return a mock Table.
// GetTable(Store,...) is a static extension method that Moq cannot intercept directly;
// the correct seam is the underlying COM interface chain.
- // StoreTableEtlInvoker is replaced to supply known data without live COM calls.
+ // The optional etl parameter supplies known data without live COM calls.
var mockColumns = new Mock(MockBehavior.Loose);
var mockTable = new Mock(MockBehavior.Loose);
mockTable.SetupGet(t => t.Columns).Returns(mockColumns.Object);
@@ -759,27 +756,18 @@ public void FromDefaultFolder_Store_WithInjectedEtlResult_ReturnsPopulatedFrame(
["StoreId"] = 5,
};
- var originalSeam = DfDeedle.StoreTableEtlInvoker;
- DfDeedle.StoreTableEtlInvoker = _ => (injectedData, injectedColInfo);
+ // Act
+ Frame df = DfDeedle.FromDefaultFolder(
+ mockStore.Object,
+ OlDefaultFolders.olFolderInbox,
+ Array.Empty(),
+ Array.Empty(),
+ etl: _ => (injectedData, injectedColInfo)
+ );
- try
- {
- // Act
- Frame df = DfDeedle.FromDefaultFolder(
- mockStore.Object,
- OlDefaultFolders.olFolderInbox,
- Array.Empty(),
- Array.Empty()
- );
-
- // Assert: frame is non-null and has data from the injected ETL result.
- df.Should().NotBeNull();
- df.RowCount.Should().Be(1);
- }
- finally
- {
- DfDeedle.StoreTableEtlInvoker = originalSeam;
- }
+ // Assert: frame is non-null and has data from the injected ETL result.
+ df.Should().NotBeNull();
+ df.RowCount.Should().Be(1);
}
// ----------------------------------------------------------------
@@ -793,7 +781,7 @@ public void FromDefaultFolder_Stores_FirstStoreHasData_ReturnsNonEmptyFrame()
// and mock folder.GetTable() (the COM method on MAPIFolder) to return a mock Table.
// GetTable(Store,...) is a static extension method that Moq cannot intercept directly;
// the correct seam is the underlying COM interface chain.
- // StoreTableEtlInvoker supplies known data with an EntryID column so that
+ // The optional etl parameter supplies known data with an EntryID column so that
// the IndexRowsWith and frame-assembly paths inside the method are exercised.
var mockColumns = new Mock(MockBehavior.Loose);
var mockTable = new Mock(MockBehavior.Loose);
@@ -824,27 +812,18 @@ public void FromDefaultFolder_Stores_FirstStoreHasData_ReturnsNonEmptyFrame()
var mockStores = new Mock(MockBehavior.Loose);
mockStores.Setup(s => s.GetEnumerator()).Returns(singleStoreList.GetEnumerator());
- var originalSeam = DfDeedle.StoreTableEtlInvoker;
- DfDeedle.StoreTableEtlInvoker = _ => (injectedData, injectedColInfo);
+ // Act
+ Frame df = DfDeedle.FromDefaultFolder(
+ mockStores.Object,
+ OlDefaultFolders.olFolderInbox,
+ Array.Empty(),
+ Array.Empty(),
+ etl: _ => (injectedData, injectedColInfo)
+ );
- try
- {
- // Act
- Frame df = DfDeedle.FromDefaultFolder(
- mockStores.Object,
- OlDefaultFolders.olFolderInbox,
- Array.Empty(),
- Array.Empty()
- );
-
- // Assert: frame is non-null and contains data from the single store.
- df.Should().NotBeNull();
- df.RowCount.Should().Be(1);
- }
- finally
- {
- DfDeedle.StoreTableEtlInvoker = originalSeam;
- }
+ // Assert: frame is non-null and contains data from the single store.
+ df.Should().NotBeNull();
+ df.RowCount.Should().Be(1);
}
private static ProgressTracker CreateProgressTracker()
diff --git a/UtilitiesCS.Test/Extensions/DictionaryExtensions_Tests.cs b/UtilitiesCS.Test/Extensions/DictionaryExtensions_Tests.cs
index 790426f82..ff5d8d208 100644
--- a/UtilitiesCS.Test/Extensions/DictionaryExtensions_Tests.cs
+++ b/UtilitiesCS.Test/Extensions/DictionaryExtensions_Tests.cs
@@ -248,6 +248,28 @@ public async Task TryAddValuesAsync_UpdatesExistingValue()
dictionary["value"].Should().Be(10);
}
+ ///
+ /// Locks the surviving cancellation contract after the internal 500 ms window was
+ /// removed: cancellation is governed solely by the caller's token, so a token that is
+ /// already cancelled still yields TaskCanceledException and leaves the value untouched.
+ ///
+ [TestMethod]
+ public async Task TryAddValuesAsync_PreCancelledToken_ThrowsTaskCanceledAndLeavesValueUnchanged()
+ {
+ // Arrange
+ var dictionary = new ConcurrentDictionary();
+ dictionary["value"] = 8;
+ var cts = new CancellationTokenSource();
+ cts.Cancel();
+
+ // Act
+ Func act = () => dictionary.TryAddValuesAsync("value", 2, cts.Token);
+
+ // Assert
+ await act.Should().ThrowAsync();
+ dictionary["value"].Should().Be(8);
+ }
+
[TestMethod]
public void UpdateOrRemove_RemovesOrUpdatesBasedOnCondition()
{
diff --git a/UtilitiesCS.Test/HelperClasses/NLogTraceWriter_Test.cs b/UtilitiesCS.Test/HelperClasses/NLogTraceWriter_Test.cs
index 60ad66b4c..7f89c1629 100644
--- a/UtilitiesCS.Test/HelperClasses/NLogTraceWriter_Test.cs
+++ b/UtilitiesCS.Test/HelperClasses/NLogTraceWriter_Test.cs
@@ -14,13 +14,10 @@ public class NLogTraceWriter_Test
private MockRepository mockRepository;
private Mock mockLogger;
private Mock mockTraceWriter;
- private System.IO.TextWriter originalOut;
[TestInitialize]
public void TestInitialize()
{
- this.originalOut = Console.Out;
- Console.SetOut(new DebugTextWriter());
this.mockRepository = new MockRepository(MockBehavior.Loose);
this.mockLogger = SetupLogger();
this.mockTraceWriter = this.mockRepository.Create();
@@ -50,12 +47,6 @@ internal void PrintLogCall(string loggerName, object message, Exception ex)
Console.WriteLine($"Exception: {ex}");
}
- [TestCleanup]
- public void TestCleanup()
- {
- Console.SetOut(this.originalOut);
- }
-
[TestMethod]
public void Trace_TraceLevel_Error()
{
diff --git a/UtilitiesCS.Test/HelperClasses/PrettyPrint_Tests.cs b/UtilitiesCS.Test/HelperClasses/PrettyPrint_Tests.cs
index 5e0cf1d99..43973cb85 100644
--- a/UtilitiesCS.Test/HelperClasses/PrettyPrint_Tests.cs
+++ b/UtilitiesCS.Test/HelperClasses/PrettyPrint_Tests.cs
@@ -11,12 +11,6 @@
namespace UtilitiesCS.Test.HelperClasses
{
- // [DoNotParallelize] — DataFramePrettyHelpers_RenderRowsMarkdownAndConsoleOutput
- // captures and restores Console.Out, which is process-wide state. Under the
- // class-level parallel scope set in TaskMaster.runsettings, a sibling test
- // class's Console.SetOut overrides this class's redirect mid-test, causing
- // PrettyPrint's Console.WriteLine output to land in the wrong writer.
- [DoNotParallelize]
[TestClass]
public class PrettyPrint_Tests
{
@@ -183,7 +177,7 @@ public void ToFormattedTextAndMarkdown_ForTwoDimensionalArray_RenderAllPrimitive
}
[TestMethod]
- public void DataFramePrettyHelpers_RenderRowsMarkdownAndConsoleOutput()
+ public void DataFramePrettyHelpers_RenderRowsMarkdownAndWriterOutput()
{
// Arrange
var frame = new DataFrame(
@@ -191,32 +185,48 @@ public void DataFramePrettyHelpers_RenderRowsMarkdownAndConsoleOutput()
new PrimitiveDataFrameColumn("Count", new[] { 1, 2 })
);
var row = frame.Rows[1];
- var originalOut = Console.Out;
using var writer = new StringWriter();
- Console.SetOut(writer);
- try
- {
- // Act
- var prettyText = frame.PrettyText();
- var prettyRow = row.Pretty();
- var markdown = frame.ToMarkdown();
- frame.PrettyPrint();
- row.PrettyPrint();
-
- // Assert
- prettyText.Should().Contain("Name");
- prettyText.Should().Contain("alpha");
- prettyRow.Should().Be(" 2");
- markdown.Should().Contain("Name");
- markdown.Should().Contain("alpha");
- writer.ToString().Should().Contain("Name");
- writer.ToString().Should().Contain(" 2");
- }
- finally
- {
- Console.SetOut(originalOut);
- }
+ // Act
+ var prettyText = frame.PrettyText();
+ var prettyRow = row.Pretty();
+ var markdown = frame.ToMarkdown();
+ frame.PrettyPrint(writer);
+ row.PrettyPrint(writer);
+
+ // Assert
+ prettyText.Should().Contain("Name");
+ prettyText.Should().Contain("alpha");
+ prettyRow.Should().Be(" 2");
+ markdown.Should().Contain("Name");
+ markdown.Should().Contain("alpha");
+ writer.ToString().Should().Contain("Name");
+ writer.ToString().Should().Contain(" 2");
+ }
+
+ ///
+ /// Exercises the null-writer default of both PrettyPrint overloads, which resolves to
+ /// Console.Out and preserves the behaviour every existing caller relies on.
+ ///
+ [TestMethod]
+ public void PrettyPrint_NullWriter_WritesToConsoleWithoutThrowing()
+ {
+ // Arrange
+ var frame = new DataFrame(
+ new StringDataFrameColumn("Name", new string[] { "alpha", null }),
+ new PrimitiveDataFrameColumn("Count", new[] { 1, 2 })
+ );
+ var row = frame.Rows[1];
+
+ // Act + Assert
+ FluentActions
+ .Invoking(() =>
+ {
+ frame.PrettyPrint();
+ row.PrettyPrint();
+ })
+ .Should()
+ .NotThrow();
}
[TestMethod]
diff --git a/UtilitiesCS.Test/OutlookObjects/Filter DASL/DASLFilterParserTests.cs b/UtilitiesCS.Test/OutlookObjects/Filter DASL/DASLFilterParserTests.cs
index c28d715ad..587671542 100644
--- a/UtilitiesCS.Test/OutlookObjects/Filter DASL/DASLFilterParserTests.cs
+++ b/UtilitiesCS.Test/OutlookObjects/Filter DASL/DASLFilterParserTests.cs
@@ -5,13 +5,6 @@
namespace UtilitiesCS.Test.OutlookObjects.FilterDASL
{
- // PrintTree_WritesIndentedTreeToConsole captures and restores Console.Out, which is
- // process-wide state. Under the class-level parallel scope declared by the Parallelize
- // attribute at UtilitiesCS.Test/Properties/AssemblyInfo.cs lines 18-21, a sibling test
- // class's Console.SetOut overrides this class's redirect mid-test and makes the captured
- // output empty. The assembly attribute, not TaskMaster.runsettings, is what takes effect:
- // the CI vstest invocation passes no /Settings: argument.
- [DoNotParallelize]
[TestClass]
public class DASLFilterParserTests
{
@@ -99,24 +92,15 @@ public void Parse_WithInvalidSyntaxThatNeverClosesParenthesis_ReturnsLeafNode()
}
[TestMethod]
- public void PrintTree_WritesIndentedTreeToConsole()
+ public void PrintTree_WritesIndentedTreeToSuppliedWriter()
{
// Arrange
var parser = new DASLFilterParser();
var tree = parser.Parse("A AND B");
using var writer = new StringWriter();
- var originalOut = Console.Out;
- Console.SetOut(writer);
-
- try
- {
- // Act
- parser.PrintTree(tree, 0);
- }
- finally
- {
- Console.SetOut(originalOut);
- }
+
+ // Act
+ parser.PrintTree(tree, 0, writer);
// Assert
writer.ToString().Should().Contain("AND").And.Contain(" A").And.Contain(" B");
diff --git a/UtilitiesCS.Test/OutlookObjects/Table/OlTableExtensionsEtlClockTests.cs b/UtilitiesCS.Test/OutlookObjects/Table/OlTableExtensionsEtlClockTests.cs
new file mode 100644
index 000000000..c2fd86705
--- /dev/null
+++ b/UtilitiesCS.Test/OutlookObjects/Table/OlTableExtensionsEtlClockTests.cs
@@ -0,0 +1,234 @@
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using FluentAssertions;
+using Microsoft.Extensions.Time.Testing;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using UtilitiesCS.Test.TestHelpers;
+using Outlook = Microsoft.Office.Interop.Outlook;
+
+namespace UtilitiesCS.Test.OutlookObjects.Table
+{
+ ///
+ /// Clock-driven tests for . The 250 ms per-row
+ /// deadline is placed under an injected clock, so both the expiry path and the green path
+ /// are deterministic and neither waits on wall time.
+ ///
+ [TestClass]
+ // Not parallelized: this class drives a real Task.Run gate.
+ [DoNotParallelize]
+ public class OlTableExtensionsEtlClockTests
+ {
+ private static Mock CreateRowMock(
+ object[] values,
+ IDictionary binaryStrings,
+ IDictionary indexedValues
+ )
+ {
+ var mockRow = new Mock();
+ mockRow.Setup(r => r.GetValues()).Returns(values);
+
+ foreach (var pair in binaryStrings)
+ {
+ mockRow.Setup(r => r.BinaryToString(pair.Key)).Returns(pair.Value);
+ }
+
+ foreach (var pair in indexedValues)
+ {
+ mockRow.Setup(r => r[pair.Key]).Returns(pair.Value);
+ }
+
+ return mockRow;
+ }
+
+ ///
+ /// Builds a Table over the supplied rows. The onGetNextRow action is the test-owned gate:
+ /// blocking inside it keeps the row read from completing, so the ETL hop deadline is the
+ /// only thing that can end the attempt.
+ ///
+ private static Mock CreateTableWithColumns(
+ string[] columnNames,
+ System.Action onGetNextRow,
+ object[,] array,
+ params Mock[] rows
+ )
+ {
+ var mockTable = new Mock();
+ var mockColumns = new Mock();
+ mockTable.Setup(t => t.Columns).Returns(mockColumns.Object);
+ mockColumns.Setup(c => c.Count).Returns(columnNames.Length);
+
+ for (var index = 0; index < columnNames.Length; index++)
+ {
+ var mockColumn = new Mock();
+ mockColumn.Setup(c => c.Name).Returns(columnNames[index]);
+ mockColumns.Setup(c => c[index + 1]).Returns(mockColumn.Object);
+ }
+
+ mockTable.Setup(t => t.GetRowCount()).Returns(rows.Length);
+
+ var currentRow = 0;
+ mockTable.Setup(t => t.MoveToStart()).Callback(() => currentRow = 0);
+ mockTable.Setup(t => t.EndOfTable).Returns(() => currentRow >= rows.Length);
+ mockTable
+ .Setup(t => t.GetNextRow())
+ .Returns(() =>
+ {
+ onGetNextRow();
+ return rows[currentRow++].Object;
+ });
+
+ if (array is not null)
+ {
+ mockTable.Setup(t => t.GetArray(It.IsAny())).Returns(array);
+ }
+
+ return mockTable;
+ }
+
+ private static Dictionary> CreateConverters()
+ {
+ return new Dictionary>
+ {
+ { "MessageRecipients", _ => "Converted Recipients" },
+ };
+ }
+
+ ///
+ /// Documents the surviving EtlAsync contract on deadline expiry: the TimeoutException is
+ /// swallowed, a null data array is returned through a null-forgiving suppression, and the
+ /// supplied token source is cancelled. This is the shape the DfDeedle guard now catches.
+ ///
+ [TestMethod]
+ public async Task EtlAsync_DeadlineExpires_ReturnsNullDataAndCancelsTokenSource()
+ {
+ // Arrange
+ var gate = new ManualResetEventSlim(false);
+ var recipient = new object();
+ var row = CreateRowMock(
+ new object[] { recipient, "raw-store", "Subject" },
+ new Dictionary { { 2, "STORE-ID-101" } },
+ new Dictionary { { 1, recipient } }
+ );
+ var mockTable = CreateTableWithColumns(
+ new[] { "MessageRecipients", "Store", "Subject" },
+ gate.Wait,
+ null,
+ row
+ );
+ var barrier = new ArmingBarrierTimeProvider(new FakeTimeProvider());
+ var tokenSource = new CancellationTokenSource();
+
+ try
+ {
+ // Act
+ Task<(object[,] data, Dictionary columnInfo)> call =
+ mockTable.Object.EtlAsync(
+ CancellationToken.None,
+ tokenSource,
+ 0,
+ null,
+ CreateConverters(),
+ barrier
+ );
+
+ // The row read is blocked on the gate, so the hop deadline is armed before the
+ // clock moves. Advancing earlier would arm the timer past an elapsed deadline.
+ await barrier.Armed;
+ barrier.Advance(250);
+ var (data, _) = await call;
+
+ // Assert
+ data.Should().BeNull();
+ tokenSource.IsCancellationRequested.Should().BeTrue();
+ }
+ finally
+ {
+ // Release the gate so the orphaned Task.Run body completes.
+ gate.Set();
+ }
+ }
+
+ ///
+ /// The deterministic green path: the clock never advances, so the 250 ms deadline cannot
+ /// fire regardless of host load, and the rows transform normally.
+ ///
+ [TestMethod]
+ public async Task EtlAsync_ClockNeverAdvances_ReturnsTransformedRows()
+ {
+ // Arrange: no gate is engaged, so the row read completes immediately.
+ var recipient = new object();
+ var row = CreateRowMock(
+ new object[] { recipient, "raw-store", "Subject" },
+ new Dictionary { { 2, "STORE-ID-102" } },
+ new Dictionary { { 1, recipient } }
+ );
+ var mockTable = CreateTableWithColumns(
+ new[] { "MessageRecipients", "Store", "Subject" },
+ () => { },
+ null,
+ row
+ );
+ var tokenSource = new CancellationTokenSource();
+
+ // Act
+ var (data, columnInfo) = await mockTable.Object.EtlAsync(
+ CancellationToken.None,
+ tokenSource,
+ 0,
+ null,
+ CreateConverters(),
+ new FakeTimeProvider()
+ );
+
+ // Assert
+ columnInfo["Store"].Should().Be(1);
+ data[0, 1].Should().Be("STORE-ID-102");
+ tokenSource.IsCancellationRequested.Should().BeFalse();
+ }
+
+ ///
+ /// The GetArray branch: with no BinaryToStringFields column and no object converters,
+ /// EtlAsync snapshots the whole table in one call instead of reading row by row. Its
+ /// 250 ms deadline is armed on a clock that never advances, so it cannot fire.
+ ///
+ [TestMethod]
+ public async Task EtlAsync_NoBinaryOrObjectFields_UsesGetArrayBranchOnControlledClock()
+ {
+ // Arrange
+ var array = new object[,]
+ {
+ { "entry-1", "Subject" },
+ };
+ var row = CreateRowMock(
+ new object[] { "entry-1", "Subject" },
+ new Dictionary(),
+ new Dictionary()
+ );
+ var mockTable = CreateTableWithColumns(
+ new[] { "EntryID", "Subject" },
+ () => { },
+ array,
+ row
+ );
+ var tokenSource = new CancellationTokenSource();
+
+ // Act
+ var (data, columnInfo) = await mockTable.Object.EtlAsync(
+ CancellationToken.None,
+ tokenSource,
+ 0,
+ null,
+ null,
+ new FakeTimeProvider()
+ );
+
+ // Assert
+ columnInfo["Subject"].Should().Be(1);
+ data[0, 0].Should().Be("entry-1");
+ tokenSource.IsCancellationRequested.Should().BeFalse();
+ }
+ }
+}
diff --git a/UtilitiesCS.Test/OutlookObjects/Table/OlTableExtensions_Tests.cs b/UtilitiesCS.Test/OutlookObjects/Table/OlTableExtensions_Tests.cs
index ec5eb9834..f6ba7b150 100644
--- a/UtilitiesCS.Test/OutlookObjects/Table/OlTableExtensions_Tests.cs
+++ b/UtilitiesCS.Test/OutlookObjects/Table/OlTableExtensions_Tests.cs
@@ -7,6 +7,7 @@
using System.Threading;
using System.Threading.Tasks;
using FluentAssertions;
+using Microsoft.Extensions.Time.Testing;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using UtilitiesCS.OutlookObjects.Fields;
@@ -14,9 +15,9 @@
namespace UtilitiesCS.Test.OutlookObjects.Table
{
- // EnumerateTable_WritesFormattedOutputAndMovesToStart redirects Console.Out,
- // which is process-wide state. Under class-level parallel execution another
- // test class can replace the writer mid-test and make the captured output empty.
+ // The console reason for this attribute was removed by the TextWriter seam under #811.
+ // It is retained because this class has not been soaked under class-level parallelism
+ // and ten of its tests drive the 2000 ms GetTableInViewAsync window.
[DoNotParallelize]
[TestClass]
public class OlTableExtensions_Tests
@@ -957,10 +958,6 @@ public async Task EtlAsync_WithBinaryAndObjectFieldsAndProgress_ReturnsTransform
null,
row
);
- // EtlAsync computes its TimeoutAfter budget as 250 ms * GetRowCount().
- // Override to a large value so the timeout cannot fire under test-host
- // contention; iteration is driven by EndOfTable/GetNextRow, not GetRowCount.
- mockTable.Setup(t => t.GetRowCount()).Returns(120);
var converters = new Dictionary>
{
{ "MessageRecipients", _ => "Converted Async Recipients" },
@@ -973,7 +970,8 @@ public async Task EtlAsync_WithBinaryAndObjectFieldsAndProgress_ReturnsTransform
tokenSource,
0,
progress,
- converters
+ converters,
+ timeProvider: new FakeTimeProvider()
);
columnInfo["Store"].Should().Be(1);
@@ -1621,7 +1619,7 @@ public void GetColumnHeaders_SchemaName_MapsToFieldName()
}
[TestMethod]
- public void EnumerateTable_WritesFormattedOutputAndMovesToStart()
+ public void EnumerateTable_WritesFormattedOutputToSuppliedWriterAndMovesToStart()
{
var schemaName = MAPIFields.FieldToSchema["Store"];
var array = new object[,]
@@ -1633,21 +1631,14 @@ public void EnumerateTable_WritesFormattedOutputAndMovesToStart()
array
);
var output = new StringWriter();
- var original = Console.Out;
- try
- {
- Console.SetOut(output);
- mockTable.Object.EnumerateTable();
- }
- finally
- {
- Console.SetOut(original);
- }
+ mockTable.Object.EnumerateTable(output);
output.ToString().Should().Contain("Store");
output.ToString().Should().Contain("Subject");
output.ToString().Should().Contain("STORE-ID-009");
+ // The null-writer path resolving to Console.Out; this file is at its line ceiling.
+ FluentActions.Invoking(() => mockTable.Object.EnumerateTable()).Should().NotThrow();
mockTable.Verify(t => t.MoveToStart(), Times.AtLeastOnce);
}
diff --git a/UtilitiesCS.Test/ReusableTypeClasses/StackGeek_Tests.cs b/UtilitiesCS.Test/ReusableTypeClasses/StackGeek_Tests.cs
index caae5708f..0052b7341 100644
--- a/UtilitiesCS.Test/ReusableTypeClasses/StackGeek_Tests.cs
+++ b/UtilitiesCS.Test/ReusableTypeClasses/StackGeek_Tests.cs
@@ -3,16 +3,10 @@
using System.Reflection;
using FluentAssertions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
+using UtilitiesCS;
namespace UtilitiesCS.Test.ReusableTypeClasses
{
- // Main_RunsSampleScenarioWithoutThrowing captures and restores Console.Out, which is
- // process-wide state. Under the class-level parallel scope declared by the Parallelize
- // attribute at UtilitiesCS.Test/Properties/AssemblyInfo.cs lines 18-21, a sibling test
- // class's Console.SetOut overrides this class's redirect mid-test and makes the captured
- // output empty. The assembly attribute, not TaskMaster.runsettings, is what takes effect:
- // the CI vstest invocation passes no /Settings: argument.
- [DoNotParallelize]
[TestClass]
public class StackGeek_Tests
{
@@ -143,24 +137,33 @@ public void DeleteMiddle_WithSingleElement_ShouldNotThrow()
helper.GetMiddle(stack).Should().BeNull();
}
+ ///
+ /// Exercises the null-writer default: Main forwards to Run(Console.Out), so this test
+ /// asserts only that the sample scenario completes, with no Console.Out capture.
+ ///
[TestMethod]
public void Main_RunsSampleScenarioWithoutThrowing()
{
// Arrange
var helper = new StackGeekReflectionHelper();
- var originalOut = Console.Out;
- using var writer = new StringWriter();
- Console.SetOut(writer);
- try
- {
- // Act
- helper.InvokeMain(Array.Empty());
- }
- finally
- {
- Console.SetOut(originalOut);
- }
+ // Act + Assert
+ FluentActions
+ .Invoking(() => helper.InvokeMain(Array.Empty()))
+ .Should()
+ .NotThrow();
+ }
+
+ ///
+ /// The TextWriter seam: the scenario writes to a test-owned writer, so the assertion no
+ /// longer depends on process-wide Console.Out and cannot be broken by a sibling class.
+ ///
+ [TestMethod]
+ public void Run_WritesScenarioToSuppliedWriter()
+ {
+ // Arrange + Act
+ using var writer = new StringWriter();
+ GFG.Run(writer);
// Assert
writer.ToString().Should().Contain("Middle Element :");
diff --git a/UtilitiesCS.Test/TestHelpers/ArmingBarrierTimeProvider.cs b/UtilitiesCS.Test/TestHelpers/ArmingBarrierTimeProvider.cs
new file mode 100644
index 000000000..df1a13c89
--- /dev/null
+++ b/UtilitiesCS.Test/TestHelpers/ArmingBarrierTimeProvider.cs
@@ -0,0 +1,55 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Time.Testing;
+
+namespace UtilitiesCS.Test.TestHelpers
+{
+ ///
+ /// A forwarding every member to an inner
+ /// unchanged and, after forwarding CreateTimer ,
+ /// completing a signal that the production loop has armed its next deadline.
+ ///
+ ///
+ /// Forwarding keeps timer ownership with the inner provider. Consecutive Advance
+ /// calls are prohibited: each deadline is armed only after the previous proxy faults, so
+ /// advancing past a deadline the loop has not created hangs the test. TrySetResult
+ /// is used because the loop can arm one more timer than a test drives.
+ ///
+ internal sealed class ArmingBarrierTimeProvider : TimeProvider
+ {
+ private readonly FakeTimeProvider _inner;
+ private volatile TaskCompletionSource _armed = NewSignal();
+
+ internal ArmingBarrierTimeProvider(FakeTimeProvider inner) => _inner = inner;
+
+ internal Task Armed => _armed.Task;
+
+ internal void ReArm() => _armed = NewSignal();
+
+ internal void Advance(int ms) => _inner.Advance(TimeSpan.FromMilliseconds(ms));
+
+ public override DateTimeOffset GetUtcNow() => _inner.GetUtcNow();
+
+ public override long GetTimestamp() => _inner.GetTimestamp();
+
+ public override TimeZoneInfo LocalTimeZone => _inner.LocalTimeZone;
+ public override long TimestampFrequency => _inner.TimestampFrequency;
+
+ public override ITimer CreateTimer(
+ TimerCallback callback,
+ object state,
+ TimeSpan dueTime,
+ TimeSpan period
+ )
+ {
+ var timer = _inner.CreateTimer(callback, state, dueTime, period);
+ _armed.TrySetResult(true);
+ return timer;
+ }
+
+ // Continuations run asynchronously so a signal never resumes a test inline.
+ internal static TaskCompletionSource NewSignal() =>
+ new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
+ }
+}
diff --git a/UtilitiesCS.Test/UtilitiesCS.Test.csproj b/UtilitiesCS.Test/UtilitiesCS.Test.csproj
index 5ce3b0e86..be7fea8cf 100644
--- a/UtilitiesCS.Test/UtilitiesCS.Test.csproj
+++ b/UtilitiesCS.Test/UtilitiesCS.Test.csproj
@@ -71,6 +71,7 @@
+
@@ -189,6 +190,7 @@
+
@@ -539,6 +541,7 @@
+
diff --git a/UtilitiesCS/Extensions/DfDeedle.FrameUtilities.cs b/UtilitiesCS/Extensions/DfDeedle.FrameUtilities.cs
index 564d72049..e05893750 100644
--- a/UtilitiesCS/Extensions/DfDeedle.FrameUtilities.cs
+++ b/UtilitiesCS/Extensions/DfDeedle.FrameUtilities.cs
@@ -126,7 +126,8 @@ IEnumerable objIndices
Store store,
OlDefaultFolders folderEnum,
string[] removeColumns,
- string[] addColumns
+ string[] addColumns,
+ Func columnInfo)>? etl = null
)
{
var table = store.GetTable(
@@ -140,7 +141,7 @@ string[] addColumns
return null;
}
- (var data, var columnInfo) = StoreTableEtlInvoker(table);
+ (var data, var columnInfo) = (etl ?? DefaultTableEtl)(table);
Frame? df = FromArray2D(data: data, columnInfo);
@@ -151,7 +152,8 @@ public static Frame FromDefaultFolder(
Stores stores,
OlDefaultFolders folderEnum,
string[] removeColumns,
- string[] addColumns
+ string[] addColumns,
+ Func columnInfo)>? etl = null
)
{
Frame? df = null;
@@ -161,7 +163,8 @@ string[] addColumns
store: store,
folderEnum: folderEnum,
removeColumns: removeColumns,
- addColumns: addColumns
+ addColumns: addColumns,
+ etl: etl
);
if (
diff --git a/UtilitiesCS/Extensions/DfDeedle.cs b/UtilitiesCS/Extensions/DfDeedle.cs
index f83e52cc4..b5adec1e5 100644
--- a/UtilitiesCS/Extensions/DfDeedle.cs
+++ b/UtilitiesCS/Extensions/DfDeedle.cs
@@ -60,30 +60,19 @@ internal static Func<
> MessageBoxInvoker = System.Windows.Forms.MessageBox.Show;
///
- /// Testability seam for the extension method
- /// that converts an Outlook Table to a 2-D data array.
- /// Tests replace this delegate to supply pre-built data without a live COM Table.
- /// Uses as the parameter type to avoid CS1769 (embedded interop
- /// types cannot be used as generic type arguments across assembly boundaries).
+ /// The production ETL over an Outlook Table, used whenever no etl argument is
+ /// supplied. Uses as the parameter type to avoid CS1769 (embedded
+ /// interop types cannot be used as generic type arguments across assembly boundaries).
///
- internal static Func<
- object,
- (object[,] data, Dictionary columnInfo)
- > TableEtlInvoker = t => ((Outlook.Table)t).ETL();
-
- ///
- /// Testability seam for the extension method
- /// as used inside .
- /// Tests replace this delegate to supply pre-built data without a live COM Table.
- /// Uses as the parameter type to avoid CS1769 (embedded interop
- /// types cannot be used as generic type arguments across assembly boundaries).
- ///
- internal static Func<
+ private static readonly Func<
object,
(object[,] data, Dictionary columnInfo)
- > StoreTableEtlInvoker = t => ((Outlook.Table)t).ETL();
+ > DefaultTableEtl = t => ((Outlook.Table)t).ETL();
- public static Frame GetEmailDataInView(Explorer activeExplorer)
+ public static Frame GetEmailDataInView(
+ Explorer activeExplorer,
+ Func columnInfo)>? etl = null
+ )
{
Outlook.Table table = activeExplorer.GetTableInView();
var currentFolder = activeExplorer.CurrentFolder;
@@ -91,7 +80,7 @@ public static Frame GetEmailDataInView(Explorer activeExplorer)
AddQfcColumns(table, currentFolder);
- (object[,] data, Dictionary columnInfo) = TableEtlInvoker(table);
+ (object[,] data, Dictionary columnInfo) = (etl ?? DefaultTableEtl)(table);
// Closes the second set of unchecked dictionary reads, the ones GetEmailDataFromTable
// performs on this synchronous path. The asynchronous path never reaches them, so its
@@ -140,7 +129,10 @@ public static async Task > GetEmailDataInViewAsync(
Explorer activeExplorer,
CancellationToken token,
CancellationTokenSource tokenSource,
- ProgressTracker progress
+ ProgressTracker progress,
+ // A null timeProvider resolves to the system clock, so production timing is unchanged.
+ // Tests inject a FakeTimeProvider to drive every deadline on this path deterministically.
+ TimeProvider? timeProvider = null
)
{
token.ThrowIfCancellationRequested();
@@ -173,7 +165,7 @@ ProgressTracker progress
//logger.Debug($"{DateTime.Now.ToString("mm:ss.fff")} Calling {nameof(AddQfcColumnsAsync)} ...");
// currentFolder is the live Explorer's CurrentFolder (non-null); the defensive
// ?.Name in the preceding log line set its flow state to maybe-null.
- await AddQfcColumnsAsync(table, currentFolder!, token, 0);
+ await AddQfcColumnsAsync(table, currentFolder!, token, 0, timeProvider: timeProvider);
//logger.Debug($"{DateTime.Now.ToString("mm:ss.fff")} Calling {nameof(OlTableExtensions.EtlAsync)} ...");
var etlStopwatch = Stopwatch.StartNew();
@@ -181,8 +173,21 @@ ProgressTracker progress
token,
tokenSource,
0,
- progress.Increment(2).SpawnChild(96)
+ progress.Increment(2).SpawnChild(96),
+ timeProvider: timeProvider
);
+
+ // EtlAsync swallows its TimeoutException and returns a null data array through a
+ // null-forgiving suppression, so this is the first point the failure can be named.
+ if (tableSnapshot.data is null)
+ {
+ throw new InvalidOperationException(
+ $"The table snapshot for folder '{folderName}' was not produced: the table ETL "
+ + "timed out or was cancelled before returning any rows, so the email data "
+ + "frame cannot be built for this folder."
+ );
+ }
+
LogDfTiming(
"GetEmailDataInViewAsync table snapshot ready | table snapshot",
$"rowCount={tableSnapshot.Item1.GetLength(0)}; columnCount={tableSnapshot.Item1.GetLength(1)}; etlElapsedMs={etlStopwatch.ElapsedMilliseconds}"
@@ -205,7 +210,7 @@ ProgressTracker progress
() => Email2dArrayToDf(storeID, tableSnapshot.Item1, tableSnapshot.Item2),
token
)
- .TimeoutAfter(1000, 2);
+ .TimeoutAfter(1000, timeProvider);
LogDfTiming(
"GetEmailDataInViewAsync dataframe transform complete | dataframe transform",
$"rowCount={df.RowCount}; columnCount={df.ColumnCount}; elapsedMs={dataframeStopwatch.ElapsedMilliseconds}; totalElapsedMs={getEmailDataStopwatch.ElapsedMilliseconds}"
diff --git a/UtilitiesCS/Extensions/DictionaryExtensions.cs b/UtilitiesCS/Extensions/DictionaryExtensions.cs
index 07eb20bc7..fc6366a0c 100644
--- a/UtilitiesCS/Extensions/DictionaryExtensions.cs
+++ b/UtilitiesCS/Extensions/DictionaryExtensions.cs
@@ -173,10 +173,9 @@ public static async Task TryAddValuesAsync(
CancellationToken token
)
{
- var linkedTS = CancellationTokenSource.CreateLinkedTokenSource(token);
- linkedTS.CancelAfter(500);
-
- return await Task.Run(() => dictionary.TryAddValues(key, value), linkedTS.Token);
+ // TryAddValues is a bounded compare-and-swap loop that performs no I/O and cannot
+ // hang, so cancellation is governed solely by the caller's token.
+ return await Task.Run(() => dictionary.TryAddValues(key, value), token);
}
public static Enums.DictionaryResult UpdateOrRemove(
diff --git a/UtilitiesCS/HelperClasses/PrettyPrint.cs b/UtilitiesCS/HelperClasses/PrettyPrint.cs
index e711d963d..805cbd57d 100644
--- a/UtilitiesCS/HelperClasses/PrettyPrint.cs
+++ b/UtilitiesCS/HelperClasses/PrettyPrint.cs
@@ -1,30 +1,30 @@
#nullable enable
using System;
using System.Collections.Generic;
+using System.IO;
using System.Linq;
using System.Text;
-using System.Threading.Tasks;
namespace UtilitiesCS
{
using System.Data;
using System.Diagnostics;
- using System.Text;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using System.Windows.Input;
using Microsoft.Data.Analysis;
using Microsoft.Office.Interop.Outlook;
- using Svg;
///
/// Class written to transform Dataframe objects for printing
///
public static class PrettyPrinters
{
- public static void PrettyPrint(this DataFrame df) => Console.WriteLine(PrettyText(df));
+ public static void PrettyPrint(this DataFrame df, TextWriter? writer = null) =>
+ (writer ?? Console.Out).WriteLine(PrettyText(df));
- public static void PrettyPrint(this DataFrameRow row) => Console.WriteLine(Pretty(row));
+ public static void PrettyPrint(this DataFrameRow row, TextWriter? writer = null) =>
+ (writer ?? Console.Out).WriteLine(Pretty(row));
public static string PrettyText(this DataFrame df) => ToStringArray2D(df).ToFormattedText();
diff --git a/UtilitiesCS/OutlookObjects/Filter DASL/DASLFilterParser.cs b/UtilitiesCS/OutlookObjects/Filter DASL/DASLFilterParser.cs
index 1cda7a3d1..e54b90273 100644
--- a/UtilitiesCS/OutlookObjects/Filter DASL/DASLFilterParser.cs
+++ b/UtilitiesCS/OutlookObjects/Filter DASL/DASLFilterParser.cs
@@ -1,6 +1,7 @@
#nullable enable
using System;
using System.Collections.Generic;
+using System.IO;
using System.Text.RegularExpressions;
using UtilitiesCS.ReusableTypeClasses;
@@ -94,12 +95,12 @@ private int FindOperatorIndex(string expression, string operatorStr)
// return match.Success ? match.Index : -1;
//}
- public void PrintTree(TreeNode node, int level)
+ public void PrintTree(TreeNode node, int level, TextWriter? writer = null)
{
- Console.WriteLine(new string(' ', level * 2) + node.Value);
+ (writer ?? Console.Out).WriteLine(new string(' ', level * 2) + node.Value);
foreach (var child in node.Children)
{
- PrintTree(child, level + 1);
+ PrintTree(child, level + 1, writer);
}
}
diff --git a/UtilitiesCS/OutlookObjects/Table/OlTableExtensions.Etl.cs b/UtilitiesCS/OutlookObjects/Table/OlTableExtensions.Etl.cs
index 246ec4c2a..2db430c91 100644
--- a/UtilitiesCS/OutlookObjects/Table/OlTableExtensions.Etl.cs
+++ b/UtilitiesCS/OutlookObjects/Table/OlTableExtensions.Etl.cs
@@ -69,7 +69,10 @@ objectConverters is not null
CancellationTokenSource tokenSource,
int counter,
ProgressTracker? progress,
- Dictionary>? objectConverters = null
+ Dictionary>? objectConverters = null,
+ // A null timeProvider resolves to the system clock, so production timing is unchanged.
+ // Tests inject a FakeTimeProvider to drive the deadline without a wall-clock wait.
+ TimeProvider? timeProvider = null
)
{
token.ThrowIfCancellationRequested();
@@ -79,7 +82,6 @@ objectConverters is not null
var rowCount = table.GetRowCount();
int milliseconds = 250 * rowCount;
- var attempts = 3;
object[,]? data = null;
var columnDictionary = table.GetColumnDictionary();
@@ -101,7 +103,7 @@ objectConverters is not null
columnDictionary,
token,
milliseconds,
- attempts,
+ timeProvider,
progress
);
}
@@ -111,13 +113,13 @@ objectConverters is not null
() => table?.GetArray(table.GetRowCount()) as object[,],
token
)
- .TimeoutAfter(milliseconds, attempts);
+ .TimeoutAfter(milliseconds, timeProvider);
}
}
catch (TimeoutException)
{
logger.Error(
- $"{DateTime.Now.ToString("mm:ss.fff")} {nameof(ETL)} timed out {attempts} times with a timeout of {milliseconds} milliseconds. Canceling"
+ $"{nameof(EtlAsync)} timed out with a timeout of {milliseconds} milliseconds. Canceling"
);
tokenSource.Cancel();
}
@@ -232,7 +234,7 @@ private static async Task EtlByRowAsync(
Dictionary columnDictionary,
CancellationToken token,
int timeout,
- int attempts,
+ TimeProvider? timeProvider,
ProgressTracker? progress = null
)
{
@@ -242,7 +244,7 @@ private static async Task EtlByRowAsync(
(var objFields, var objIndices) = GetObjectFields(objectConverters, columnDictionary);
var rows = await Task.Run(() => table.CastToRowArray(progress?.SpawnChild(65)), token)
- .TimeoutAfter(timeout, attempts);
+ .TimeoutAfter(timeout, timeProvider);
token.ThrowIfCancellationRequested();
var jagged = await Task.Run(
@@ -256,7 +258,7 @@ private static async Task EtlByRowAsync(
),
token
)
- .TimeoutAfter(timeout, attempts);
+ .TimeoutAfter(timeout, timeProvider);
var data = jagged.To2D();
return data;
diff --git a/UtilitiesCS/OutlookObjects/Table/OlTableExtensions.TableAccess.cs b/UtilitiesCS/OutlookObjects/Table/OlTableExtensions.TableAccess.cs
index 34f1f74d8..ef605ced3 100644
--- a/UtilitiesCS/OutlookObjects/Table/OlTableExtensions.TableAccess.cs
+++ b/UtilitiesCS/OutlookObjects/Table/OlTableExtensions.TableAccess.cs
@@ -2,6 +2,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
+using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
@@ -378,8 +379,9 @@ public static string[] GetColumnHeaders(this Outlook.Table table)
return headers;
}
- public static void EnumerateTable(this Outlook.Table table)
+ public static void EnumerateTable(this Outlook.Table table, TextWriter? writer = null)
{
+ var target = writer ?? Console.Out;
int columnCount = table.Columns.Count;
int[] charSpacing = Enumerable.Repeat(20, columnCount).ToArray();
Justify[] justification = Enumerable.Repeat(Justify.Left, columnCount).ToArray();
@@ -420,9 +422,9 @@ public static void EnumerateTable(this Outlook.Table table)
rows.Add(lineDivider);
string output = string.Join("\n", rows.ToArray());
- Console.WriteLine("");
- Console.WriteLine("");
- Console.WriteLine(output);
+ target.WriteLine("");
+ target.WriteLine("");
+ target.WriteLine(output);
table.MoveToStart();
}
diff --git a/UtilitiesCS/ReusableTypeClasses/Other/StackGeek.cs b/UtilitiesCS/ReusableTypeClasses/Other/StackGeek.cs
index ee42b9c5f..6664bf45c 100644
--- a/UtilitiesCS/ReusableTypeClasses/Other/StackGeek.cs
+++ b/UtilitiesCS/ReusableTypeClasses/Other/StackGeek.cs
@@ -171,6 +171,13 @@ void deleteMiddle(myStack ms)
// Driver code
public static void Main(String[] args)
+ {
+ Run(Console.Out);
+ }
+
+ // The sample scenario, written to a supplied writer so a test can assert on the output
+ // without redirecting the process-wide Console.Out.
+ public static void Run(System.IO.TextWriter writer)
{
GFG ob = new GFG();
myStack ms = ob.createMyStack();
@@ -184,11 +191,11 @@ public static void Main(String[] args)
ob.push(ms, 88);
ob.push(ms, 99);
- Console.WriteLine("Popped : " + ob.pop(ms));
- Console.WriteLine("Popped : " + ob.pop(ms));
- Console.WriteLine("Middle Element : " + ob.findMiddle(ms));
+ writer.WriteLine("Popped : " + ob.pop(ms));
+ writer.WriteLine("Popped : " + ob.pop(ms));
+ writer.WriteLine("Middle Element : " + ob.findMiddle(ms));
ob.deleteMiddle(ms);
- Console.WriteLine("New Middle Element : " + ob.findMiddle(ms));
+ writer.WriteLine("New Middle Element : " + ob.findMiddle(ms));
}
}
diff --git a/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/code-review.2026-09-08T11-30.md b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/code-review.2026-09-08T11-30.md
new file mode 100644
index 000000000..93120c00f
--- /dev/null
+++ b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/code-review.2026-09-08T11-30.md
@@ -0,0 +1,315 @@
+# Code Review — utilitiescs-test-determinism (Issue #811)
+
+- Date: 2026-09-08T11-30
+- Branch: `bug/utilitiescs-test-determinism-780-803-594-811`
+- Head: `3805ca89f0c72e2bb448726695f18f420bd260f7`
+- Base: `bb1c7d4b60f7b782227956f36859314d5c47bb03`
+- Source under review: 20 changed `.cs` / `.csproj` files; 48 further documentation and evidence
+ files reviewed by direct reading
+
+Blocking findings in this artifact: **0**. (The single blocking finding for this branch is F-1 in
+`policy-audit.2026-09-08T11-30.md`; it is a process gap, not a code defect.)
+
+## 1. Overall Assessment
+
+The change is well-scoped and the technique is correct for the problem class. Each of the three
+defects is treated at the level where the nondeterminism originates rather than suppressed at the
+test level: a guard with no consumer is deleted, wall-clock deadlines that must survive in
+production are placed under an injectable `TimeProvider`, and a process-wide output dependency is
+converted into an explicit parameter. Notably, the change removes an existing serialization stopgap
+and an existing timing tolerance rather than adding new ones, which is the direction the repository
+policy asks for.
+
+The production risk profile is low. Every new parameter is optional and trailing; every default
+reproduces prior behavior exactly (`null` `TimeProvider` resolves to the system clock, `null`
+`TextWriter` resolves to `Console.Out`, `null` `etl` resolves to the extracted
+`DefaultTableEtl`). The one intentional behavior change — an `InvalidOperationException` replacing
+a `NullReferenceException` one statement later — is a strict diagnostic improvement on a path that
+already failed.
+
+## 2. Design and Architecture
+
+### 2.1 Deleting the `TryAddValuesAsync` window — correct
+
+`DictionaryExtensions.cs` now passes the caller's token straight to `Task.Run`. This reviewer
+verified the two load-bearing claims independently rather than accepting them:
+
+- `TryAddValuesAsync` has zero production call sites. A repository-wide search over `*.cs` returns
+ exactly four hits: the definition at `DictionaryExtensions.cs:169` and three references in
+ `UtilitiesCS.Test/Extensions/DictionaryExtensions_Tests.cs` (two test method names and one
+ invocation). No production caller can be affected.
+- The token passed to `Task.Run` can only cancel work that has not started, so a window that
+ cancels after 500 ms could only ever convert scheduling latency into a spurious
+ `TaskCanceledException`. Deleting it removes a failure mode without removing a guarantee.
+
+The change also removes a per-call undisposed linked `CancellationTokenSource` and its timer. The
+replacement comment states the invariant (bounded compare-and-swap loop, no I/O, cannot hang)
+rather than restating the code, which is the correct comment style.
+
+### 2.2 `TimeProvider` seams — used substantively, not decoratively
+
+This was checked specifically. The seams are load-bearing, not ornamental:
+
+- `OlTableExtensions.Etl.cs` converts three `TimeoutAfter(ms, attempts)` calls to
+ `TimeoutAfter(ms, timeProvider)`. The `attempts` local is deleted rather than left dangling, so
+ the inert retry parameter is genuinely gone from this path.
+- The seam is exercised by real behavior change in tests: `EtlAsync_DeadlineExpires_...` advances a
+ fake clock by exactly 250 ms and observes the `catch (TimeoutException)` block execute, driving
+ the `EtlAsync` method-span line rate from 0.7000 to 0.9796. A decorative seam would not have
+ moved coverage into a previously unreachable catch block.
+- `ArmingBarrierTimeProvider` is a genuine correctness requirement, not convenience.
+ `FakeTimeProvider` arms timers relative to its current time, so advancing before the production
+ code creates its timer arms that timer past an already-elapsed deadline and the awaiting test
+ hangs. The barrier forwards `CreateTimer` to the inner provider and completes a
+ `TaskCompletionSource` afterwards, so the test can `await barrier.Armed` before calling
+ `Advance`. `TaskCreationOptions.RunContinuationsAsynchronously` is correct here: it prevents a
+ timer-creating production thread from being hijacked to run the test continuation inline.
+ `TrySetResult` rather than `SetResult` is correct because the production loop may arm one more
+ timer than the test drives.
+- `DfDeedleEtlTimeoutTests` demonstrates the mechanism is understood rather than copied: it uses
+ two independent test-owned gates (`gateA` inside `Columns.Add("SentOn")`, `gateB` inside
+ `GetNextRow`) plus an explicit `ReArm()` between them, so the identity of each armed timer is
+ determined by the test rather than by a thread-pool race. Without that, the number of `Armed`
+ signals preceding the ETL deadline would itself be nondeterministic.
+
+Both gates are released in `finally`, so no orphaned `Task.Run` body outlives the test. That
+matters in a `[DoNotParallelize]`-free assembly; leaked blocked pool threads would degrade
+subsequent classes.
+
+### 2.3 Delegate parameters replacing mutable statics — correct, and it narrows the public surface
+
+Replacing `internal static Func<...> TableEtlInvoker` and `StoreTableEtlInvoker` with an optional
+`etl` parameter and a `private static readonly DefaultTableEtl` is the right call. It is
+preference 2 in the `.claude/rules/csharp.md` DI-seam order (injectable delegate for a single call
+path), the default remains safe and deterministic, and it converts two mutable process-wide fields
+into one immutable private one. The `Stores` overload correctly forwards `etl` to the `Store`
+overload, so the injected delegate reaches the inner call.
+
+Constructor injection — preference 1 — is unavailable because all touched members are `static`
+extension or factory methods. A trailing optional parameter is the smallest seam that works. This
+is a correct application of the rule rather than a shortcut around it.
+
+### 2.4 The null-snapshot guard — correctly placed
+
+The guard sits between the `EtlAsync` call and the `LogDfTiming` statement that dereferences
+`tableSnapshot.Item1`, and therefore ahead of the pre-existing `ValidateRequiredEmailColumns`
+guard, which inspects `Item2` only and would not have caught this. The message names the folder and
+states the mechanism ("the table ETL timed out or was cancelled before returning any rows"), which
+satisfies the "fail fast and explicitly" requirement and gives the `QfcDatamodel.FrameBuilding.cs`
+boundary something attributable to log.
+
+One observation, not a defect: the guard tests `tableSnapshot.data is null` while the two lines
+below still read `tableSnapshot.Item1` / `.Item2`. Mixing the named and positional forms of the
+same tuple within four lines is slightly inconsistent. The positional reads are pre-existing lines
+and switching them would have added diff noise, so the choice is defensible, but a future edit
+should unify on the named form.
+
+### 2.5 `TextWriter` seams — correct, with one asymmetry
+
+`PrintTree`, both `PrettyPrint` overloads, `EnumerateTable` and the new `GFG.Run` all take the
+writer as a parameter and resolve `null` to `Console.Out`. `EnumerateTable` hoists the resolution
+into a local `target` once rather than repeating `(writer ?? Console.Out)` three times, which is
+the better form; `PrintTree` repeats the expression once per call and also forwards the original
+nullable `writer` on recursion, which is correct (resolving at each level would be equivalent but
+allocates nothing either way). The asymmetry is cosmetic.
+
+`GFG.Main` keeping its `String[] args` parameter while delegating to `Run(Console.Out)` preserves
+the existing entry-point shape. Reasonable.
+
+## 3. Test Quality
+
+### 3.1 Strengths
+
+- Assertions are specific. `ThrowAsync().WithMessage("*Inbox*")` will
+ fail if the code regresses to `NullReferenceException` or drops the folder name, which is exactly
+ what a generic `Should().Throw()` would have missed.
+- `MockBehavior.Strict` is used on `Row`, `Table`, `Column`, `TableView` and `Explorer` in
+ `DfDeedleEtlTimeoutTests`, so an unanticipated production call fails the test rather than
+ returning a silent default.
+- `SilentProgressTracker` overrides all three `Report` members to no-ops, removing progress
+ reporting as a timing or output variable. This is a deliberate isolation choice, not incidental.
+- The green-path tests use an un-advanced `FakeTimeProvider` rather than a widened deadline. This is
+ the distinction AC5 turns on and the change gets it right: the deadline is not made larger, it is
+ made unreachable by a clock the test owns.
+- The retired `Returns(120)` tolerance is verified gone from the final tree, not merely absent from
+ the added lines. Checking the final state as well as the diff is the correct verification shape.
+
+### 3.2 Findings
+
+**C-1 (Minor).** `OlTableExtensions_Tests.EnumerateTable_WritesFormattedOutputToSuppliedWriterAndMovesToStart`
+performs two Acts: it asserts on the supplied-writer path, then invokes the null-writer path with
+`FluentActions.Invoking(...).Should().NotThrow()` before the final `Verify`. The in-code comment
+states the reason ("this file is at its line ceiling"). This trades test isolation for compliance
+with the 500-line cap on an already-1846-line file. The trade is understandable but the better
+resolution is a small new test file, which would satisfy both constraints. Recommend as follow-up.
+
+**C-2 (Minor).** `Main_RunsSampleScenarioWithoutThrowing` is now a bare `NotThrow` assertion. It
+does still cover the `Main` to `Run(Console.Out)` default branch, and the substantive assertions
+moved to `Run_WritesScenarioToSuppliedWriter`, so the pair is complete. But the surviving test's
+name promises "runs sample scenario" while asserting only absence of an exception. The doc comment
+above it states this accurately, so the intent is documented; the name is slightly stronger than
+the assertion.
+
+**C-3 (Minor).** `PrettyPrint_NullWriter_WritesToConsoleWithoutThrowing` and the null-writer half
+of C-1 write to the real `Console.Out` during a now-parallel-eligible run. This is harmless because
+after this change no test captures `Console.Out` — but that safety is a property of the whole
+assembly, not of these tests, and nothing enforces it. If a future test reintroduces a
+`Console.Out` capture, these two null-writer paths become new noise sources. A one-line note in the
+class header of each would make the dependency explicit.
+
+**C-4 (Observation).** `DfDeedleEtlTimeoutTests` and `OlTableExtensionsEtlClockTests` both carry
+`[DoNotParallelize]` with a stated reason ("drives a real `Task.Run` gate"). That is the correct
+default for gate-driving classes and it is consistent with the pre-existing
+`DfDeedleQfcColumnTimeoutTests`. Net effect on the serialized set: minus three classes (attributes
+removed) plus two classes (new gate-driving classes), so the serialized set shrinks by one.
+
+**C-5 (Observation).** `DfDeedle_COM_Tests` still swaps `DfDeedle.MessageBoxInvoker` in four tests.
+The class header now asserts it "has no reader on any parallel-phase path". This reviewer verified
+that claim: the only production readers are `DfDeedle.QfcColumns.cs:27,170,194`, reached by exactly
+three test classes, of which two are `[DoNotParallelize]` and the third is `DfDeedle_COM_Tests`
+itself (serialised internally by `ClassLevel` scope). The claim holds today. It is not enforced by
+any test or analyzer, and it rests on the MSTest phase-ordering assumption that `spec.md` Risk 3
+explicitly labels documentation-sourced and not run-verified — the same assumption the AC3 seam
+work was intended to stop relying on. Recommend the class header state the dependency ("this holds
+only while both reader classes remain `[DoNotParallelize]`") so a future edit cannot silently break
+it.
+
+## 4. Directed Assessment: the three removed `[DoNotParallelize]` attributes
+
+The reviewer was asked to reach an independent verdict on three specific questions. The findings
+below are this reviewer's own; they do not adopt the executor's classification.
+
+### 4.1 Is removing the three attributes a legitimate consequence of AC3, or unnecessary widening?
+
+**Legitimate, and required by the criterion's own scope definition.**
+
+`spec.md` line 55 defines the AC3 work as: "introduce a `TextWriter` seam on the four production
+members whose output is captured by tests, convert those tests to their own writer, **and remove the
+`[DoNotParallelize]` serialization stopgap that currently suppresses the race**." The removal is not
+an incidental side effect; it is the deliverable. `spec.md` line 62 gives the reason: with the
+attribute in place, the AC4 gate would exercise the stopgap rather than the seam, and the seam would
+be unproven. That reasoning is sound. A fix that leaves the suppressant in place is untestable by
+construction.
+
+Three of four attributes were removed. The fourth, on `OlTableExtensions_Tests`, was deliberately
+retained under the conservative fallback that `spec.md` Mitigation 4 pre-authorised, and its comment
+was rewritten to stop claiming the console as its reason ("The console reason for this attribute was
+removed by the `TextWriter` seam under #811. It is retained because this class has not been soaked
+under class-level parallelism and ten of its tests drive the 2000 ms `GetTableInViewAsync` window").
+That is the correct judgment: the class is 1846 lines of COM mocks with a documented untested
+parallel-safety profile, and the comment now states a true reason rather than a stale one. Retaining
+it is not an inconsistency; it is the risk-weighted subset.
+
+Scope check: each of the three removals is confined to the file that owned the captured output, and
+each is paired with the seam conversion in the same file. No unrelated class lost an attribute. This
+is not widening.
+
+### 4.2 Can raising parallel density reasonably be judged to have increased the surfacing probability of the `ILGlobals` race?
+
+**No — not on any evidence available, and the mechanism argues against it. The claim is not
+supportable in either direction beyond "scheduling was perturbed".**
+
+The reasoning, which this reviewer derived rather than inherited:
+
+1. **The instantaneous concurrency is capped by the worker count, not by the class count.**
+ `AssemblyInfo.cs:18-21` sets `Workers = 0`, which resolved to 24 on the run host. During the
+ parallel phase, at most 24 classes execute at once regardless of how many are eligible.
+
+2. **The eligible pool is already an order of magnitude larger than the cap.** A search for
+ `[TestClass]` across `UtilitiesCS.Test` returns more than 400 files, against roughly 35 classes
+ carrying `[DoNotParallelize]`. The parallel phase is worker-saturated throughout; it is not
+ starved for runnable classes. Adding three more classes to a pool of ~400 does not raise the
+ number of classes executing concurrently, because that number was already pinned at 24.
+
+3. **What actually changes is the dispatch order and the parallel-phase duration.** Three short,
+ purely in-memory classes (a stack sample, a DataFrame pretty-printer, a DASL string parser) move
+ from the sequential tail into the saturated pool. Their combined runtime is a negligible fraction
+ of a 51-to-74-second run over 7162 tests. The perturbation to when `ILGlobals_Tests` and
+ `MethodBodyReader_Tests` happen to be co-scheduled is real but undirected: it can as easily
+ reduce the overlap as increase it. There is no argument, and no measurement in the evidence, that
+ establishes a direction.
+
+4. **The race's participants and window are untouched.** Both racing classes were parallel-eligible
+ before this change and are parallel-eligible after. Neither is in the write set. Neither gained
+ nor lost an attribute. The vulnerable window — the interval in `LoadOpCodes()` between the
+ reassignment at line 123 and the completion of the fill loop at line 137 — is byte-identical.
+
+5. **Counter-consideration, stated fairly.** The three moved classes do consume worker slots they
+ previously did not, which marginally extends the saturated window. If one insists on a
+ directional claim, that is the only mechanism available, and its magnitude is on the order of
+ three short classes out of more than four hundred — well under one percent, against a race that
+ was already surfacing at an unquantified base rate. The evidence does not measure the base rate:
+ the two baseline runs (P0-T10, P0-T12) did not exhibit it, but two single runs of an
+ intermittent failure establish nothing about its frequency. Attributing run 7 to this change is
+ therefore speculation, and the executor's own hedged wording ("can therefore alter the
+ probability that any latent race surfaces") is the most that the evidence supports.
+
+**Verdict: the AC4 failure is wholly attributable to a pre-existing defect. This change did not
+create it, did not make its participants newly concurrent, and did not measurably increase its
+exposure. What this change did do is bring the defect to light by running the suite ten times,
+which is the gate working as designed.**
+
+### 4.3 Blocking or Non-blocking for the AC4 shortfall?
+
+**Non-blocking**, for the following reasons, weighed against the strongest contrary argument.
+
+The strongest argument for Blocking is that the item's stated purpose (`spec.md` Context) is "so the
+required `mstest-coverage` check stops failing on unrelated pull requests", and with the `ILGlobals`
+race unfixed the check can still fail intermittently. On that reading the business objective is not
+achieved and the branch should not merge.
+
+That argument does not survive scrutiny for three reasons:
+
+1. **The branch is a strict improvement, and blocking it makes the stated problem worse, not
+ better.** Three failure modes are removed with strong evidence — 130 of 130 tracked assertions
+ `Passed` across ten runs, including both intermittent-failure sentinels. Holding the fix hostage
+ to a fourth, unrelated defect leaves four failure modes in `main` instead of one.
+
+2. **Fixing `ILGlobals` here would violate the repository's own bugfix discipline.** CLAUDE.md's
+ Bugfix Workflow requires the minimal targeted fix and states: "If you uncover deeper design
+ problems, open a new issue instead of widening scope." The `ILGlobals` fix touches two production
+ files and two test classes that are outside the declared 20-path write set, and doing it properly
+ would need its own RED-first regression test for a race that is not deterministically
+ reproducible. That is a separate item.
+
+3. **The shortfall is reported honestly and no re-run-until-green was attempted.** AC4 is left
+ unchecked in `spec.md`, the ten-run artifact states "AC4 IS NOT SATISFIED" in its verdict line,
+ and the issue-update artifact records 4 of 5 delivered against a plan that predicted 5 of 5,
+ explicitly refusing to write the predicted value. Given that AC4 exists precisely to stop
+ re-run-until-green, an executor that re-ran until green would deserve a blocking finding; one
+ that reported the failure does not.
+
+**What is Blocking is the disposal of the finding, not the finding itself.** The `ILGlobals` race
+exists only as prose inside a feature folder that is archived at merge, with no
+`docs/features/potential/` entry and no issue. The executor's stated reason — "authoring it was not
+in this plan's write set" — is contradicted by the same plan task having authored two other
+`docs/features/potential/` entries. That is finding F-1 in `policy-audit.2026-09-08T11-30.md` and it
+is the one item that must be closed before merge. It costs one file.
+
+## 5. Findings Summary
+
+| ID | Severity | Summary | Location |
+|---|---|---|---|
+| C-1 | Minor | One test performs two Acts to respect a 500-line ceiling | `OlTableExtensions_Tests.cs`, `EnumerateTable_WritesFormattedOutputToSuppliedWriterAndMovesToStart` |
+| C-2 | Minor | Test name is stronger than its surviving assertion | `StackGeek_Tests.Main_RunsSampleScenarioWithoutThrowing` |
+| C-3 | Minor | Two null-writer tests write to real `Console.Out` under an unenforced assembly-wide assumption | `PrettyPrint_Tests`, `OlTableExtensions_Tests` |
+| C-4 | Observation | New gate-driving classes correctly `[DoNotParallelize]`; serialized set shrinks by one | `DfDeedleEtlTimeoutTests.cs`, `OlTableExtensionsEtlClockTests.cs` |
+| C-5 | Observation | `MessageBoxInvoker` safety claim is true but unenforced and rests on an unverified ordering assumption | `DfDeedle_COM_Tests.cs` header, `DfDeedle.cs:60` |
+| C-6 | Observation | Named and positional tuple access mixed within four lines | `DfDeedle.cs`, null-snapshot guard and `LogDfTiming` |
+
+No finding in this artifact is Blocking. None requires a code change before merge.
+
+## 6. Recommendations
+
+1. Close F-1 from the policy audit: file the `ILGlobals` race as a
+ `docs/features/potential/` entry and promote it to an issue. Candidate fixes, in order of
+ preference: initialise both tables in a static constructor or via `Lazy` so they are
+ published once and immutably; or take a `lock` inside `LoadOpCodes()` and around the reads; or,
+ as an interim stopgap only, add `[DoNotParallelize]` to `ILGlobals_Tests` and
+ `MethodBodyReader_Tests`. The first is preferred because `LoadOpCodes()` is idempotent and the
+ tables are conceptually constant.
+2. Split the two Acts in C-1 into a separate small test file, which also reduces a file that is
+ 1346 lines over the cap.
+3. Add the enforcement caveat described in C-5 to the `DfDeedle_COM_Tests` class header.
+4. Consider following the `spec.md` rollout list item to soak `OlTableExtensions_Tests` without
+ `[DoNotParallelize]` as separate work, now that its console reason is genuinely gone.
diff --git a/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/baseline/p0-t10-vstest-coverage.md b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/baseline/p0-t10-vstest-coverage.md
new file mode 100644
index 000000000..58db4aa89
--- /dev/null
+++ b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/baseline/p0-t10-vstest-coverage.md
@@ -0,0 +1,69 @@
+# P0-T10 — Baseline toolchain step 4 (nine-assembly coverage run)
+
+Timestamp: 2026-09-08T09-25
+Task: [P0-T10]
+Command: dotnet-coverage collect --output coverage/p0-baseline.cobertura.xml --output-format cobertura --settings coverage.config -- /Settings:scripts\vscode\TaskMaster.cli.runsettings /InIsolation "/Logger:trx;LogFileName=p0-t10.trx" /ResultsDirectory:coverage/trx/p0-t10 /Blame:CollectHangDump;TestTimeout=4min;HangDumpType=None "/TestCaseFilter:TestCategory!=LiveOutlook&FullyQualifiedName!~ShellUtilities&FullyQualifiedName!~SysImageListHelper&FullyQualifiedName!~OSBrowser"
+EXIT_CODE: 0
+
+This is the mandatory numeric coverage baseline. It uses the D15 argument shape: the repository
+wrapper's own shape, issued directly rather than through `Invoke-MSTestWithCoverage.ps1`, because
+that wrapper hard-codes its filter and cannot take the shell-icon exclusion, and because its
+assembly discovery rejects every assembly under a dot-claude path when run from an agent worktree.
+
+## TRX counters
+
+`coverage/trx/p0-t10/p0-t10.trx` exists.
+
+```
+total=7153 executed=7153 passed=7153 failed=0 error=0 timeout=0 aborted=0 notExecuted=0
+```
+
+PREEXISTING_FAILURE_SET: none
+
+`failed` + `error` + `aborted` + `timeout` = 0. No test failed on this run, including the two
+sentinels this item repairs:
+`DictionaryExtensions_Tests.TryAddValuesAsync_UpdatesExistingValue` and
+`DfDeedle_COM_Tests.GetEmailDataInViewAsync_SeparatesTableSnapshotFromDataFrameTransform`. Both
+passed. That is the expected shape of a load-dependent defect: the failures are intermittent, not
+deterministic, which is exactly why AC1 has no deterministic RED (D6) and why AC4 requires ten
+consecutive runs rather than one. A green baseline is not evidence that the defect is absent.
+
+## Root Cobertura figures
+
+Read from the root element of `coverage/p0-baseline.cobertura.xml`. `line-rate` and `branch-rate`
+are fractions, not percentages.
+
+| Attribute | Value |
+|---|---|
+| `line-rate` | `0.8601092896174863` |
+| `lines-covered` | `172353` |
+| `lines-valid` | `200385` |
+| `branch-rate` | `0.6630576006929406` |
+| `branches-covered` | `21434` |
+| `branches-valid` | `32326` |
+
+Line coverage is 86.01 percent repository-wide on the merged denominator.
+
+## Wall clock
+
+RUN_SECONDS: 64
+
+vstest reported `Total time: 1.0297 Minutes`. The #798 baseline measured 54.5 s for the same
+nine-assembly shape, so this run is consistent with it.
+
+## Acceptance evaluation
+
+- `EXIT_CODE: 0` recorded; the run had no failing test, so the zero-failure branch of the
+ acceptance applies and no `PREEXISTING_FAILURE_SET:` entries exist. PASS
+- `coverage/trx/p0-t10/p0-t10.trx` exists and its counters are recorded in the
+ `total= executed= passed= failed= error= timeout= aborted= notExecuted=` form. PASS
+- `failed` + `error` + `aborted` + `timeout` equals 0. PASS
+- `coverage/p0-baseline.cobertura.xml` exists and all six root figures are recorded as numbers.
+ PASS
+
+## Output Summary
+
+Nine-assembly `/InIsolation` coverage baseline: 7153 tests, 7153 passed, 0 failed, 64 s wall
+clock, exit 0. Repository-wide line coverage 0.8601 (172353 of 200385 lines); branch coverage
+0.6631 (21434 of 32326 branches). Raw Cobertura and TRX documents stay under the gitignored
+`coverage/` directory and are not committed (D15).
diff --git a/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/baseline/p0-t11-per-file-coverage.md b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/baseline/p0-t11-per-file-coverage.md
new file mode 100644
index 000000000..20cf1a7af
--- /dev/null
+++ b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/baseline/p0-t11-per-file-coverage.md
@@ -0,0 +1,89 @@
+# P0-T11 — Baseline per-file coverage for the eight production files
+
+Timestamp: 2026-09-08T09-26
+Task: [P0-T11]
+Command: pwsh -NoProfile -File coverage/plan811-helper.ps1 running PROC-COV (`Get-FileLineMap`, `Get-FileSummary`, `Get-MethodLineRate`) over `coverage/p0-baseline.cobertura.xml`
+EXIT_CODE: 0
+
+PROC-COV merges the `./lines/line` and `./methods/method/lines/line` node axes by line number,
+keeping the maximum `hits`, which is the repository helper's own dedup rule. Suffixes are written
+with the single backslashes the collector emits.
+
+## Per-file summaries
+
+| Suffix | valid | covered | uncovered |
+|---|---|---|---|
+| `UtilitiesCS\Extensions\DictionaryExtensions.cs` | 108 | 101 | 7 |
+| `UtilitiesCS\OutlookObjects\Table\OlTableExtensions.Etl.cs` | 297 | 271 | 26 |
+| `UtilitiesCS\Extensions\DfDeedle.cs` | 159 | 157 | 2 |
+| `UtilitiesCS\Extensions\DfDeedle.FrameUtilities.cs` | 169 | 117 | 52 |
+| `UtilitiesCS\OutlookObjects\Filter DASL\DASLFilterParser.cs` | 79 | 76 | 3 |
+| `UtilitiesCS\HelperClasses\PrettyPrint.cs` | 440 | 375 | 65 |
+| `UtilitiesCS\OutlookObjects\Table\OlTableExtensions.TableAccess.cs` | 270 | 215 | 55 |
+| `UtilitiesCS\ReusableTypeClasses\Other\StackGeek.cs` | 93 | 93 | 0 |
+
+Every one of the eight matched at least one `class` element, so `valid` is greater than 0 in all
+eight cases and no suffix silently measured nothing.
+
+## Baseline uncovered ceilings for P7-T6
+
+These are the figures P7-T6 clause 1 compares against; final `uncovered` must be at most the value
+below for each file.
+
+```
+BASELINE_UNCOVERED: UtilitiesCS\Extensions\DictionaryExtensions.cs=7
+BASELINE_UNCOVERED: UtilitiesCS\OutlookObjects\Table\OlTableExtensions.Etl.cs=26
+BASELINE_UNCOVERED: UtilitiesCS\Extensions\DfDeedle.cs=2
+BASELINE_UNCOVERED: UtilitiesCS\Extensions\DfDeedle.FrameUtilities.cs=52
+BASELINE_UNCOVERED: UtilitiesCS\OutlookObjects\Filter DASL\DASLFilterParser.cs=3
+BASELINE_UNCOVERED: UtilitiesCS\HelperClasses\PrettyPrint.cs=65
+BASELINE_UNCOVERED: UtilitiesCS\OutlookObjects\Table\OlTableExtensions.TableAccess.cs=55
+BASELINE_UNCOVERED: UtilitiesCS\ReusableTypeClasses\Other\StackGeek.cs=0
+```
+
+## `StackGeek.Main` method line rate
+
+MAIN_LINE_RATE: 1
+
+`Get-MethodLineRate` for suffix `UtilitiesCS\ReusableTypeClasses\Other\StackGeek.cs`, method
+`Main`, returned the number `1`, not `ABSENT`. `Main` is fully covered at baseline, which is the
+comparison point for the new `Run(TextWriter)` extraction that P5-T4 creates and P7-T6 clause 3
+holds to a line rate of at least 0.90.
+
+## `EtlAsync` span
+
+`EtlAsync` is `async` and emits no `` element in this pipeline's Cobertura output, so the
+span rule applies instead: the declaration line through the closing brace.
+
+```
+ETLASYNC_SPAN: 66-130
+ETLASYNC_SPAN_RATE: 35/50
+```
+
+50 of the 65 lines in the span carry a line node; 35 of those are covered, giving a span rate of
+0.700. The 15 uncovered lines in the span are:
+
+```
+94, 109, 110, 111, 112, 113, 114, 115, 117, 118, 119, 120, 121, 122, 123
+```
+
+Lines 117 through 123 are the `catch (TimeoutException)` block — the deadline-expiry path that is
+the mechanism of the AC2 defect and is entirely unexercised at baseline. Line 114 is the
+`.TimeoutAfter(milliseconds, attempts)` call on the `GetArray` branch. P7-T6 clause 4 requires the
+final span rate to be at least this 35/50, and the new
+`OlTableExtensionsEtlClockTests.EtlAsync_DeadlineExpires_ReturnsNullDataAndCancelsTokenSource`
+test is what covers the `catch` block for the first time.
+
+## Acceptance evaluation
+
+- All eight suffixes matched at least one `class` element with `valid` greater than 0. PASS
+- `Get-MethodLineRate` for `Main` returned `1`, a number rather than `ABSENT`. PASS
+- The `EtlAsync` span `valid` is 50, greater than 0. PASS
+- `BASELINE_UNCOVERED: =` is recorded for each of the eight. PASS
+
+## Output Summary
+
+Eight production files measured against the P0-T10 baseline Cobertura document. Uncovered line
+counts range from 0 (`StackGeek.cs`) to 65 (`PrettyPrint.cs`). `StackGeek.Main` is at line rate 1.
+The `EtlAsync` span 66-130 is at 35 of 50 covered lines, with the entire `catch (TimeoutException)`
+block (117-123) uncovered at baseline.
diff --git a/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/baseline/p0-t12-ci-shape-probe.md b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/baseline/p0-t12-ci-shape-probe.md
new file mode 100644
index 000000000..8d2b8d488
--- /dev/null
+++ b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/baseline/p0-t12-ci-shape-probe.md
@@ -0,0 +1,64 @@
+# P0-T12 — CI-verbatim AC4 command-shape probe
+
+Timestamp: 2026-09-08T09-28
+Task: [P0-T12]
+Command: /EnableCodeCoverage /InIsolation "/Logger:trx;LogFileName=p0-t12.trx" /ResultsDirectory:coverage/trx/p0-t12 /Blame:CollectHangDump;TestTimeout=4min;HangDumpType=None "/TestCaseFilter:TestCategory!=LiveOutlook&FullyQualifiedName!~ShellUtilities&FullyQualifiedName!~SysImageListHelper&FullyQualifiedName!~OSBrowser"
+EXIT_CODE: 0
+
+AC4_COMMAND_SHAPE: CI-VERBATIM
+
+This probe was run once against the pre-change tree, before any source edit, to establish whether
+the CI-verbatim shape is locally viable. No `/Settings:` file is passed, so parallelism comes only
+from `[assembly: Parallelize(Workers = 0, Scope = ClassLevel)]` at
+`UtilitiesCS.Test/Properties/AssemblyInfo.cs:18-21`, exactly as in
+`.github/workflows/_mstest-coverage.yml:99`. The nine assemblies are enumerated explicitly rather
+than discovered recursively, so the dot-claude worktree discovery problem cannot arise.
+
+## TRX counters
+
+`coverage/trx/p0-t12/p0-t12.trx` exists.
+
+```
+total=7153 executed=7153 passed=7153 failed=0 error=0 timeout=0 aborted=0 notExecuted=0
+```
+
+No test produced an outcome other than `Passed`. The failing-test enumeration the acceptance
+condition would otherwise require is therefore empty, and the CI-VERBATIM branch applies: the set
+of failing tests is a subset of the six named failure modes this item repairs because it is the
+empty set.
+
+## Wall clock
+
+PROBE_SECONDS: 62
+
+A P8 pair task runs two such runs back to back, so the estimate for each of P8-T1 through P8-T5 is
+about 124 s of test time plus process startup, comfortably inside the 600000 ms tool timeout.
+
+## `.coverage` file cleanup
+
+| Observation | Value |
+|---|---|
+| `*.coverage` files found under `coverage/trx/p0-t12` | 2 |
+| `*.coverage` files remaining after deletion | 0 |
+
+vstest emits two `.coverage` files per run under `/EnableCodeCoverage`. Both were deleted after
+the counters were read, as the task requires. They are large binaries and are never committed.
+
+## Consequence for Phase 8
+
+Because `AC4_COMMAND_SHAPE` is `CI-VERBATIM`, the P8-T1 through P8-T5 runs use the base argument
+shape with no `/Settings:` argument. The `RUNSETTINGS-FALLBACK` branch does not apply and
+`/Settings:TaskMaster.runsettings` is not added.
+
+## Acceptance evaluation
+
+- `coverage/trx/p0-t12/p0-t12.trx` exists. PASS
+- Counters recorded. PASS
+- `PROBE_SECONDS: 62` recorded. PASS
+- Exactly one of the two shape verdicts is recorded: `AC4_COMMAND_SHAPE: CI-VERBATIM`. PASS
+- `.coverage` files under the results directory were deleted after the counters were read. PASS
+
+## Output Summary
+
+The CI-verbatim AC4 command shape is locally viable. One probe run: 7153 tests, 7153 passed, 0
+failed, 62 s, exit 0. Phase 8 will use the CI-verbatim shape with no runsettings file.
diff --git a/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/baseline/p0-t2-worktree-identity.md b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/baseline/p0-t2-worktree-identity.md
new file mode 100644
index 000000000..8a81e06b3
--- /dev/null
+++ b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/baseline/p0-t2-worktree-identity.md
@@ -0,0 +1,51 @@
+# P0-T2 — Worktree identity and anchor commit
+
+Timestamp: 2026-09-08T09-17
+Task: [P0-T2]
+Command: pwsh -NoProfile -File coverage/plan811-helper.ps1 (C1 preamble, then the six git observations below)
+EXIT_CODE: 0
+
+BASE-SHA: bb1c7d4b60f7b782227956f36859314d5c47bb03
+
+## Observations
+
+| Observation | Value |
+|---|---|
+| `git rev-parse HEAD` | `bb1c7d4b60f7b782227956f36859314d5c47bb03` |
+| `git rev-parse --abbrev-ref HEAD` | `bug/utilitiescs-test-determinism-780-803-594-811` |
+| `git cat-file -t bb1c7d4b60f7b782227956f36859314d5c47bb03` | `commit` |
+| `git merge-base HEAD bb1c7d4b60f7b782227956f36859314d5c47bb03` | `bb1c7d4b60f7b782227956f36859314d5c47bb03` |
+| `git rev-parse origin/main` | `0e9c95a5dd45104d82f46fd801973a6bc068f25f` |
+| `git merge-base --is-ancestor origin/main HEAD` exit code | `0` |
+
+## Scoped porcelain
+
+`git status --porcelain --untracked-files=all -- . ":(exclude).claude"`
+
+```
+ M docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/plan.2026-09-07T22-03.md
+?? docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/baseline/phase0-instructions-read.md
+```
+
+Both entries are under the feature folder. No path outside `/` is listed. The modified
+plan file carries the orchestrator's re-anchor edit (decision D16) plus this executor's P0-T1
+check-off. The untracked file is the P0-T1 evidence artifact. The C1 helper script
+`coverage/plan811-helper.ps1` does not appear because `coverage/*` is gitignored (`.gitignore:144`).
+
+## Acceptance evaluation
+
+- Branch name equals `bug/utilitiescs-test-determinism-780-803-594-811`. PASS
+- `cat-file -t` prints `commit`. PASS
+- Merge-base output equals the anchor, so the anchor is an ancestor of HEAD; here it is HEAD
+ itself, as expected until P7-T12 commits. PASS
+- Scoped porcelain lists only paths under `/`. PASS
+- `BASE-SHA:` present on its own line; no host path anywhere in this artifact. PASS
+- `git merge-base --is-ancestor origin/main HEAD` exited 0, so the merged `origin/main`
+ (`0e9c95a5dd45104d82f46fd801973a6bc068f25f`) is still contained in this branch. Main has not
+ advanced since the re-anchor. No reconciliation is required before source edits. PASS
+
+## Output Summary
+
+Worktree identity confirmed. HEAD equals the D16 anchor `bb1c7d4b`. `origin/main` at `0e9c95a5`
+is an ancestor of HEAD (exit 0), so the re-anchored base is still valid. Working tree carries only
+two feature-folder paths and nothing outside them.
diff --git a/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/baseline/p0-t3-toolchain-bootstrap.md b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/baseline/p0-t3-toolchain-bootstrap.md
new file mode 100644
index 000000000..3f31f0e35
--- /dev/null
+++ b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/baseline/p0-t3-toolchain-bootstrap.md
@@ -0,0 +1,51 @@
+# P0-T3 — Repo-local SDK bootstrap and tool-manifest restore
+
+Timestamp: 2026-09-08T09-18
+Task: [P0-T3]
+Command: pwsh -NoProfile -File coverage/plan811-helper.ps1 wrapping `pwsh -NoProfile -File scripts/vscode/Install-RepoDotNetSdk.ps1`, then `.dotnet-sdk\dotnet.exe --version`, then `.dotnet-sdk\dotnet.exe tool restore`
+EXIT_CODE: 0
+
+BRANCH APPLIED: INSTALL
+
+`Test-Path .dotnet-sdk\dotnet.exe` returned `False` before the task, so the SDK acquisition branch
+applied. This is a fresh agent worktree that had never been bootstrapped for C# builds.
+
+## Observations
+
+| Observation | Value |
+|---|---|
+| `Test-Path .dotnet-sdk\dotnet.exe` before | `False` |
+| `Install-RepoDotNetSdk.ps1` exit code | `0` |
+| `Test-Path .dotnet-sdk\dotnet.exe` after | `True` |
+| `dotnet --version` | `8.0.205` |
+| `dotnet --version` exit code | `0` |
+| `dotnet tool restore` exit code | `0` |
+
+Installer output (host path redacted to ``):
+
+```
+Downloading .NET SDK 8.0.205 from https://builds.dotnet.microsoft.com/dotnet/Sdk/8.0.205/dotnet-sdk-8.0.205-win-x64.zip...
+Installed repo-local .NET SDK 8.0.205 to \.dotnet-sdk.
+```
+
+Tool-restore output, verbatim:
+
+```
+Tool 'csharpier' (version '1.2.6') was restored. Available commands: csharpier
+
+Restore was successful.
+```
+
+## Acceptance evaluation
+
+- `dotnet --version` prints `8.0.205`, matching the `global.json` pin. PASS
+- Tool-restore output contains the line
+ `Tool 'csharpier' (version '1.2.6') was restored. Available commands: csharpier`. PASS
+- Both commands exited 0. PASS
+- The artifact records which branch applied (INSTALL). PASS
+
+## Output Summary
+
+Repo-local .NET SDK 8.0.205 downloaded and installed under `.dotnet-sdk` on the INSTALL branch.
+Manifest-pinned CSharpier 1.2.6 restored. Both steps exited 0, so the format gate can now be
+invoked through `dotnet tool run`.
diff --git a/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/baseline/p0-t4-package-restore.md b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/baseline/p0-t4-package-restore.md
new file mode 100644
index 000000000..2d6050616
--- /dev/null
+++ b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/baseline/p0-t4-package-restore.md
@@ -0,0 +1,36 @@
+# P0-T4 — NuGet packages.config restore
+
+Timestamp: 2026-09-08T09-18
+Task: [P0-T4]
+Command: pwsh -NoProfile -File scripts/vscode/Invoke-Restore.ps1
+EXIT_CODE: 0
+
+The wrapper runs msbuild `/t:Restore /p:RestorePackagesConfig=true` against `TaskMaster.sln`. Its
+`-SolutionPath`, `-Configuration` and `-Platform` parameters were left at their defaults
+(`TaskMaster.sln`, `Debug`, `Any CPU`). No package was added, removed or version-changed by this
+task; `packages.config` files are untouched.
+
+## Observations
+
+| Observation | Value |
+|---|---|
+| `Invoke-Restore.ps1` exit code | `0` |
+| msbuild summary | `Build succeeded.` / `0 Warning(s)` / `0 Error(s)` |
+| `Test-Path packages\Microsoft.Bcl.TimeProvider.10.0.11\lib` | `True` |
+| `Test-Path packages\Microsoft.Extensions.TimeProvider.Testing.10.9.0\lib` | `True` |
+| Directories directly under `packages\` | `172` |
+
+## Acceptance evaluation
+
+- Exit code 0. PASS
+- Both seam-package `lib` directories print `True`. These are the two packages spec.md relies on
+ (`Microsoft.Bcl.TimeProvider 10.0.11` for the production `TimeProvider` seam,
+ `Microsoft.Extensions.TimeProvider.Testing 10.9.0` for `FakeTimeProvider` in tests). Both were
+ already referenced before this change; the plan adds no package. PASS
+- The count of directories directly under `packages\` is recorded (172). PASS
+
+## Output Summary
+
+Solution-wide packages.config restore succeeded with 0 warnings and 0 errors. 172 package
+directories present. Both `TimeProvider` seam packages resolved, so the seam work in Phase 1 and
+the `FakeTimeProvider` tests in Phases 2, 5 and 6 can compile without any package change.
diff --git a/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/baseline/p0-t5-tool-resolution.md b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/baseline/p0-t5-tool-resolution.md
new file mode 100644
index 000000000..f499acfb4
--- /dev/null
+++ b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/baseline/p0-t5-tool-resolution.md
@@ -0,0 +1,38 @@
+# P0-T5 — External tool resolution
+
+Timestamp: 2026-09-08T09-19
+Task: [P0-T5]
+Command: pwsh -NoProfile -File coverage/plan811-helper.ps1 (C1 preamble resolves `$vstest` and `$msbuild` through vswhere; then `Get-Command dotnet-coverage` and `dotnet-coverage --version`)
+EXIT_CODE: 0
+
+All resolved paths below are redacted: the Visual Studio installation root is written as
+`` and the user profile root as ``.
+
+## Observations
+
+| Tool | Resolved (redacted) | Non-empty | `Test-Path` |
+|---|---|---|---|
+| `vstest.console.exe` | `\Common7\IDE\Extensions\TestPlatform\vstest.console.exe` | `True` | `True` |
+| `MSBuild.exe` | `\MSBuild\Current\Bin\MSBuild.exe` | `True` | `True` |
+| `dotnet-coverage` | `\.dotnet\tools\dotnet-coverage.exe` | `True` | resolved by `Get-Command` |
+
+| Observation | Value |
+|---|---|
+| `dotnet-coverage` branch | ALREADY-PRESENT (no install was required) |
+| `dotnet-coverage --version` | `18.10.0+f4cc39224845ffa74bf246c9da2399d50e5d6342` |
+| `dotnet-coverage --version` exit code | `0` |
+
+## Acceptance evaluation
+
+- `$vstest` and `$msbuild` are both non-empty and `Test-Path` on each prints `True`. PASS
+- `dotnet-coverage --version` exited 0 and printed a version string beginning with a digit
+ (`18.10.0...`). PASS
+- `Get-Command dotnet-coverage` succeeded, so the global-install branch did not apply; the
+ ALREADY-PRESENT branch is recorded. PASS
+- Every resolved path is redacted to `` or `` in this artifact. PASS
+
+## Output Summary
+
+All three external tools resolved on the first attempt. vstest.console.exe and MSBuild.exe located
+through vswhere against the latest installed Visual Studio; dotnet-coverage 18.10.0 already present
+as a global .NET tool, so no install branch was taken.
diff --git a/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/baseline/p0-t6-citation-baseline.md b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/baseline/p0-t6-citation-baseline.md
new file mode 100644
index 000000000..d0213cd62
--- /dev/null
+++ b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/baseline/p0-t6-citation-baseline.md
@@ -0,0 +1,99 @@
+# P0-T6 — Citation baseline against the merged tree
+
+Timestamp: 2026-09-08T09-20
+Task: [P0-T6]
+Command: pwsh -NoProfile -File coverage/plan811-helper.ps1 — `(Get-Content -LiteralPath ).Count` for line counts, `@(Select-String -LiteralPath -CaseSensitive -SimpleMatch '').Count` for token counts, `Test-Path` for the three new files
+EXIT_CODE: 0
+
+MISMATCH_COUNT: 0
+
+No `CITATION-MISMATCH:` line was produced. Execution proceeds; the plan's one permitted halt did
+not fire. All measurements were taken against the merged base tree
+`bb1c7d4b60f7b782227956f36859314d5c47bb03` (decision D16), before any source edit.
+
+## Line counts (17 of 17 match)
+
+| Path | Expected | Observed |
+|---|---|---|
+| `UtilitiesCS/Extensions/DictionaryExtensions.cs` | 282 | 282 |
+| `UtilitiesCS/OutlookObjects/Table/OlTableExtensions.Etl.cs` | 474 | 474 |
+| `UtilitiesCS/Extensions/DfDeedle.cs` | 314 | 314 |
+| `UtilitiesCS/Extensions/DfDeedle.FrameUtilities.cs` | 276 | 276 |
+| `UtilitiesCS/OutlookObjects/Filter DASL/DASLFilterParser.cs` | 122 | 122 |
+| `UtilitiesCS/HelperClasses/PrettyPrint.cs` | 680 | 680 |
+| `UtilitiesCS/OutlookObjects/Table/OlTableExtensions.TableAccess.cs` | 430 | 430 |
+| `UtilitiesCS/ReusableTypeClasses/Other/StackGeek.cs` | 199 | 199 |
+| `UtilitiesCS.Test/Extensions/DictionaryExtensions_Tests.cs` | 296 | 296 |
+| `UtilitiesCS.Test/Extensions/DfDeedle_COM_Tests.cs` | 869 | 869 |
+| `UtilitiesCS.Test/Extensions/DfDeedleQfcColumnTimeoutTests.cs` | 500 | 500 |
+| `UtilitiesCS.Test/ReusableTypeClasses/StackGeek_Tests.cs` | 276 | 276 |
+| `UtilitiesCS.Test/HelperClasses/PrettyPrint_Tests.cs` | 408 | 408 |
+| `UtilitiesCS.Test/OutlookObjects/Filter DASL/DASLFilterParserTests.cs` | 125 | 125 |
+| `UtilitiesCS.Test/OutlookObjects/Table/OlTableExtensions_Tests.cs` | 1855 | 1855 |
+| `UtilitiesCS.Test/HelperClasses/NLogTraceWriter_Test.cs` | 119 | 119 |
+| `UtilitiesCS/Threading/TimeOutTask.cs` | 1011 | 1011 |
+
+## New files absent (3 of 3 match)
+
+| Path | Expected | Observed |
+|---|---|---|
+| `UtilitiesCS.Test/Extensions/DfDeedleEtlTimeoutTests.cs` | ABSENT | ABSENT |
+| `UtilitiesCS.Test/OutlookObjects/Table/OlTableExtensionsEtlClockTests.cs` | ABSENT | ABSENT |
+| `UtilitiesCS.Test/TestHelpers/ArmingBarrierTimeProvider.cs` | ABSENT | ABSENT |
+
+## Token counts (42 of 42 match)
+
+All counts are case-sensitive `-SimpleMatch` line counts, per C2.
+
+| File | Token | Expected | Observed |
+|---|---|---|---|
+| `DictionaryExtensions.cs` | `CancelAfter(` | 1 | 1 |
+| `DictionaryExtensions.cs` | `CreateLinkedTokenSource` | 1 | 1 |
+| `OlTableExtensions.Etl.cs` | `var attempts = 3;` | 2 | 2 |
+| `OlTableExtensions.Etl.cs` | `TimeoutAfter(milliseconds, attempts)` | 2 | 2 |
+| `OlTableExtensions.Etl.cs` | `TimeoutAfter(timeout, attempts)` | 2 | 2 |
+| `OlTableExtensions.Etl.cs` | `timed out {attempts} times` | 2 | 2 |
+| `OlTableExtensions.Etl.cs` | `DateTime.Now` | 2 | 2 |
+| `OlTableExtensions.Etl.cs` | `TimeProvider` | 0 | 0 |
+| `DfDeedle.cs` | `TableEtlInvoker` | 3 | 3 |
+| `DfDeedle.cs` | `TimeoutAfter(1000, 2)` | 2 | 2 |
+| `DfDeedle.cs` | `TimeProvider` | 0 | 0 |
+| `DfDeedle.cs` | `was not produced` | 0 | 0 |
+| `DfDeedle.FrameUtilities.cs` | `StoreTableEtlInvoker` | 1 | 1 |
+| `DASLFilterParser.cs` | `Console.WriteLine` | 1 | 1 |
+| `DASLFilterParser.cs` | `TextWriter` | 0 | 0 |
+| `PrettyPrint.cs` | `Console.WriteLine` | 2 | 2 |
+| `PrettyPrint.cs` | `TextWriter` | 0 | 0 |
+| `OlTableExtensions.TableAccess.cs` | `Console.WriteLine` | 5 | 5 |
+| `OlTableExtensions.TableAccess.cs` | `TextWriter` | 0 | 0 |
+| `StackGeek.cs` | `Console.WriteLine` | 7 | 7 |
+| `StackGeek.cs` | `public static void Run(` | 0 | 0 |
+| `DfDeedle_COM_Tests.cs` | `TableEtlInvoker` | 14 | 14 |
+| `DfDeedle_COM_Tests.cs` | `FakeTimeProvider` | 0 | 0 |
+| `DfDeedleQfcColumnTimeoutTests.cs` | `class ArmingBarrierTimeProvider` | 1 | 1 |
+| `StackGeek_Tests.cs` | `Console.SetOut` | 3 | 3 |
+| `StackGeek_Tests.cs` | `DoNotParallelize` | 1 | 1 |
+| `PrettyPrint_Tests.cs` | `Console.SetOut` | 3 | 3 |
+| `PrettyPrint_Tests.cs` | `DoNotParallelize` | 2 | 2 |
+| `DASLFilterParserTests.cs` | `Console.SetOut` | 3 | 3 |
+| `DASLFilterParserTests.cs` | `DoNotParallelize` | 1 | 1 |
+| `OlTableExtensions_Tests.cs` | `Console.SetOut` | 2 | 2 |
+| `OlTableExtensions_Tests.cs` | `DoNotParallelize` | 1 | 1 |
+| `OlTableExtensions_Tests.cs` | `Returns(120)` | 1 | 1 |
+| `OlTableExtensions_Tests.cs` | `cannot fire under test-host` | 1 | 1 |
+| `OlTableExtensions_Tests.cs` | `redirects Console.Out` | 1 | 1 |
+| `OlTableExtensions_Tests.cs` | `FakeTimeProvider` | 0 | 0 |
+| `NLogTraceWriter_Test.cs` | `Console.SetOut` | 2 | 2 |
+| `NLogTraceWriter_Test.cs` | `originalOut` | 3 | 3 |
+| `NLogTraceWriter_Test.cs` | `TestCleanup` | 2 | 2 |
+| `UtilitiesCS.Test.csproj` | `Extensions\DfDeedleEtlTimeoutTests.cs` | 0 | 0 |
+| `UtilitiesCS.Test.csproj` | `OutlookObjects\Table\OlTableExtensionsEtlClockTests.cs` | 0 | 0 |
+| `UtilitiesCS.Test.csproj` | `TestHelpers\ArmingBarrierTimeProvider.cs` | 0 | 0 |
+
+## Output Summary
+
+62 assertions evaluated (17 line counts, 3 absence checks, 42 token counts). All 62 matched the
+plan's stated values exactly. `MISMATCH_COUNT: 0`. The re-anchor recorded in D16 did not move any
+value this task asserts: the merge changed only `UtilitiesCS.Test/UtilitiesCS.Test.csproj`, whose
+line count is not asserted here, and the three project-file token counts it does assert are all
+zero-hit checks for items the plan has yet to add.
diff --git a/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/baseline/p0-t7-csharpier-check.md b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/baseline/p0-t7-csharpier-check.md
new file mode 100644
index 000000000..4ce9bde5c
--- /dev/null
+++ b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/baseline/p0-t7-csharpier-check.md
@@ -0,0 +1,30 @@
+# P0-T7 — Baseline toolchain step 1 (CSharpier check)
+
+Timestamp: 2026-09-08T09-21
+Task: [P0-T7]
+Command: dotnet tool run csharpier check .
+EXIT_CODE: 0
+
+Invoked as `& $dotnet tool run csharpier check .` from the worktree root, where `$dotnet` is the
+repo-local `.dotnet-sdk\dotnet.exe` installed by P0-T3. Going through `dotnet tool run` binds the
+manifest-pinned CSharpier 1.2.6, matching `.github/workflows/_format-check.yml`.
+
+PRE-EXISTING DRIFT: none
+
+Exit code 0 and no path was reported as unformatted, so the tree carries no pre-existing formatting
+drift. Every rewrite the P7-T1 format pass produces will therefore be attributable to this change.
+
+## Output Summary
+
+Summary line, verbatim:
+
+```
+Checked 1613 files in 7197ms.
+```
+
+The line begins with `Checked ` and ends with `ms.`, as the acceptance condition requires. The
+processed-file count is 1613 on this tree, 26 higher than the 1587 recorded by #798 one day
+earlier; the difference is files added by the 31 origin/main commits merged as `bb1c7d4b`, not a
+change made by this item. The count is a processed-file count, not a rewrite count, so it is
+recorded for provenance and is not the pass signal; exit 0 with no reported path is the pass
+signal.
diff --git a/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/baseline/p0-t8-msbuild-analyzers.md b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/baseline/p0-t8-msbuild-analyzers.md
new file mode 100644
index 000000000..09ab3e221
--- /dev/null
+++ b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/baseline/p0-t8-msbuild-analyzers.md
@@ -0,0 +1,50 @@
+# P0-T8 — Baseline toolchain step 2 (msbuild analyzer gate)
+
+Timestamp: 2026-09-08T09-22
+Task: [P0-T8]
+Command: msbuild TaskMaster.sln /t:Rebuild /m /p:Configuration=Debug "/p:Platform=Any CPU" /p:EnableNETAnalyzers=true /p:EnforceCodeStyleInBuild=true /nologo /v:q "/flp:LogFile=coverage/msbuild-p0-t8.log;Verbosity=normal"
+EXIT_CODE: 0
+
+BASELINE_ANALYZER_WARNINGS: 0
+
+`/t:Rebuild` was used, not `/t:Build`. A warm `/t:Build` returns exit 0 with `CoreCompile` skipped
+on every project, because MSBuild's up-to-date check does not invalidate on a command-line `/p:`
+change, so the analyzer gate would run no analyzers and could not fail.
+
+The normal-verbosity file log is written to `coverage/msbuild-p0-t8.log`, which is gitignored and
+is not committed. `/v:q` on the console plus `Verbosity=normal` on the file logger is what makes
+the `0 Error(s)` summary line readable; at minimal verbosity MSBuild omits the error summary
+entirely.
+
+## Observations
+
+| Observation | Value |
+|---|---|
+| Build exit code | `0` |
+| Count of lines in the file log exactly equal to ` 0 Error(s)` | `1` |
+| Distinct analyzer or compiler warning ids in the log | none |
+| `Test-Path UtilitiesCS.Test\bin\Debug\UtilitiesCS.Test.dll` after the build | `True` |
+
+Summary lines quoted verbatim from the file log:
+
+```
+ 0 Warning(s)
+ 0 Error(s)
+```
+
+## Acceptance evaluation
+
+- `EXIT_CODE: 0`. PASS
+- The file log contains the line ` 0 Error(s)` (found once). The assertion is on that verbatim
+ summary line, not on the absence of the substring `error`, which a successful msbuild run prints
+ in switch names and summary text regardless. PASS
+- Both the `Warning(s)` and `Error(s)` lines are quoted verbatim, and
+ `BASELINE_ANALYZER_WARNINGS: 0` is recorded. PASS
+- `UtilitiesCS.Test\bin\Debug\UtilitiesCS.Test.dll` exists afterwards. PASS
+
+## Output Summary
+
+Full-solution rebuild with .NET analyzers and code-style enforcement enabled: exit 0, 0 warnings,
+0 errors. The baseline analyzer warning ceiling for P1-T8, P5-T10 and P7-T3 is therefore 0, which
+is a strict ceiling: any analyzer warning introduced by this change will exceed it and fail those
+gates.
diff --git a/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/baseline/p0-t9-msbuild-nullable.md b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/baseline/p0-t9-msbuild-nullable.md
new file mode 100644
index 000000000..5ec61b86e
--- /dev/null
+++ b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/baseline/p0-t9-msbuild-nullable.md
@@ -0,0 +1,44 @@
+# P0-T9 — Baseline toolchain step 3 (nullable / warnings-as-errors)
+
+Timestamp: 2026-09-08T09-23
+Task: [P0-T9]
+Command: msbuild TaskMaster.sln /t:Rebuild /m /p:Configuration=Debug "/p:Platform=Any CPU" /p:TreatWarningsAsErrors=true /nologo /v:q "/flp:LogFile=coverage/msbuild-p0-t9.log;Verbosity=normal"
+EXIT_CODE: 0
+
+## `/p:Nullable=enable` was not supplied, and why
+
+`/p:Nullable=enable` was deliberately omitted because nullable enforcement in this repository is
+per-file opt-in through the `#nullable enable` directive, and no project carries a ``
+element or a `Directory.Build.props`; supplying the property solution-wide would conscript every
+file that has never adopted the pragma, which is why `.github/workflows/_build-nullable.yml`
+omits it as well (CLAUDE.md C#1.3). Omitting it loses no enforcement over any file that has opted
+in, and all eight production files in this change carry `#nullable enable`.
+
+`/t:Rebuild` was used for the same reason as P0-T8: a warm `/t:Build` skips `CoreCompile` and the
+gate cannot fail.
+
+## Observations
+
+| Observation | Value |
+|---|---|
+| Build exit code | `0` |
+| Count of lines in the file log exactly equal to ` 0 Error(s)` | `1` |
+
+Summary lines quoted verbatim from `coverage/msbuild-p0-t9.log`:
+
+```
+ 0 Warning(s)
+ 0 Error(s)
+```
+
+## Acceptance evaluation
+
+- `EXIT_CODE: 0`. PASS
+- The file log contains ` 0 Error(s)`. PASS
+- The artifact states in one sentence that `/p:Nullable=enable` was not supplied and why. PASS
+
+## Output Summary
+
+Full-solution rebuild with `/p:TreatWarningsAsErrors=true`: exit 0, 0 warnings, 0 errors. The
+nullable baseline is clean, so any `CS86xx` diagnostic introduced by the Phase 1 or Phase 3 edits
+to the eight `#nullable enable` production files will surface as a build error at P7-T4.
diff --git a/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/baseline/phase0-instructions-read.md b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/baseline/phase0-instructions-read.md
new file mode 100644
index 000000000..ac75ad190
--- /dev/null
+++ b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/baseline/phase0-instructions-read.md
@@ -0,0 +1,47 @@
+# P0-T1 — Phase 0 policy read record
+
+Timestamp: 2026-09-08T09-16
+Task: [P0-T1]
+Command: Read tool over each listed path in the item worktree; `pwsh -NoProfile -File coverage/plan811-helper.ps1` for the Test-Path and line reads
+EXIT_CODE: 0
+
+Policy Order:
+
+1. `CLAUDE.md`
+2. `.claude/rules/general-code-change.md`
+3. `.claude/rules/general-unit-test.md`
+4. `.claude/rules/csharp.md`
+5. `.claude/rules/tonality.md`
+6. `.claude/rules/plan-acceptance-gates.md`
+7. `docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/issue.md`
+8. `docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/spec.md`
+9. `docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/research/root-cause.2026-09-07T22-10.md`
+
+All nine paths were read in that order and in full.
+
+Work Mode: full-bug
+
+Read from `issue.md` line 12, whose verbatim text is `- Work Mode: full-bug`.
+
+AC Source: spec.md lines 292-296 (AC1..AC5)
+
+`spec.md` line 291 is the heading `## Acceptance Criteria`; lines 292 through 296 carry
+`- [ ] AC1:` through `- [ ] AC5:`, all five unchecked at Phase 0.
+
+user-story.md: ABSENT (expected)
+
+`Test-Path` on `/user-story.md` returned `False`. `full-bug` work mode takes `spec.md`
+as its sole acceptance-criteria source, so the absence is the expected state and not a gap.
+
+## Output Summary
+
+Nine policy and requirement documents read in the mandated order. Work mode marker confirmed as
+`full-bug` at `issue.md` line 12. Five acceptance criteria confirmed present and unchecked at
+`spec.md` lines 292-296. `user-story.md` confirmed absent. Four of the six policy files
+(`CLAUDE.md`, `general-code-change.md`, `general-unit-test.md`, `tonality.md`) were additionally
+verified byte-identical to the copies already loaded in the executor session by comparing
+`git hash-object` blob hashes across the two worktrees; all four hashes matched.
+
+Note on `issue.md`: it carries an AC-shaped list under `## Proposed Fix / Validation Ideas`.
+Per the `acceptance-criteria-tracking` skill that list is NOT the authority for `full-bug`;
+`spec.md` is. Only `spec.md` is checked off by this plan.
diff --git a/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/issue-updates/issue-811.2026-09-08T10-44.md b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/issue-updates/issue-811.2026-09-08T10-44.md
new file mode 100644
index 000000000..e2bf6cec0
--- /dev/null
+++ b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/issue-updates/issue-811.2026-09-08T10-44.md
@@ -0,0 +1,92 @@
+# Issue update mirror — #811
+
+Timestamp: 2026-09-08T10-44
+POSTING BLOCKED: GitHub posting is gated to the orchestrator by repository hooks
+PostedAs: unknown
+
+## Deviation from the plan's predicted values
+
+P8-T10's acceptance predicted `Checked off (delivered): 5` and `Remaining (unchecked): 0`. The
+observed state is 4 delivered and 1 remaining, because AC4's ten-run gate recorded one failing run
+(see `evidence/regression-testing/p8-t6-ac4-ten-run.md`). The true figures are written below. This
+task is therefore left unchecked in the plan: its acceptance condition as written cannot be
+satisfied without misreporting the result, and the fail-closed evidence rule makes misreporting the
+worse outcome.
+
+---
+
+## Text intended for the issue
+
+### Acceptance Criteria Status
+
+- Source: `docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/spec.md`
+- Total AC items: 5
+- Checked off (delivered): 4
+- Remaining (unchecked): 1
+- Items remaining: AC4: A full nine-assembly `/InIsolation` run with `TestCategory!=LiveOutlook` reports zero failures on ten consecutive runs, recorded as evidence.
+
+| AC | State | Evidence |
+|---|---|---|
+| AC1 | delivered | `fail-before-exception.2026-09-08T09-50.md`, `p4-t4-ac1-pass-after.md`, `p8-t6-ac4-ten-run.md` |
+| AC2 | delivered | `p1-t9-seam-scoped-run.md`, `p2-t4-ac2-fail-before.md`, `p3-t2-ac2-pass-after.md` |
+| AC3 | delivered | `p5-t10-ac3-pass-after.md` |
+| AC4 | **not delivered** | `p8-t6-ac4-ten-run.md` — 9 of 10 runs clean |
+| AC5 | delivered | `p7-t10-ac5-timing-hack-search.md` |
+
+### The ten-run gate
+
+Ten consecutive full nine-assembly `/InIsolation` runs on the unchanged source commit `03b7bd57`,
+24 class-level workers, 7162 tests per run.
+
+| Run | total | executed | passed | failed | error | aborted | timeout | seconds |
+|---|---|---|---|---|---|---|---|---|
+| 1 | 7162 | 7162 | 7162 | 0 | 0 | 0 | 0 | 70.9 |
+| 2 | 7162 | 7162 | 7162 | 0 | 0 | 0 | 0 | 54.2 |
+| 3 | 7162 | 7162 | 7162 | 0 | 0 | 0 | 0 | 55.0 |
+| 4 | 7162 | 7162 | 7162 | 0 | 0 | 0 | 0 | 51.9 |
+| 5 | 7162 | 7162 | 7162 | 0 | 0 | 0 | 0 | 51.3 |
+| 6 | 7162 | 7162 | 7162 | 0 | 0 | 0 | 0 | 70.7 |
+| 7 | 7162 | 7162 | 7161 | 1 | 0 | 0 | 0 | 73.9 |
+| 8 | 7162 | 7162 | 7162 | 0 | 0 | 0 | 0 | 69.4 |
+| 9 | 7162 | 7162 | 7162 | 0 | 0 | 0 | 0 | 54.2 |
+| 10 | 7162 | 7162 | 7162 | 0 | 0 | 0 | 0 | 58.8 |
+
+The single failure, on run 7, was
+`UtilitiesCS.Test.NewtonsoftHelpers.SDILReader.MethodBodyReader_Tests.GetBodyCode_ReturnsConcatenatedInstructions`.
+It is a different defect from the three this item repairs: an unsynchronised process-wide static in
+`UtilitiesCS/NewtonsoftHelpers/SDIL Reader/ILGlobals.cs`, whose `LoadOpCodes()` reassigns
+`singleByteOpCodes` to a fresh all-default array before filling it, raced by two test classes that
+both call it and neither of which carries `[DoNotParallelize]`. Neither file is in this item's
+write set and neither was modified here. It needs its own issue.
+
+All 13 tests this change adds, renames or repairs read `Passed` in every one of the ten runs (130
+assertions, 0 not-passed), including both intermittent-failure sentinels
+`TryAddValuesAsync_UpdatesExistingValue` and
+`GetEmailDataInViewAsync_SeparatesTableSnapshotFromDataFrameTransform`. On this evidence the three
+defects #811 targets did not recur.
+
+The local filter extends `TestCategory!=LiveOutlook` with the documented shell-icon exclusion, an
+environmental workstation issue covered by CI. The CI run on the pull request supplies the
+unfiltered single-run form; one CI run is not ten.
+
+### Follow-ups recorded
+
+- `docs/features/potential/2026-09-08-etl-deadline-mechanics-follow-ups.md`
+- `docs/features/potential/2026-09-08-console-out-aggressors-and-banned-symbol-promotion.md`
+
+A third follow-up is needed for the `ILGlobals` static race described above; it is reported to the
+orchestrator rather than filed here, because authoring it was not in this plan's write set.
+
+### Closure pointers
+
+#780, #803 and #594 are superseded by #811.
+
+---
+
+## Acceptance evaluation
+
+- The file exists at the canonical path. PASS
+- Contains `Total AC items: 5`. PASS
+- Contains `Checked off (delivered): 5`. **FAIL** — the observed value is 4 and the artifact records
+ the observed value rather than the predicted one.
+- Contains no host token. PASS (re-verified by P8-T12)
diff --git a/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/other/p4-t5-ac1-checkoff-note.md b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/other/p4-t5-ac1-checkoff-note.md
new file mode 100644
index 000000000..e89f45c91
--- /dev/null
+++ b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/other/p4-t5-ac1-checkoff-note.md
@@ -0,0 +1,71 @@
+# P4-T5 — AC1 check-off note and its outstanding dependency
+
+Timestamp: 2026-09-08T09-52
+Task: [P4-T5]
+Command: Edit of `/spec.md` line 292, changing `- [ ] AC1:` to `- [x] AC1:` with the criterion text unchanged
+EXIT_CODE: 0
+
+## Criterion
+
+AC1: `TryAddValuesAsync` no longer cancels on a fixed wall-clock window, or the window is driven by
+an injected `TimeProvider`; the test passes deterministically under 24-worker parallel coverage
+runs.
+
+## Evidence cited for the check-off
+
+| Clause | Evidence |
+|---|---|
+| "no longer cancels on a fixed wall-clock window" (first disjunct) | `evidence/regression-testing/fail-before-exception.2026-09-08T09-50.md` — `CancelAfter(` and `CreateLinkedTokenSource` both moved from 1 to 0 in `UtilitiesCS/Extensions/DictionaryExtensions.cs`, with `TryAddValues(key, value), token)` moving from 0 to 1 as the positive control |
+| cancellation contract preserved | `evidence/regression-testing/p4-t4-ac1-pass-after.md` — `TryAddValuesAsync_PreCancelledToken_ThrowsTaskCanceledAndLeavesValueUnchanged` passes |
+| "the test passes" | `evidence/regression-testing/p4-t4-ac1-pass-after.md` — `TryAddValuesAsync_UpdatesExistingValue` passes in 2.76 ms |
+
+AC1 is satisfied by its first disjunct. The `TimeProvider` alternative was not taken, because the
+deleted window had no production consumer and guarded a bounded compare-and-swap loop, so a seam
+would have preserved a deadline with no semantic (decision D3).
+
+## Outstanding dependency at the time of this check-off
+
+The final clause of AC1, "the test passes deterministically under 24-worker parallel coverage
+runs", is **not** discharged by any evidence available at P4-T5. A single scoped run of one test
+class proves the test passes; it does not prove it passes under parallel load, which is the exact
+condition the #780 failure depends on.
+
+That clause is evidenced by **P8-T6**, the ten-run AC4 gate: ten consecutive full nine-assembly
+`/InIsolation` runs with `Workers = 0` resolving to the local processor count, in which
+`TryAddValuesAsync_UpdatesExistingValue` must pass every time. The check-off is recorded here
+together with that dependency so the dependency is auditable rather than implicit.
+
+## Resolution of that dependency (added 2026-09-08T10-40, after P8-T6 ran)
+
+DEPENDENCY: DISCHARGED. The AC1 check-off stands.
+
+P8-T6 ran ten consecutive full-suite runs on the unchanged source commit `03b7bd57` at 24 workers.
+`TryAddValuesAsync_UpdatesExistingValue` read `Passed` in **all ten**, which is precisely the
+clause this note was tracking: "the test passes deterministically under 24-worker parallel coverage
+runs".
+
+Nine of the ten runs were completely clean. Run 7 reported one failure, but it was a different
+test in a different assembly area,
+`MethodBodyReader_Tests.GetBodyCode_ReturnsConcatenatedInstructions`, caused by an unsynchronised
+static in `ILGlobals` that lies outside this item's write set (see `p8-t4-ac4-runs.md`). That
+failure defeats **AC4**, whose condition is a property of the whole run rather than of one test,
+and AC4 is correspondingly left unchecked. It does not bear on AC1, whose condition names this
+one test, and that test did not fail once in ten runs.
+
+This supersedes the earlier conservative wording in this note, which said the check-off must be
+reverted if P8-T6 did not produce ten clean runs. That wording conflated the two criteria: AC1's
+clause is about the #780 test specifically, and AC4's is about the run as a whole. The precise
+condition for AC1 was met.
+
+The reconciliation task P8-T11 lists `fail-before-exception`, `p4-t4-ac1-pass-after.md` and
+`p8-t6-ac4-ten-run.md` as the three artifacts that jointly discharge AC1.
+
+## Acceptance evaluation
+
+- `'- [x] AC1:'` counts 1 and `'- [ ] AC1:'` counts 0 in spec.md. Verified in the same task run.
+- This note artifact exists and names P8-T6 as the evidence for the parallel-load clause. PASS
+
+## Output Summary
+
+AC1 checked off in spec.md on the strength of the structural deletion proof and the P4-T4 pass-after
+run. The parallel-determinism clause remains dependent on P8-T6 and is recorded as such.
diff --git a/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/other/p6-t2-reflection-binding-check.md b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/other/p6-t2-reflection-binding-check.md
new file mode 100644
index 000000000..b5113b9e3
--- /dev/null
+++ b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/other/p6-t2-reflection-binding-check.md
@@ -0,0 +1,95 @@
+# P6-T2 — Reflection binding re-verification
+
+Timestamp: 2026-09-08T10-01
+Task: [P6-T2]
+Command: Grep over `UtilitiesCS.Test/OutlookObjects/Table/OlTableExtensions_Tests.cs` for `EtlByRowAsync` and over `UtilitiesCS/OutlookObjects/Table/OlTableExtensions.Etl.cs` for the same, after all Phase 1, 5 and 6 edits
+EXIT_CODE: 0
+
+REFLECTION_BINDING: UNCHANGED
+
+A reflection binding is invisible to the compiler: a signature change that breaks it produces no
+build error and instead fails at run time with a null `MethodInfo`. This task re-checks it after
+every edit rather than relying on the P5-T10 build.
+
+## The reflection call
+
+`UtilitiesCS.Test/OutlookObjects/Table/OlTableExtensions_Tests.cs`, in
+`EtlByRowAsync_PrivateHelper_ReturnsConvertedRows` (test method opens at line 1098; the call spans
+lines 1122-1135, originally cited as 1124-1137 and now two lines earlier because P6-T1 removed the
+four `Returns(120)` tolerance lines above it):
+
+```csharp
+var asyncRows = await InvokeStaticAsync>(
+ "EtlByRowAsync",
+ new[]
+ {
+ typeof(Outlook.Table),
+ typeof(Dictionary>),
+ typeof(Dictionary),
+ typeof(CancellationToken),
+ },
+ mockTable.Object,
+ converters,
+ columnDictionary,
+ CancellationToken.None
+);
+```
+
+The explicit four-type array selects the four-parameter overload by exact signature match.
+
+## The four-parameter overload it binds to (unchanged)
+
+`UtilitiesCS/OutlookObjects/Table/OlTableExtensions.Etl.cs:173-178`:
+
+```csharp
+private static async Task> EtlByRowAsync(
+ Table table,
+ Dictionary>? objectConverters,
+ Dictionary columnDictionary,
+ CancellationToken token
+)
+```
+
+Its parameter list is `Table table, Dictionary>? objectConverters,
+Dictionary columnDictionary, CancellationToken token` — exactly the four types the
+reflection array names, in the same order. P1-T1 did not edit this overload (decision D9, D14).
+
+## The seven-parameter overload that was edited
+
+`UtilitiesCS/OutlookObjects/Table/OlTableExtensions.Etl.cs:231-239`:
+
+```csharp
+private static async Task EtlByRowAsync(
+ Table table,
+ Dictionary>? objectConverters,
+ Dictionary columnDictionary,
+ CancellationToken token,
+ int timeout,
+ TimeProvider? timeProvider,
+ ProgressTracker? progress = null
+)
+```
+
+Its list ends `TimeProvider? timeProvider, ProgressTracker? progress = null`, which is exactly what
+the acceptance condition requires. P1-T1 replaced `int attempts` with `TimeProvider? timeProvider`
+in this overload only.
+
+A third overload also carries the name — the public extension
+`EtlByRowAsync(this IAsyncEnumerable, ...)` at line 218 — but it takes five parameters of
+different types and cannot be selected by the four-type array.
+
+## Acceptance evaluation
+
+- The four-parameter overload's parameter list is unchanged and matches the reflection array
+ type-for-type and in order. PASS
+- The seven-parameter overload's list ends `TimeProvider? timeProvider, ProgressTracker? progress = null`.
+ PASS
+- `REFLECTION_BINDING: UNCHANGED` recorded. PASS
+
+## Output Summary
+
+The reflection-bound four-parameter `EtlByRowAsync` overload is untouched by this change, so the
+binding in `EtlByRowAsync_PrivateHelper_ReturnsConvertedRows` still resolves. The `TimeProvider`
+parameter was added only to the seven-parameter overload, which no test binds by reflection. The
+P6-T5 run confirms the test passes at run time, which is the behavioural counterpart to this
+static check.
diff --git a/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/other/p7-t12-source-commit.md b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/other/p7-t12-source-commit.md
new file mode 100644
index 000000000..a76cf446b
--- /dev/null
+++ b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/other/p7-t12-source-commit.md
@@ -0,0 +1,87 @@
+# P7-T12 — Source commit
+
+Timestamp: 2026-09-08T10-23
+Task: [P7-T12]
+Command: `git add -A -- . ":(exclude).claude"`, then `git commit -m "fix(811): make UtilitiesCS.Test deterministic under parallel coverage (AC1-AC3, AC5)"` with a body listing the three defects
+EXIT_CODE: 0
+
+SOURCE-HEAD: 03b7bd57cacfc902a8b9f4e917ace627ffcac464
+
+## Commit contents
+
+55 files changed, 3092 insertions, 343 deletions. Three files created:
+
+```
+UtilitiesCS.Test/Extensions/DfDeedleEtlTimeoutTests.cs
+UtilitiesCS.Test/OutlookObjects/Table/OlTableExtensionsEtlClockTests.cs
+UtilitiesCS.Test/TestHelpers/ArmingBarrierTimeProvider.cs
+```
+
+The remaining new files are the 32 evidence artifacts written by Phases 0 through 7. The
+`:(exclude).claude` pathspec kept the tracked dot-claude agent-memory tree out of the commit.
+
+## Committed source diff — exactly the 20 Write Set paths
+
+`git diff --name-only bb1c7d4b60f7b782227956f36859314d5c47bb03..HEAD -- "*.cs" "*.csproj"`
+
+```
+UtilitiesCS.Test/Extensions/DfDeedleEtlTimeoutTests.cs
+UtilitiesCS.Test/Extensions/DfDeedleQfcColumnTimeoutTests.cs
+UtilitiesCS.Test/Extensions/DfDeedle_COM_Tests.cs
+UtilitiesCS.Test/Extensions/DictionaryExtensions_Tests.cs
+UtilitiesCS.Test/HelperClasses/NLogTraceWriter_Test.cs
+UtilitiesCS.Test/HelperClasses/PrettyPrint_Tests.cs
+UtilitiesCS.Test/OutlookObjects/Filter DASL/DASLFilterParserTests.cs
+UtilitiesCS.Test/OutlookObjects/Table/OlTableExtensionsEtlClockTests.cs
+UtilitiesCS.Test/OutlookObjects/Table/OlTableExtensions_Tests.cs
+UtilitiesCS.Test/ReusableTypeClasses/StackGeek_Tests.cs
+UtilitiesCS.Test/TestHelpers/ArmingBarrierTimeProvider.cs
+UtilitiesCS.Test/UtilitiesCS.Test.csproj
+UtilitiesCS/Extensions/DfDeedle.FrameUtilities.cs
+UtilitiesCS/Extensions/DfDeedle.cs
+UtilitiesCS/Extensions/DictionaryExtensions.cs
+UtilitiesCS/HelperClasses/PrettyPrint.cs
+UtilitiesCS/OutlookObjects/Filter DASL/DASLFilterParser.cs
+UtilitiesCS/OutlookObjects/Table/OlTableExtensions.Etl.cs
+UtilitiesCS/OutlookObjects/Table/OlTableExtensions.TableAccess.cs
+UtilitiesCS/ReusableTypeClasses/Other/StackGeek.cs
+```
+
+20 paths, equal to the Write Set. This is now a two-dot range against the committed history rather
+than a worktree comparison, so it cannot pass vacuously.
+
+## Tree state after the commit
+
+| Observation | Value |
+|---|---|
+| `git status --porcelain -- "*.cs" "*.csproj"` | empty |
+| `git status --porcelain --untracked-files=all -- . ":(exclude).claude"` immediately after the commit | empty |
+| The same command after this artifact was written | lists exactly one path, this artifact |
+
+This artifact is written after the commit, so it is deliberately the single untracked path until
+P8-T12 commits it. Nothing else is written between the commit and that point except the Phase 8
+artifacts, which P8-T12 also commits.
+
+## Host-token scan of the commit message
+
+| Token class | Hits |
+|---|---|
+| Account name (derived at run time, never written here) | 0 |
+| Machine name (derived at run time, never written here) | 0 |
+| Drive-rooted profile-path shape | 0 |
+
+## Acceptance evaluation
+
+- Commit exit 0. PASS
+- `git status --porcelain --untracked-files=all -- . ":(exclude).claude"` lists exactly one path,
+ this task's artifact. PASS
+- `git status --porcelain -- "*.cs" "*.csproj"` is empty. PASS
+- `git diff --name-only $Base..HEAD -- "*.cs" "*.csproj"` lists exactly the 20 Write Set paths.
+ PASS
+- The commit message contains no absolute path, account name or machine name. PASS
+
+## Output Summary
+
+Source commit `03b7bd57cacfc902a8b9f4e917ace627ffcac464` created on
+`bug/utilitiescs-test-determinism-780-803-594-811`. The committed source diff against the anchor
+is exactly the 20 declared paths. Working tree is clean apart from this artifact.
diff --git a/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/other/p8-t11-ac-reconciliation.md b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/other/p8-t11-ac-reconciliation.md
new file mode 100644
index 000000000..150d5994f
--- /dev/null
+++ b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/other/p8-t11-ac-reconciliation.md
@@ -0,0 +1,71 @@
+# P8-T11 — Final acceptance-criteria reconciliation
+
+Timestamp: 2026-09-08T10-44
+Task: [P8-T11]
+Command: `Select-String` over `/spec.md` lines 292-296 for `- [x] AC` and `- [ ] AC`
+EXIT_CODE: 0
+
+## Observed state of spec.md lines 292-296
+
+| Line | Criterion | State |
+|---|---|---|
+| 292 | AC1 | `- [x]` |
+| 293 | AC2 | `- [x]` |
+| 294 | AC3 | `- [x]` |
+| 295 | AC4 | `- [ ]` |
+| 296 | AC5 | `- [x]` |
+
+`Select-String -SimpleMatch '- [x] AC'` counts **4**; `'- [ ] AC'` counts **1**.
+
+The task's acceptance requires 5 and 0. It is therefore not met, and this task is left unchecked.
+The counts above are the observed values; the fail-closed evidence rule makes recording the true
+state the required outcome, and checklist state must not contradict evidence on disk.
+
+## Evidence that discharged each criterion
+
+| AC | Discharged by | Verdict |
+|---|---|---|
+| AC1 | `evidence/regression-testing/fail-before-exception.2026-09-08T09-50.md` (structural proof: `CancelAfter(` and `CreateLinkedTokenSource` both 1 to 0, positive control 0 to 1), `evidence/regression-testing/p4-t4-ac1-pass-after.md` (both dictionary tests pass; the #780 test runs in 2.76 ms), `evidence/regression-testing/p8-t6-ac4-ten-run.md` (`TryAddValuesAsync_UpdatesExistingValue` passes in all ten 24-worker runs) | DELIVERED |
+| AC2 | `evidence/regression-testing/p1-t9-seam-scoped-run.md` (static seams replaced; `TableEtlInvoker` count 0 in both the production and the test file), `evidence/regression-testing/p2-t4-ac2-fail-before.md` (RED: `NullReferenceException` where `InvalidOperationException` was asserted), `evidence/regression-testing/p3-t2-ac2-pass-after.md` (GREEN after the guard, same test unchanged) | DELIVERED |
+| AC3 | `evidence/regression-testing/p5-t10-ac3-pass-after.md` (six named tests pass; `Console.SetOut` totals 0 across the five converted test files; solution build clean) | DELIVERED |
+| AC4 | `evidence/regression-testing/p8-t6-ac4-ten-run.md` (9 of 10 runs clean; run 7 failed one test) | **NOT DELIVERED** |
+| AC5 | `evidence/qa-gates/p7-t10-ac5-timing-hack-search.md` (all eleven patterns count 0 over 737 added lines; the pre-existing `Returns(120)` tolerance retired) | DELIVERED |
+
+## Why AC4 is not delivered
+
+AC4 reads: "A full nine-assembly `/InIsolation` run with `TestCategory!=LiveOutlook` reports zero
+failures on ten consecutive runs, recorded as evidence."
+
+Ten consecutive runs were performed and recorded. Nine reported zero failures. Run 7 reported one:
+
+```
+UtilitiesCS.Test.NewtonsoftHelpers.SDILReader.MethodBodyReader_Tests.GetBodyCode_ReturnsConcatenatedInstructions
+```
+
+The criterion is a property of the whole run, not of the tests this item touches, so a single
+failure anywhere defeats it regardless of cause. It is left unchecked.
+
+The cause is an unsynchronised process-wide static in
+`UtilitiesCS/NewtonsoftHelpers/SDIL Reader/ILGlobals.cs` raced by `ILGlobals_Tests` and
+`MethodBodyReader_Tests`, neither of which carries `[DoNotParallelize]` and neither of which is in
+this item's 20-path write set. Full derivation in `evidence/regression-testing/p8-t4-ac4-runs.md`.
+It is a pre-existing latent defect in the same family as #811 but a distinct instance, and it needs
+its own issue. Fixing it here would require editing files outside the declared write set.
+
+The distinction against AC1 matters and is recorded in
+`evidence/other/p4-t5-ac1-checkoff-note.md`: AC1's parallel-determinism clause names one test,
+which passed ten times out of ten, whereas AC4's clause covers every test in the run.
+
+### Acceptance Criteria Status
+
+- Source: `docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/spec.md`
+- Total AC items: 5
+- Checked off (delivered): 4
+- Remaining (unchecked): 1
+- Items remaining: AC4: A full nine-assembly `/InIsolation` run with `TestCategory!=LiveOutlook` reports zero failures on ten consecutive runs, recorded as evidence.
+
+## Output Summary
+
+Four of the five acceptance criteria are delivered and checked off in spec.md with named evidence.
+AC4 is not, because one of the ten required runs failed on a defect outside this item's write set.
+The three defects #811 targets did not recur in any of the ten runs.
diff --git a/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/other/p8-t12-final-commit.md b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/other/p8-t12-final-commit.md
new file mode 100644
index 000000000..916d0271c
--- /dev/null
+++ b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/other/p8-t12-final-commit.md
@@ -0,0 +1,83 @@
+# P8-T12 — Final commit and clean-tree gate
+
+Timestamp: 2026-09-08T10-45
+Task: [P8-T12]
+Command: the P8-T8 four counts re-run over `` and `docs/features/potential/`, then `git add -A -- . ":(exclude).claude"`, then `git commit -m "docs(811): AC4 ten-run evidence, sanitisation, follow-ups, AC reconciliation"`
+EXIT_CODE: 0
+
+## Sanitisation re-count
+
+The plan states the expected result as 0, 0, 0, 0. That holds for everything this item wrote. It
+does not hold for the whole `docs/features/potential/` tree, because that tree contains 194 files,
+almost all of them pre-existing and unrelated to this item. Both scopes are recorded.
+
+### Scope A — this item's own written set
+
+`` (all 47 files) plus the two potential entries P8-T9 created.
+
+| Check | Count |
+|---|---|
+| Files scanned | 49 |
+| Account-token content matches | 0 |
+| Machine-token content matches | 0 |
+| Drive-rooted profile-path matches | 0 |
+| Offending file or directory names | 0 |
+
+**0, 0, 0, 0**, as the plan expects.
+
+### Scope B — the whole `docs/features/potential/` tree
+
+| Check | Count |
+|---|---|
+| Files scanned | 194 |
+| Account-token content matches | 9 |
+| Machine-token content matches | 0 |
+| Drive-rooted profile-path matches | 16 |
+
+All 25 hits lie in ten files, every one of them under `docs/features/potential/promoted/` and every
+one of them present at the base commit `bb1c7d4b` (verified individually with `git cat-file -e`;
+`EXISTS_AT_BASE=True` for all ten):
+
+```
+promoted/2026-08-10-mstest-coverage-discovery-claude-worktree-exclusion.md
+promoted/2026-08-11-research-doc-cohort-library-false-negative.md
+promoted/2026-08-14-orchestrator-hooks-reference-absent-python-validators.md
+promoted/2026-08-14-potential-to-issue-promoted-copy-not-written.md
+promoted/2026-08-26-efc-store-root-selection-leaks-full-outlook-path-into-filing-boundary.md
+promoted/2026-08-26-matchbestspecialfolder-substring-matching-codified-by-tests.md
+promoted/2026-08-26-taskmaster-csproj-publishurl-leaks-user-profile-path-and-org-name.md
+promoted/2026-08-28-trx-evidence-host-tokens-and-malformed-xml.md
+promoted/2026-08-29-parallel-run-merge-gate-misparses-pr-number.md
+promoted/2026-09-02-committed-host-identity-leaks.md
+```
+
+These are pre-existing committed host-identity leaks in other items' promoted entries. Several of
+them are documents *about* host-identity leakage, which is why they quote the offending strings.
+This item does not modify any of them, and rewriting ten unrelated committed documents would be a
+scope escape well outside the 20-path write set. They are left untouched and reported instead. Note
+that `docs/features/potential/promoted/2026-09-02-committed-host-identity-leaks.md` already tracks
+this class of problem.
+
+No file was rewritten by this task.
+
+## Intended commit message
+
+```
+docs(811): AC4 ten-run evidence, sanitisation, follow-ups, AC reconciliation
+```
+
+## Acceptance evaluation
+
+Recorded before the commit; the post-commit observations are reported in the executor's completion
+message rather than written here, so that nothing is written after the commit.
+
+- The four counts are re-run over `$Feature` and `docs/features/potential/` and recorded above,
+ with the scope split made explicit. Scope A is 0, 0, 0, 0 as expected; scope B is non-zero
+ solely through pre-existing files this change does not touch.
+- The intended commit message is recorded.
+
+## Output Summary
+
+Everything this item wrote is free of account tokens, machine tokens and drive-rooted profile
+paths. The 25 hits in the wider potential tree are pre-existing, all in `promoted/`, all present at
+the base commit, and all outside this item's write set.
diff --git a/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/other/remediation-cycle-1-r1-closure.md b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/other/remediation-cycle-1-r1-closure.md
new file mode 100644
index 000000000..437391b29
--- /dev/null
+++ b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/other/remediation-cycle-1-r1-closure.md
@@ -0,0 +1,39 @@
+# Remediation Cycle 1 — R-1 Closure Evidence
+
+- Timestamp: 2026-09-08T00:00
+- Command: `git -C diff --name-only bb1c7d4b60f7b782227956f36859314d5c47bb03..HEAD -- "UtilitiesCS/**" "UtilitiesCS.Test/**"`
+- EXIT_CODE: 0
+- Output Summary: New potential-entry file authored; no source-file modification introduced by this
+ cycle; GitHub issue creation deferred to the operator.
+
+## New File Authored
+
+- Path: `docs/features/potential/2026-09-08-ilglobals-loadopcodes-unsynchronised-static-race.md`
+- Line count: 111 lines.
+
+## Source-File Isolation Confirmation
+
+`git -C diff --name-only bb1c7d4b60f7b782227956f36859314d5c47bb03..HEAD -- "UtilitiesCS/**" "UtilitiesCS.Test/**"`
+returned the same 20 pre-existing source paths that #811 already modified prior to this
+remediation cycle (11 under `UtilitiesCS.Test/`, 9 under `UtilitiesCS/`). No path was added to or
+removed from that list by this cycle, confirming this cycle introduced no source-file
+modification under `UtilitiesCS/` or `UtilitiesCS.Test/`.
+
+`git -C status --porcelain --untracked-files=all` immediately before staging showed
+exactly five untracked paths, all documentation/evidence artifacts belonging to this cycle:
+- `docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/code-review.2026-09-08T11-30.md`
+- `docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/feature-audit.2026-09-08T11-30.md`
+- `docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/policy-audit.2026-09-08T11-30.md`
+- `docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/remediation-inputs.2026-09-08T11-30.md`
+- `docs/features/potential/2026-09-08-ilglobals-loadopcodes-unsynchronised-static-race.md`
+
+No entry under `UtilitiesCS/` or `UtilitiesCS.Test/` appears in that status output, corroborating
+the diff-based confirmation above.
+
+## GitHub Issue Creation — Deferred
+
+GitHub issue creation and promotion-tool execution were intentionally not performed in this cycle.
+Reason: R-1's directive withholds issue creation as the operator's action; this remediation cycle's
+scope is limited to authoring the potential-entry documentation file. The `## Next Step` promotion
+checkbox in the new entry is left unchecked (`- [ ] Promote to GitHub issue (bug-report template)`)
+to reflect that this half of R-1's acceptance criteria remains outstanding pending operator action.
diff --git a/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/qa-gates/p1-t8-seam-build.md b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/qa-gates/p1-t8-seam-build.md
new file mode 100644
index 000000000..8d9677fe9
--- /dev/null
+++ b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/qa-gates/p1-t8-seam-build.md
@@ -0,0 +1,55 @@
+# P1-T8 — Seam build with the analyzer gate
+
+Timestamp: 2026-09-08T09-37
+Task: [P1-T8]
+Command: msbuild TaskMaster.sln /t:Rebuild /m /p:Configuration=Debug "/p:Platform=Any CPU" /p:EnableNETAnalyzers=true /p:EnforceCodeStyleInBuild=true /nologo /v:q "/flp:LogFile=coverage/msbuild-p1-t8.log;Verbosity=normal"
+EXIT_CODE: 0
+
+## Observations
+
+| Observation | Value |
+|---|---|
+| Build exit code | `0` |
+| Count of lines in the file log exactly equal to ` 0 Error(s)` | `1` |
+| Analyzer / compiler warning count | `0` |
+| `BASELINE_ANALYZER_WARNINGS` from P0-T8 | `0` |
+| Warning count does not exceed the baseline | yes (0 is not greater than 0) |
+
+Summary lines quoted verbatim from `coverage/msbuild-p1-t8.log`:
+
+```
+ 0 Warning(s)
+ 0 Error(s)
+```
+
+A scan of the file log for lines matching the diagnostic shape
+`: (warning|error) ` returned no lines, so there is no warning to enumerate by diagnostic id.
+
+## What this proves
+
+The build succeeded across the whole solution after Phase 1 replaced two public static properties
+with optional parameters and added optional `TimeProvider?` parameters to two public members. The
+in-solution callers named in spec.md therefore still compile unchanged against the new signatures:
+
+- `QuickFiler/Controllers/QfcDatamodel.FrameBuilding.cs:15,82-89` — calls
+ `GetEmailDataInViewAsync` with four positional arguments and reads the frame builder; the new
+ trailing `TimeProvider? timeProvider = null` is source-compatible.
+- `ToDoModel/Data Model/ID/IDList.cs:130,226` — calls `FromDefaultFolder(Store, ...)`; the new
+ trailing optional delegate parameter is source-compatible.
+
+The two deleted static properties `DfDeedle.TableEtlInvoker` and `DfDeedle.StoreTableEtlInvoker`
+are a public-surface removal. A surviving reader anywhere in the solution would have produced a
+CS0117, and the build reports 0 errors, so every reader and writer of both properties was updated
+in this same change.
+
+## Acceptance evaluation
+
+- `EXIT_CODE: 0`. PASS
+- The log contains ` 0 Error(s)`. PASS
+- Warning count recorded (0) and does not exceed `BASELINE_ANALYZER_WARNINGS` (0). PASS
+
+## Output Summary
+
+Full-solution rebuild after the Phase 1 seam work: exit 0, 0 warnings, 0 errors, no analyzer
+diagnostics introduced. Production behaviour is unchanged because every new parameter defaults to
+null, which resolves to the system clock and to the production ETL delegate respectively.
diff --git a/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/qa-gates/p7-t1-csharpier-format.md b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/qa-gates/p7-t1-csharpier-format.md
new file mode 100644
index 000000000..700b33046
--- /dev/null
+++ b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/qa-gates/p7-t1-csharpier-format.md
@@ -0,0 +1,89 @@
+# P7-T1 — Toolchain step 1 (CSharpier format)
+
+Timestamp: 2026-09-08T10-05
+Task: [P7-T1]
+Command: dotnet tool run csharpier format .
+EXIT_CODE: 0
+Toolchain pass: 3
+
+REWRITTEN_COUNT: 0
+
+## Why the exit code is not the signal
+
+`csharpier format` exits 0 whether or not it rewrote anything, and its summary line
+`Formatted 1616 files in ms.` is a processed-file count, not a rewrite count: it printed 1616
+on both passes below, including the pass that rewrote five files and the pass that rewrote none.
+The rewrite observation is therefore taken from the tree, as two independent measurements:
+
+1. SHA-256 hashes of the 19 write-set `.cs` files, taken immediately before and immediately after
+ the command. `REWRITTEN_COUNT` counts hash differences.
+2. `git status --porcelain --untracked-files=all -- "*.cs" ":(exclude).claude"` before and after.
+ Any porcelain entry that appears is added to the count. This second observation exists because
+ an already-modified file stays marked `M` when reformatted, so porcelain alone cannot see a
+ rewrite of a file that was already dirty; the hash comparison is what detects that case.
+
+## Pass 1 (2026-09-08T10-05) — five files rewritten, loop restarted
+
+`REWRITTEN_COUNT: 5`, all detected by hash difference; the porcelain count was 19 before and 19
+after, so porcelain alone would have reported nothing. The five:
+
+```
+UtilitiesCS/HelperClasses/PrettyPrint.cs
+UtilitiesCS.Test/Extensions/DfDeedleEtlTimeoutTests.cs
+UtilitiesCS.Test/OutlookObjects/Table/OlTableExtensionsEtlClockTests.cs
+UtilitiesCS.Test/TestHelpers/ArmingBarrierTimeProvider.cs
+UtilitiesCS.Test/ReusableTypeClasses/StackGeek_Tests.cs
+```
+
+Three of the five are new files this plan created, which is the expected case: a newly authored
+file has never been through the formatter. `PrettyPrint.cs` was reformatted because the `using`
+block was reordered by hand. `StackGeek_Tests.cs` was reformatted because the new
+`FluentActions.Invoking(...)` chain exceeded the 100-column width and CSharpier broke it across
+four lines.
+
+Because `REWRITTEN_COUNT` was greater than 0, the toolchain loop restarted at P7-T1 as the plan
+requires.
+
+## Pass 2 (2026-09-08T10-05) — clean
+
+```
+REWRITTEN_COUNT: 0
+```
+
+No hash differed and no porcelain entry appeared. `FORMAT_EXIT: 0`.
+
+## Pass 3 (2026-09-08T10-14) — clean, after the P7-T7 gap-closure edit
+
+The loop restarted a second time because P7-T6 failed its clause 2 and P7-T7 added two tests. The
+format pass over that edit also rewrote nothing:
+
+```
+REWRITTEN_COUNT: 0
+```
+
+`FORMAT_EXIT: 0`, `PRETTYPRINT_LINECOUNT: 680`. This is the final pass, and the artifact records
+its state.
+
+## Line-cap observation
+
+PRETTYPRINT_LINECOUNT: 680
+
+`UtilitiesCS/HelperClasses/PrettyPrint.cs` is at most 680 lines after the pass, as the acceptance
+condition requires. It is exactly 680, unchanged from baseline, so CSharpier's own reformatting of
+the file did not consume the zero-growth budget D10 established.
+
+## Acceptance evaluation
+
+- `EXIT_CODE: 0`. PASS
+- `REWRITTEN_COUNT: 0` on the final pass, defined as hash differences plus appeared porcelain
+ entries, and never read from the `Formatted N files` line. PASS
+- The loop restarted at P7-T1 when `REWRITTEN_COUNT` was greater than 0, and the final artifact
+ shows `REWRITTEN_COUNT: 0`. PASS
+- `PrettyPrint.cs` line count is at most 680 after the pass. PASS
+
+## Output Summary
+
+Three format passes. The first rewrote five files (three newly created by this plan, two
+reformatted after hand edits) and triggered a loop restart. The second rewrote nothing. The third,
+run after P7-T7's gap-closure edit forced a further restart, also rewrote nothing, confirming the
+tree is at a CSharpier fixed point. `PrettyPrint.cs` holds at exactly 680 lines throughout.
diff --git a/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/qa-gates/p7-t10-ac5-timing-hack-search.md b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/qa-gates/p7-t10-ac5-timing-hack-search.md
new file mode 100644
index 000000000..25ad3e61f
--- /dev/null
+++ b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/qa-gates/p7-t10-ac5-timing-hack-search.md
@@ -0,0 +1,95 @@
+# P7-T10 — AC5 timing-hack search over the anchored diff
+
+Timestamp: 2026-09-08T10-21
+Task: [P7-T10]
+Command: `git diff bb1c7d4b60f7b782227956f36859314d5c47bb03 -- "*.cs" ":(exclude).claude"`, filtered to added lines (`^\+` and not `^\+\+\+`), then counted with `Select-String -SimpleMatch` and the stated regexes
+EXIT_CODE: 0
+
+AC5 has no automated enforcement in this repository: `BannedSymbols.txt` bans `Thread.Sleep` and
+`Task.Delay` but not `CancelAfter`, `TimeoutAfter` or `WaitOne`, and `.editorconfig` holds RS0030
+at `suggestion`, so no toolchain step fails on a newly introduced sleep. This explicit search over
+the diff is therefore the enforcement mechanism, as spec.md records under "AC5 enforcement, stated
+honestly".
+
+## Scope of the search
+
+| Observation | Value |
+|---|---|
+| Total diff lines over `*.cs` (dot-claude excluded) | 1530 |
+| Added lines examined | 737 |
+
+## Fixed-string counts over added lines
+
+| Pattern | Count | Verdict |
+|---|---|---|
+| `Thread.Sleep` | 0 | OK |
+| `Task.Delay` | 0 | OK |
+| `CancelAfter` | 0 | OK |
+| `WaitOne` | 0 | OK |
+| `Returns(120)` | 0 | OK |
+| `SpinWait` | 0 | OK |
+| `Stopwatch` | 0 | OK |
+
+`Stopwatch` counts 0 as the plan predicts: the production files' existing stopwatches
+(`OlTableExtensions.Etl.cs`, `DfDeedle.cs`) are pre-existing lines, not added ones, so they do not
+enter this search.
+
+## Regex shape counts over added lines
+
+| Shape | Meaning | Count | Verdict |
+|---|---|---|---|
+| `new CancellationTokenSource\(\s*\d` | a numeric constructor timeout | 0 | OK |
+| `\.Wait\(\s*(\d\|TimeSpan)` | a timed wait | 0 | OK |
+| `catch \(`, restricted to added lines in hunks of files under `UtilitiesCS.Test/` | a retry loop needs a catch | 0 | OK |
+| `Returns\(\s*\d{2,}\s*\)` on a line that also contains `GetRowCount` | a mock value chosen to outrun a deadline | 0 | OK |
+
+The `catch (` restriction was applied by tracking the `diff --git` header of each file section and
+only counting added lines while inside a section whose post-image path is under `UtilitiesCS.Test/`.
+The two new test classes use `finally` only, to release their gates; neither catches anything.
+
+## RETIRED
+
+The existing timing tolerance is gone from the final tree, not merely absent from the added lines:
+
+| Token in `UtilitiesCS.Test/OutlookObjects/Table/OlTableExtensions_Tests.cs` | Final count |
+|---|---|
+| `Returns(120)` | 0 |
+| `cannot fire under test-host` | 0 |
+
+Both were 1 at the base commit (measured by P0-T6). The mock row count that was set to 120 so the
+250 ms deadline could not fire is replaced by an un-advanced `FakeTimeProvider`, which controls the
+clock rather than widening the deadline.
+
+## RETAINED_GATES
+
+Two occurrences of a `.Wait` member reference survive in the two new test files, and both are
+release-by-test gates rather than timed waits:
+
+| File | Occurrence | Form |
+|---|---|---|
+| `DfDeedleEtlTimeoutTests.cs` | `BuildExplorer(BuildFolderWithTriageUdp(), gateA.Wait, gateB.Wait)` | two method-group references, passed as delegates |
+| `OlTableExtensionsEtlClockTests.cs` | `gate.Wait,` | one method-group reference, passed as a delegate |
+
+Count: 2 lines, carrying 3 method-group references. Each is `ManualResetEventSlim.Wait()` with no
+timeout argument, invoked inside a mock callback so that the production code blocks until the test
+itself releases the gate in a `finally`. A wait with no timeout cannot mask a timing defect: it
+does not bound how long the code under test may take, and the test's outcome is decided by the
+injected clock, not by elapsed wall time. The `\.Wait\(\s*(\d|TimeSpan)` regex above is what would
+catch a timed variant, and it counts 0. This is the same pattern the pre-existing
+`DfDeedleQfcColumnTimeoutTests` already uses.
+
+## Acceptance evaluation
+
+- Every one of the eleven counts is 0. PASS
+- Each pattern is listed with its count. PASS
+- A separate `RETIRED:` record shows `Returns(120)` and `cannot fire under test-host` both count 0
+ in the final tree of `OlTableExtensions_Tests.cs`. PASS
+- A `RETAINED_GATES:` record identifies the surviving `gate.Wait` occurrences as untimed
+ release-by-test gates, with their count. PASS
+
+## Output Summary
+
+No sleep, delay, retry, timed wait, spin, numeric token-source timeout, or deadline-outrunning
+mock value appears anywhere in the 737 added lines. The one pre-existing timing tolerance in the
+touched files was retired rather than retained. AC5 is satisfied on the technique, not only on the
+outcome.
diff --git a/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/qa-gates/p7-t2-csharpier-check.md b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/qa-gates/p7-t2-csharpier-check.md
new file mode 100644
index 000000000..f7601cf3a
--- /dev/null
+++ b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/qa-gates/p7-t2-csharpier-check.md
@@ -0,0 +1,32 @@
+# P7-T2 — Toolchain step 1 verification (CSharpier check)
+
+Timestamp: 2026-09-08T10-06
+Task: [P7-T2]
+Command: dotnet tool run csharpier check .
+EXIT_CODE: 0
+Toolchain pass: 3
+
+## Output Summary
+
+Summary line, verbatim:
+
+```
+Checked 1616 files in 7454ms.
+```
+
+The line begins with `Checked ` and ends with `ms.`, as the acceptance condition requires. Exit
+code 0 with no path reported as unformatted.
+
+This is the read-only CI-parity check that pairs with the P7-T1 write-mode format pass: `format`
+exits 0 whether or not it rewrote anything, while `check` exits non-zero when any file would be
+rewritten. Its passing here independently confirms the `REWRITTEN_COUNT: 0` observation P7-T1
+recorded from file hashes.
+
+The processed-file count is 1616, three higher than the 1613 P0-T7 recorded, which is exactly the
+three new `.cs` files this plan created
+(`DfDeedleEtlTimeoutTests.cs`, `OlTableExtensionsEtlClockTests.cs`, `ArmingBarrierTimeProvider.cs`).
+
+## Acceptance evaluation
+
+- `EXIT_CODE: 0`. PASS
+- The summary line is quoted verbatim, begins with `Checked ` and ends with `ms.`. PASS
diff --git a/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/qa-gates/p7-t3-msbuild-analyzers.md b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/qa-gates/p7-t3-msbuild-analyzers.md
new file mode 100644
index 000000000..cb4dc555d
--- /dev/null
+++ b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/qa-gates/p7-t3-msbuild-analyzers.md
@@ -0,0 +1,46 @@
+# P7-T3 — Toolchain step 2 (msbuild analyzer gate)
+
+Timestamp: 2026-09-08T10-07
+Task: [P7-T3]
+Command: msbuild TaskMaster.sln /t:Rebuild /m /p:Configuration=Debug "/p:Platform=Any CPU" /p:EnableNETAnalyzers=true /p:EnforceCodeStyleInBuild=true /nologo /v:q "/flp:LogFile=coverage/msbuild-p7-t3.log;Verbosity=normal"
+EXIT_CODE: 0
+Toolchain pass: 3
+
+## Observations
+
+| Observation | Value |
+|---|---|
+| Build exit code | `0` |
+| Count of lines exactly equal to ` 0 Error(s)` | `1` |
+| Warning count | `0` |
+| `BASELINE_ANALYZER_WARNINGS` from P0-T8 | `0` |
+| Warning count exceeds the baseline | no |
+| Distinct diagnostic ids present | none |
+
+Summary lines quoted verbatim from `coverage/msbuild-p7-t3.log`:
+
+```
+ 0 Warning(s)
+ 0 Error(s)
+```
+
+The acceptance condition requires that any warning present be enumerated by diagnostic id. A scan
+of the log for the pattern `(warning|error) ` produced an empty id set, so there is no warning
+to enumerate. The baseline ceiling of 0 is a strict one: any analyzer diagnostic this change had
+introduced would have exceeded it.
+
+`/t:Rebuild` is used rather than `/t:Build`, so `CoreCompile` actually ran on every project and
+the analyzers actually executed. A warm `/t:Build` would have returned exit 0 having skipped
+compilation, and the gate could not have failed.
+
+## Acceptance evaluation
+
+- `EXIT_CODE: 0`. PASS
+- The log contains ` 0 Error(s)`. PASS
+- The warning count (0) does not exceed `BASELINE_ANALYZER_WARNINGS` (0), and the empty diagnostic
+ id set is recorded. PASS
+
+## Output Summary
+
+Full-solution rebuild with .NET analyzers and code-style enforcement after the complete change
+set: exit 0, 0 warnings, 0 errors, no diagnostic ids.
diff --git a/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/qa-gates/p7-t4-msbuild-nullable.md b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/qa-gates/p7-t4-msbuild-nullable.md
new file mode 100644
index 000000000..2ec4db099
--- /dev/null
+++ b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/qa-gates/p7-t4-msbuild-nullable.md
@@ -0,0 +1,45 @@
+# P7-T4 — Toolchain step 3 (nullable / warnings-as-errors)
+
+Timestamp: 2026-09-08T10-07
+Task: [P7-T4]
+Command: msbuild TaskMaster.sln /t:Rebuild /m /p:Configuration=Debug "/p:Platform=Any CPU" /p:TreatWarningsAsErrors=true /nologo /v:q "/flp:LogFile=coverage/msbuild-p7-t4.log;Verbosity=normal"
+EXIT_CODE: 0
+Toolchain pass: 3
+
+## Observations
+
+| Observation | Value |
+|---|---|
+| Build exit code | `0` |
+| Count of lines exactly equal to ` 0 Error(s)` | `1` |
+| Warning count | `0` |
+
+Summary lines quoted verbatim from `coverage/msbuild-p7-t4.log`:
+
+```
+ 0 Warning(s)
+ 0 Error(s)
+```
+
+This is the gate that matters most for the eight production files in the write set, all of which
+carry `#nullable enable` and are therefore opted in to nullable flow analysis, with
+`/p:TreatWarningsAsErrors=true` promoting any `CS86xx` diagnostic to a build error. The change adds
+a null test against a non-nullable tuple element (`tableSnapshot.data is null` in `DfDeedle.cs`),
+two new nullable-annotated optional parameters (`TimeProvider? timeProvider`, `TextWriter? writer`)
+and a nullable delegate parameter, and none of them produced a diagnostic.
+
+`/p:Nullable=enable` is not supplied, matching `.github/workflows/_build-nullable.yml`. Nullable
+enforcement here is per-file opt-in through the pragma, and every file this change touches on the
+production side already carries it, so no enforcement is lost. `/t:Rebuild` is used so compilation
+actually runs.
+
+## Acceptance evaluation
+
+- `EXIT_CODE: 0`. PASS
+- The log contains ` 0 Error(s)`. PASS
+
+## Output Summary
+
+Full-solution rebuild with warnings treated as errors after the complete change set: exit 0, 0
+warnings, 0 errors. The new guard, the new optional parameters and the new delegate parameter all
+compile clean under nullable analysis.
diff --git a/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/qa-gates/p7-t5-vstest-coverage.md b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/qa-gates/p7-t5-vstest-coverage.md
new file mode 100644
index 000000000..2a94a1741
--- /dev/null
+++ b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/qa-gates/p7-t5-vstest-coverage.md
@@ -0,0 +1,110 @@
+# P7-T5 — Toolchain step 4 (final nine-assembly coverage run)
+
+Timestamp: 2026-09-08T10-16
+Task: [P7-T5]
+Command: dotnet-coverage collect --output coverage/p7-final.cobertura.xml --output-format cobertura --settings coverage.config -- /Settings:scripts\vscode\TaskMaster.cli.runsettings /InIsolation "/Logger:trx;LogFileName=p7-t5.trx" /ResultsDirectory:coverage/trx/p7-t5 /Blame:CollectHangDump;TestTimeout=4min;HangDumpType=None "/TestCaseFilter:TestCategory!=LiveOutlook&FullyQualifiedName!~ShellUtilities&FullyQualifiedName!~SysImageListHelper&FullyQualifiedName!~OSBrowser"
+EXIT_CODE: 0
+Toolchain pass: 3
+
+RERUN_COUNT: 0
+
+Each execution of this task passed on its first run. No re-run-until-green was performed and none
+is permitted: any failure here, including a failure of one of the two retained sentinels, would
+have been a defect requiring a fix and a restart of the loop at P7-T1.
+
+This task ran twice, once per toolchain-loop pass. The pass-2 run recorded
+`total=7160 passed=7160 failed=0`, exit 0, 76 s, `line-rate=0.8603698145941662`. P7-T6 then failed
+its clause 2 on two uncovered added lines, P7-T7 added two tests to close them, and the loop
+restarted at P7-T1. The figures below are the final pass-3 run, which is the one this artifact
+records.
+
+## TRX counters (final pass)
+
+```
+total=7162 executed=7162 passed=7162 failed=0 error=0 timeout=0 aborted=0 notExecuted=0
+```
+
+`failed` + `error` + `aborted` + `timeout` = 0. No result carried an outcome other than `Passed`.
+
+## Test-count delta
+
+| Observation | Value |
+|---|---|
+| P0-T10 baseline `total` | 7153 |
+| P7-T5 final `total` | 7162 |
+| Delta | +9 |
+
+The plan's stated expectation is +7 for the seven C4 tests, adjusted upward by the count of any
+test P7-T7 adds. P7-T7 added two, so the adjusted expectation is +9 and the observed delta is
+exactly +9. The seven C4 tests:
+
+1. `DictionaryExtensions_Tests.TryAddValuesAsync_PreCancelledToken_ThrowsTaskCanceledAndLeavesValueUnchanged`
+2. `DfDeedleEtlTimeoutTests.GetEmailDataInViewAsync_EtlDeadlineExpires_ThrowsInvalidOperationNamingFolder`
+3. `DfDeedleEtlTimeoutTests.GetEmailDataInViewAsync_ClockNeverAdvances_ReturnsOneRowFrame`
+4. `OlTableExtensionsEtlClockTests.EtlAsync_DeadlineExpires_ReturnsNullDataAndCancelsTokenSource`
+5. `OlTableExtensionsEtlClockTests.EtlAsync_ClockNeverAdvances_ReturnsTransformedRows`
+6. `StackGeek_Tests.Run_WritesScenarioToSuppliedWriter`
+7. `PrettyPrint_Tests.PrettyPrint_NullWriter_WritesToConsoleWithoutThrowing`
+
+plus the two tests P7-T7 added:
+
+8. `DfDeedleEtlTimeoutTests.GetEmailDataInView_NoEtlArgument_UsesProductionDefaultDelegate`
+9. `OlTableExtensionsEtlClockTests.EtlAsync_NoBinaryOrObjectFields_UsesGetArrayBranchOnControlledClock`
+
+The delta being exactly 9 also confirms that the three renamed tests were renamed and not
+duplicated, and that no test was lost when `Main_RunsSampleScenarioWithoutThrowing` was rewritten.
+
+## The nine C4 names
+
+| Test | Outcome |
+|---|---|
+| `TryAddValuesAsync_PreCancelledToken_ThrowsTaskCanceledAndLeavesValueUnchanged` | `Passed` |
+| `GetEmailDataInViewAsync_EtlDeadlineExpires_ThrowsInvalidOperationNamingFolder` | `Passed` |
+| `GetEmailDataInViewAsync_ClockNeverAdvances_ReturnsOneRowFrame` | `Passed` |
+| `EtlAsync_DeadlineExpires_ReturnsNullDataAndCancelsTokenSource` | `Passed` |
+| `EtlAsync_ClockNeverAdvances_ReturnsTransformedRows` | `Passed` |
+| `Run_WritesScenarioToSuppliedWriter` | `Passed` |
+| `PrettyPrint_NullWriter_WritesToConsoleWithoutThrowing` | `Passed` |
+| `TryAddValuesAsync_UpdatesExistingValue` (retained #780 sentinel) | `Passed` |
+| `GetEmailDataInViewAsync_SeparatesTableSnapshotFromDataFrameTransform` (retained #803 sentinel) | `Passed` |
+
+None returned `ABSENT`. The two P7-T7 gap-closure tests,
+`GetEmailDataInView_NoEtlArgument_UsesProductionDefaultDelegate` and
+`EtlAsync_NoBinaryOrObjectFields_UsesGetArrayBranchOnControlledClock`, also both report `Passed`.
+
+## Root Cobertura figures
+
+Read from the root element of `coverage/p7-final.cobertura.xml`.
+
+| Attribute | Baseline (P0-T10) | Final (P7-T5 pass 3) |
+|---|---|---|
+| `line-rate` | `0.8601092896174863` | `0.8604239666249645` |
+| `lines-covered` | `172353` | `172626` |
+| `lines-valid` | `200385` | `200629` |
+| `branch-rate` | `0.6630576006929406` | `0.6639784946236559` |
+| `branches-covered` | `21434` | `21489` |
+| `branches-valid` | `32326` | `32364` |
+
+Repository-wide line coverage rose from 86.011 to 86.042 percent. The full delta analysis,
+including the per-file and per-changed-line gates, is in `p7-t6-coverage-delta.md`.
+
+## Wall clock
+
+RUN_SECONDS: 58 for the final pass (the pass-2 run took 76 s; the baseline run took 64 s). The
+variation is run-to-run scheduling noise on a shared workstation, not a property of the change.
+
+## Acceptance evaluation
+
+- `EXIT_CODE: 0`. PASS
+- Counters recorded. PASS
+- `failed` + `error` + `aborted` + `timeout` = 0, with `RERUN_COUNT: 0`. PASS
+- `total` exceeds the P0-T10 `total` by exactly 9, the adjusted expectation stated above (7 C4
+ tests plus the 2 tests P7-T7 added). PASS
+- All six root Cobertura figures recorded as numbers. PASS
+- `Read-TrxOutcome` is `Passed` for all nine C4 names. PASS
+
+## Output Summary
+
+Final full-suite coverage run: 7162 tests, 7162 passed, 0 failed, exit 0, 58 s, no re-runs.
+Repository-wide line coverage 0.860424 (172626 of 200629). All nine tracked test names pass,
+including both intermittent-failure sentinels this item repairs, as do the two gap-closure tests.
diff --git a/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/qa-gates/p7-t6-coverage-delta.md b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/qa-gates/p7-t6-coverage-delta.md
new file mode 100644
index 000000000..53b378c42
--- /dev/null
+++ b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/qa-gates/p7-t6-coverage-delta.md
@@ -0,0 +1,92 @@
+# P7-T6 — Coverage delta
+
+Timestamp: 2026-09-08T10-17
+Task: [P7-T6]
+Command: pwsh -NoProfile -File coverage/plan811-helper.ps1 running PROC-COV over `coverage/p0-baseline.cobertura.xml` and `coverage/p7-final.cobertura.xml` and PROC-CHANGED (`git diff --unified=0 bb1c7d4b60f7b782227956f36859314d5c47bb03 -- `)
+EXIT_CODE: 0
+Toolchain pass: 3
+
+All eight production files are tracked at the base commit, so the anchored diff sees every change
+to them and no staging companion is needed for this task.
+
+## Clause 1 — per-file uncovered, final at most baseline
+
+| Suffix | Baseline valid/covered/uncovered | Final valid/covered/uncovered | Verdict |
+|---|---|---|---|
+| `UtilitiesCS\Extensions\DictionaryExtensions.cs` | 108 / 101 / 7 | 106 / 99 / 7 | OK |
+| `UtilitiesCS\OutlookObjects\Table\OlTableExtensions.Etl.cs` | 297 / 271 / 26 | 296 / 284 / 12 | OK |
+| `UtilitiesCS\Extensions\DfDeedle.cs` | 159 / 157 / 2 | 163 / 163 / 0 | OK |
+| `UtilitiesCS\Extensions\DfDeedle.FrameUtilities.cs` | 169 / 117 / 52 | 170 / 118 / 52 | OK |
+| `UtilitiesCS\OutlookObjects\Filter DASL\DASLFilterParser.cs` | 79 / 76 / 3 | 79 / 76 / 3 | OK |
+| `UtilitiesCS\HelperClasses\PrettyPrint.cs` | 440 / 375 / 65 | 440 / 375 / 65 | OK |
+| `UtilitiesCS\OutlookObjects\Table\OlTableExtensions.TableAccess.cs` | 270 / 215 / 55 | 271 / 216 / 55 | OK |
+| `UtilitiesCS\ReusableTypeClasses\Other\StackGeek.cs` | 93 / 93 / 0 | 96 / 96 / 0 | OK |
+
+CLAUSE1_FAILURES: 0. No file regressed; two improved materially (`OlTableExtensions.Etl.cs` from
+26 uncovered to 12, `DfDeedle.cs` from 2 to 0).
+
+## Clause 2 — every added executable line has hits greater than 0
+
+```
+ADDED_TOTAL=76 EXECUTABLE_COVERED=38 NON_EXECUTABLE=38 UNCOVERED=0
+```
+
+76 post-image added lines across the eight files. 38 carry a line node in the final Cobertura
+document and all 38 report `hits` greater than 0; the other 38 have no line node and are recorded
+as `hits=non-executable` (signature continuation lines, `//` and `///` comment lines, braces and
+`using` directives). CLAUSE2_FAILURES: 0.
+
+This clause failed on the first evaluation of this task, with two uncovered added lines. Both were
+closed by P7-T7 and this is the post-closure measurement; see `p7-t7-gap-closure.md`.
+
+## Clause 3 — `StackGeek.Run` line rate
+
+RUN_LINE_RATE: 1
+
+`Get-MethodLineRate` for suffix `UtilitiesCS\ReusableTypeClasses\Other\StackGeek.cs`, method
+`Run`, returns 1. `Run` is a new method extracted by P5-T4, so CLAUDE.md UT2's 90 percent floor
+for new methods applies; 100 percent clears it. The rate is a number, not `ABSENT`, so the method
+element was found and the measurement is not vacuous.
+
+## Clause 4 — `EtlAsync` span rate
+
+| | Span | Rate |
+|---|---|---|
+| Baseline (P0-T11) | 66-130 | 35/50 = 0.7000 |
+| Final | 66-132 | 48/49 = 0.9796 |
+
+The final span was read from the edited file by the rule the plan states: the line containing
+`> EtlAsync(` through the first later line that is exactly eight spaces followed by `}`. The final
+rate 0.9796 is at least the baseline 0.7000, so the clause passes with a wide margin.
+
+The improvement is the point of the change: the `catch (TimeoutException)` block was entirely
+uncovered at baseline (lines 117-123 of the pre-change file) and is now exercised by
+`OlTableExtensionsEtlClockTests.EtlAsync_DeadlineExpires_ReturnsNullDataAndCancelsTokenSource`,
+and the `GetArray` branch's `TimeoutAfter` is exercised by
+`EtlAsync_NoBinaryOrObjectFields_UsesGetArrayBranchOnControlledClock`.
+
+ETLASYNC_UNCOVERED_FINAL: 96 — a single remaining uncovered line in the span, down from fifteen.
+
+## Clause 5 — repository-wide comparison
+
+COMPARABILITY: A
+
+| Observation | Value |
+|---|---|
+| Baseline `lines-valid` | 200385 |
+| Final `lines-valid` | 200629 |
+| Denominator delta | 0.1218 percent |
+| Baseline `line-rate` | 0.860109289617486 |
+| Final `line-rate` | 0.860423966624964 |
+| Branch A gate: final at least baseline minus 0.005 | True |
+
+The denominators differ by 0.1218 percent, which is within the 1 percent band, so branch A applies
+and the rate comparison is gated. Final line-rate 0.86042 is above baseline 0.86011, so coverage
+improved rather than regressed and the gate passes without needing the tolerance.
+
+## Output Summary
+
+All six clauses pass. Per-file uncovered counts held or improved on all eight production files;
+all 38 added executable lines are covered; the new `Run` method is at line rate 1; the `EtlAsync`
+span rose from 0.7000 to 0.9796; and repository-wide line coverage rose from 0.860109 to 0.860424
+on a denominator that moved 0.12 percent (COMPARABILITY: A, gate satisfied).
diff --git a/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/qa-gates/p7-t7-gap-closure.md b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/qa-gates/p7-t7-gap-closure.md
new file mode 100644
index 000000000..d6b505970
--- /dev/null
+++ b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/qa-gates/p7-t7-gap-closure.md
@@ -0,0 +1,83 @@
+# P7-T7 — Coverage gap closure
+
+Timestamp: 2026-09-08T10-17
+Task: [P7-T7]
+Command: two tests added, then the toolchain loop restarted at P7-T1 and P7-T5 and P7-T6 re-run
+EXIT_CODE: 0
+
+GAP CLOSURE: REQUIRED AND COMPLETED
+
+The first evaluation of P7-T6 failed clause 2 with two uncovered added lines. Both are now
+covered.
+
+## The two gaps and the tests that closed them
+
+### 1. `UtilitiesCS/Extensions/DfDeedle.cs:70` — the `DefaultTableEtl` lambda body
+
+The added line is `> DefaultTableEtl = t => ((Outlook.Table)t).ETL();`, the production ETL delegate
+P1-T3 introduced to replace the deleted `TableEtlInvoker` static. Its lambda body executes only
+when `GetEmailDataInView` is called with no `etl` argument. Every pre-existing test of that method
+passes an injected delegate, so the production default was never invoked and the line reported
+zero hits.
+
+Test added, in `UtilitiesCS.Test/Extensions/DfDeedleEtlTimeoutTests.cs` (the write-set test file
+that owns `DfDeedle.cs` per this task's ownership rule):
+
+`UtilitiesCS.Test.Extensions.DfDeedleEtlTimeoutTests.GetEmailDataInView_NoEtlArgument_UsesProductionDefaultDelegate`
+
+It calls `DfDeedle.GetEmailDataInView(explorer.Object)` with no `etl` argument over the same mock
+Explorer the class already builds, and asserts the frame has one row. This is the delegate
+counterpart of the null-writer default tests added for AC3: it proves the optional parameter's
+default reproduces the previous behaviour, which is exactly the invariant spec.md requires of every
+new optional parameter.
+
+### 2. `UtilitiesCS/OutlookObjects/Table/OlTableExtensions.Etl.cs:116` — the `GetArray` branch deadline
+
+The added line is `.TimeoutAfter(milliseconds, timeProvider);` on the `else` branch of `EtlAsync`,
+taken when the column set contains no `BinaryToStringFields` member and no object converter
+matches. It is an added line because P1-T1 changed its second argument from `attempts` to
+`timeProvider`. The corresponding pre-change line was also uncovered at baseline (P0-T11 recorded
+line 114 among the fifteen uncovered lines of the `EtlAsync` span), so this branch has never had a
+test.
+
+Test added, in `UtilitiesCS.Test/OutlookObjects/Table/OlTableExtensionsEtlClockTests.cs` (the
+write-set test file that owns `OlTableExtensions.Etl.cs`):
+
+`UtilitiesCS.Test.OutlookObjects.Table.OlTableExtensionsEtlClockTests.EtlAsync_NoBinaryOrObjectFields_UsesGetArrayBranchOnControlledClock`
+
+It supplies columns `EntryID` and `Subject` — neither is in
+`MAPIFields.BinaryToStringFields`, whose members are `ConversationIndex`, `ConversationId` and
+`Store` — and passes a null converter dictionary, so the branch condition is false on both
+disjuncts and the `GetArray` path is taken. The clock is an un-advanced `FakeTimeProvider`, so the
+250 ms deadline is armed but cannot fire. The test asserts the transformed array and that the token
+source was not cancelled.
+
+Neither test uses a sleep, a retry, a timed wait, or a mock value chosen to outrun a deadline; both
+are covered by the P7-T10 AC5 search over the added lines.
+
+## Loop restart
+
+Adding the two tests required restarting the toolchain loop at P7-T1, which was done. Pass 3
+recorded: `P7T1_REWRITTEN_COUNT: 0`, `P7T2_CHECK_EXIT: 0`, `P7T3_EXIT: 0` with ` 0 Error(s)` and
+0 warnings, `P7T4_EXIT: 0` with ` 0 Error(s)` and 0 warnings.
+
+## Re-run results
+
+`p7-t5-vstest-coverage.md` (re-run): `total` 7162, `passed` 7162, `failed` 0, exit 0. The delta
+over the P0-T10 baseline of 7153 is +9: the seven C4 tests plus these two gap-closure tests. Both
+new tests report `Passed`.
+
+`p7-t6-coverage-delta.md` (re-run): all six clauses pass.
+`ADDED_TOTAL=76 EXECUTABLE_COVERED=38 NON_EXECUTABLE=38 UNCOVERED=0`.
+
+## Acceptance evaluation
+
+- The artifact exists and records the outcome. PASS
+- The added test names are listed, fully qualified. PASS
+- A passing re-run of P7-T5 and P7-T6 accompanies them. PASS
+
+## Output Summary
+
+Two coverage gaps closed with two tests, both placed in the write-set test file that owns the
+production file. Clause 2 moved from 2 uncovered added lines to 0. The toolchain loop was restarted
+and completed clean on pass 3.
diff --git a/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/qa-gates/p7-t8-line-cap.md b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/qa-gates/p7-t8-line-cap.md
new file mode 100644
index 000000000..cb23729b5
--- /dev/null
+++ b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/qa-gates/p7-t8-line-cap.md
@@ -0,0 +1,66 @@
+# P7-T8 — File-size audit
+
+Timestamp: 2026-09-08T10-19
+Task: [P7-T8]
+Command: `(Get-Content -LiteralPath ).Count` for the 19 write-set `.cs` files, after the final format pass
+EXIT_CODE: 0
+
+## Measured line counts
+
+| File | Lines | Cap | Verdict |
+|---|---|---|---|
+| `UtilitiesCS/Extensions/DictionaryExtensions.cs` | 281 | 500 | OK |
+| `UtilitiesCS/OutlookObjects/Table/OlTableExtensions.Etl.cs` | 476 | 500 | OK |
+| `UtilitiesCS/Extensions/DfDeedle.cs` | 319 | 500 | OK |
+| `UtilitiesCS/Extensions/DfDeedle.FrameUtilities.cs` | 279 | 500 | OK |
+| `UtilitiesCS/OutlookObjects/Filter DASL/DASLFilterParser.cs` | 123 | 500 | OK |
+| `UtilitiesCS/HelperClasses/PrettyPrint.cs` | 680 | at most 680 (pre-existing violation) | OK |
+| `UtilitiesCS/OutlookObjects/Table/OlTableExtensions.TableAccess.cs` | 432 | 500 | OK |
+| `UtilitiesCS/ReusableTypeClasses/Other/StackGeek.cs` | 206 | 500 | OK |
+| `UtilitiesCS.Test/Extensions/DictionaryExtensions_Tests.cs` | 318 | 500 | OK |
+| `UtilitiesCS.Test/Extensions/DfDeedle_COM_Tests.cs` | 848 | strictly below 869 (pre-existing violation) | OK |
+| `UtilitiesCS.Test/Extensions/DfDeedleEtlTimeoutTests.cs` | 228 | 500 | OK |
+| `UtilitiesCS.Test/OutlookObjects/Table/OlTableExtensionsEtlClockTests.cs` | 234 | 500 | OK |
+| `UtilitiesCS.Test/Extensions/DfDeedleQfcColumnTimeoutTests.cs` | 453 | strictly below 500 | OK |
+| `UtilitiesCS.Test/TestHelpers/ArmingBarrierTimeProvider.cs` | 55 | 500 | OK |
+| `UtilitiesCS.Test/ReusableTypeClasses/StackGeek_Tests.cs` | 279 | 500 | OK |
+| `UtilitiesCS.Test/HelperClasses/PrettyPrint_Tests.cs` | 418 | 500 | OK |
+| `UtilitiesCS.Test/OutlookObjects/Filter DASL/DASLFilterParserTests.cs` | 109 | 500 | OK |
+| `UtilitiesCS.Test/OutlookObjects/Table/OlTableExtensions_Tests.cs` | 1846 | at most 1848 (pre-existing violation) | OK |
+| `UtilitiesCS.Test/HelperClasses/NLogTraceWriter_Test.cs` | 110 | 500 | OK |
+
+## Not touched
+
+| File | Lines | Expected |
+|---|---|---|
+| `UtilitiesCS/Threading/TimeOutTask.cs` | 1011 | exactly 1011 (untouched) |
+
+`TimeOutTask.cs` is byte-identical to the base commit; P7-T9's numstat over it is empty.
+
+## Project file
+
+`UtilitiesCS.Test/UtilitiesCS.Test.csproj` is 988 lines. It is recorded as exempt: the 500-line
+cap in the General Code Change Policy governs production code, test code and reusable script
+files, and does not reach `*.csproj`. Its growth is exactly three `` items, from
+985 in the merged base tree to 988.
+
+## Acceptance evaluation
+
+- Every file other than the three pre-existing violations is at most 500 lines. The largest is
+ `OlTableExtensions.Etl.cs` at 476. PASS
+- `UtilitiesCS/HelperClasses/PrettyPrint.cs` is at most 680 (it is exactly 680, unchanged). PASS
+- `UtilitiesCS.Test/Extensions/DfDeedle_COM_Tests.cs` is strictly below 869 (848, a reduction of
+ 21). PASS
+- `UtilitiesCS.Test/OutlookObjects/Table/OlTableExtensions_Tests.cs` is at most 1848 (1846, a
+ reduction of 9). PASS
+- `UtilitiesCS.Test/Extensions/DfDeedleQfcColumnTimeoutTests.cs` is strictly below 500 (453, a
+ reduction of 47 achieved by moving `ArmingBarrierTimeProvider` out). PASS
+- `UtilitiesCS/Threading/TimeOutTask.cs` is exactly 1011. PASS
+- The project file is recorded as exempt from the cap. PASS
+
+## Output Summary
+
+No new cap violation was introduced. All three pre-existing violations shrank or held:
+`DfDeedle_COM_Tests.cs` 869 to 848, `OlTableExtensions_Tests.cs` 1855 to 1846, `PrettyPrint.cs`
+680 to 680. `DfDeedleQfcColumnTimeoutTests.cs`, which was at exactly the 500-line cap and could not
+grow, dropped to 453.
diff --git a/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/qa-gates/p7-t9-write-set-diff.md b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/qa-gates/p7-t9-write-set-diff.md
new file mode 100644
index 000000000..d97244e15
--- /dev/null
+++ b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/qa-gates/p7-t9-write-set-diff.md
@@ -0,0 +1,82 @@
+# P7-T9 — Write-set scope audit
+
+Timestamp: 2026-09-08T10-19
+Task: [P7-T9]
+Command: `git add --intent-to-add -- `, then `git diff --name-only bb1c7d4b60f7b782227956f36859314d5c47bb03 -- "*.cs" "*.csproj" "*.config" "*.props" "*.targets" "*.runsettings" "*.yml" ":(exclude).claude"`, then `git status --porcelain --untracked-files=all -- . ":(exclude).claude"`, then `git diff --numstat bb1c7d4b60f7b782227956f36859314d5c47bb03 -- UtilitiesCS/Threading/TimeOutTask.cs UtilitiesCS/Extensions/DfDeedle.QfcColumns.cs`
+EXIT_CODE: 0
+
+The `--intent-to-add` staging is what makes the three new files visible to an anchored
+`--name-only` diff, which otherwise enumerates tracked changes only and would report them as
+absent. The `:(exclude).claude` pathspec is applied to every span because the dot-claude
+agent-memory tree is tracked in this repository and may be dirty from preparation agents.
+
+## Anchored name-only diff — exactly the 20 Write Set paths
+
+```
+UtilitiesCS.Test/Extensions/DfDeedleEtlTimeoutTests.cs
+UtilitiesCS.Test/Extensions/DfDeedleQfcColumnTimeoutTests.cs
+UtilitiesCS.Test/Extensions/DfDeedle_COM_Tests.cs
+UtilitiesCS.Test/Extensions/DictionaryExtensions_Tests.cs
+UtilitiesCS.Test/HelperClasses/NLogTraceWriter_Test.cs
+UtilitiesCS.Test/HelperClasses/PrettyPrint_Tests.cs
+UtilitiesCS.Test/OutlookObjects/Filter DASL/DASLFilterParserTests.cs
+UtilitiesCS.Test/OutlookObjects/Table/OlTableExtensionsEtlClockTests.cs
+UtilitiesCS.Test/OutlookObjects/Table/OlTableExtensions_Tests.cs
+UtilitiesCS.Test/ReusableTypeClasses/StackGeek_Tests.cs
+UtilitiesCS.Test/TestHelpers/ArmingBarrierTimeProvider.cs
+UtilitiesCS.Test/UtilitiesCS.Test.csproj
+UtilitiesCS/Extensions/DfDeedle.FrameUtilities.cs
+UtilitiesCS/Extensions/DfDeedle.cs
+UtilitiesCS/Extensions/DictionaryExtensions.cs
+UtilitiesCS/HelperClasses/PrettyPrint.cs
+UtilitiesCS/OutlookObjects/Filter DASL/DASLFilterParser.cs
+UtilitiesCS/OutlookObjects/Table/OlTableExtensions.Etl.cs
+UtilitiesCS/OutlookObjects/Table/OlTableExtensions.TableAccess.cs
+UtilitiesCS/ReusableTypeClasses/Other/StackGeek.cs
+```
+
+20 paths. Sorted, this list equals the plan's 20-path Write Set exactly:
+
+- **No fewer.** Every one of the 20 declared paths is touched. Items 1 to 8 are the production
+ files, 9 to 19 the test files, 20 the project file.
+- **No more.** No path outside the set appears. In particular no `.github/workflows/` file, no
+ `packages.config`, no `.runsettings`, no `.editorconfig` and no `BannedSymbols.txt` was touched,
+ and the diff span explicitly includes the `*.config`, `*.props`, `*.targets`, `*.runsettings`
+ and `*.yml` globs so that a change to any of them would have appeared.
+
+## Scoped porcelain status
+
+The porcelain output lists the same 20 source paths — three as `A` (the intent-to-added new files)
+and seventeen as `M` — plus, under `/`, the modified `plan.2026-09-07T22-03.md` and
+`spec.md` and 27 untracked evidence artifacts. No path outside the 20 plus `/` appears.
+
+`docs/features/potential/` is empty at this point; P8-T9 has not yet run. The C1 helper script
+`coverage/plan811-helper.ps1` does not appear because `coverage/*` is gitignored.
+
+## Untouched-file control
+
+```
+git diff --numstat bb1c7d4b60f7b782227956f36859314d5c47bb03 -- UtilitiesCS/Threading/TimeOutTask.cs UtilitiesCS/Extensions/DfDeedle.QfcColumns.cs
+```
+
+Output is empty. Both files are byte-identical to the base commit, which confirms two deliberate
+non-goals held:
+
+- The inert `(int, int)` `TimeoutAfter` overloads in `TimeOutTask.cs` were not deleted (D9; the
+ file is 1011 lines and already over the cap, and the overloads retain two test callers).
+- `DfDeedle.QfcColumns.cs` was not edited, so its line-96 doc comment still names the now-deleted
+ `TableEtlInvoker` and is stale. That is recorded as a follow-up in P8-T9 rather than fixed here,
+ because touching the file would widen the write set.
+
+## Acceptance evaluation
+
+- The name-only list, sorted, equals exactly the 20 Write Set paths: none missing, none extra.
+ PASS
+- The porcelain output lists no path outside the 20 paths plus `/` plus
+ `docs/features/potential/`. PASS
+- `git diff --numstat` over `TimeOutTask.cs` and `DfDeedle.QfcColumns.cs` is empty. PASS
+
+## Output Summary
+
+The change is exactly the declared 20-path write set. No scope escape. Both files the plan
+declared untouched are byte-identical to the base commit.
diff --git a/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/qa-gates/p8-t8-sanitisation.md b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/qa-gates/p8-t8-sanitisation.md
new file mode 100644
index 000000000..8ea4310a4
--- /dev/null
+++ b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/qa-gates/p8-t8-sanitisation.md
@@ -0,0 +1,58 @@
+# P8-T8 — Host-token sanitisation sweep
+
+Timestamp: 2026-09-08T10-42
+Task: [P8-T8]
+Command: pwsh -NoProfile -File coverage/plan811-helper.ps1 — `Select-String` over every file under `/evidence` for the run-time-derived account and machine tokens and the drive-rooted profile-path shape, plus a name scan over the same tree
+EXIT_CODE: 0
+
+The account and machine tokens are derived at run time
+(`[regex]::Escape((Split-Path -Leaf $env:USERPROFILE))` and
+`[regex]::Escape($env:COMPUTERNAME)`) and their values are never written into this artifact.
+
+## Counts
+
+| Check | Count | Verdict |
+|---|---|---|
+| Files scanned under `/evidence` | 40 | — |
+| Content matches for the account token (case-insensitive) | 0 | OK |
+| Content matches for the machine token (case-insensitive) | 0 | OK |
+| Content matches for the drive-rooted profile-path shape | 0 | OK |
+| File or directory names containing either token | 0 | OK |
+
+All four final counts are 0.
+
+## Substitutions performed
+
+None. No file required rewriting, so the per-file substitution count is 0 for every file. This is
+the observation, not the exit code: the rewrite step exits 0 whether or not it changes anything,
+so the counts above are what establish the result.
+
+Two artifacts quote redacted stack traces that originally contained absolute worktree paths, and
+both were redacted at authoring time rather than by this sweep:
+
+- `evidence/regression-testing/p2-t4-ac2-fail-before.md` — the `NullReferenceException` trace, with
+ the worktree path written as ``.
+- `evidence/regression-testing/p8-t4-ac4-runs.md` — the `MethodBodyReader_Tests` failure trace,
+ likewise written as ``.
+
+`evidence/baseline/p0-t5-tool-resolution.md` writes the Visual Studio installation root as
+`` and the user profile root as `` for the same reason.
+
+## Coverage of this sweep
+
+The sweep covers every artifact produced by P0-T1 through P8-T7, which is the 40 files counted
+above. The artifacts produced by P8-T9 through P8-T11 are written after this task from plan text
+only and are re-counted by P8-T12, which re-runs these same four checks over `$Feature` and
+`docs/features/potential/`.
+
+## Acceptance evaluation
+
+- All four final counts are 0. PASS
+- The artifact never writes either token's value. PASS
+- The sweep covers every artifact produced by P0-T1 through P8-T7, and P8-T12 re-counts the later
+ ones. PASS
+
+## Output Summary
+
+40 evidence files scanned. No account name, machine name, drive-rooted profile path, or offending
+file name found. No substitution was required.
diff --git a/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/regression-testing/fail-before-exception.2026-09-08T09-50.md b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/regression-testing/fail-before-exception.2026-09-08T09-50.md
new file mode 100644
index 000000000..977c07218
--- /dev/null
+++ b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/regression-testing/fail-before-exception.2026-09-08T09-50.md
@@ -0,0 +1,90 @@
+# Fail-before exception dossier — AC1 (issue #780)
+
+Timestamp: 2026-09-08T09-50
+Task: [P4-T3]
+Acceptance criterion: AC1
+EXIT_CODE: 0
+
+## Why a failing run is impossible
+
+WhyFailingRunImpossible: The #780 failure is load-dependent thread-pool scheduling latency above 500 ms, and the only ways to force it on demand are a sleep or a deliberately starved thread pool, both forbidden by AC5 and by the repository unit-test policy. A test built either way would also be nondeterministic in the opposite direction, passing or failing with the same host load the defect depends on.
+
+The #780 failure is thread-pool scheduling latency exceeding 500 ms under a 24-worker coverage
+run, so it is load-dependent rather than deterministic: `TryAddValuesAsync_UpdatesExistingValue`
+passes in about 2 ms when run alone and fails intermittently only under full-suite parallel load.
+The only ways to force the latency on demand are a sleep or a deliberately starved thread pool,
+and AC5 forbids stabilising or reproducing behaviour with a sleep, a retry, or a timing tolerance,
+as does the repository unit-test policy's ban on wall-clock waits in test code. A test that waited
+for the pool to fall behind would also be nondeterministic in the opposite direction: it would
+pass or fail depending on the same host load the defect depends on. No deterministic failing run
+is therefore constructible, and this dossier stands in its place.
+
+This is recorded as decision D6 in the plan and is a deviation from the spec Test Strategy's
+"token cancelled after the work is observed to start" bullet, which is not authored: `TryAddValues`
+offers no observation point without a custom operator type, and such a test would pass identically
+before and after the fix, so it would carry no discriminating power.
+
+## Alternative proof
+
+### 1. Structural count gate on the deleted window
+
+The defect is a hard-coded wall-clock cancellation window in production code. Its presence and its
+removal are both directly measurable, and the measurement is exact rather than probabilistic.
+
+| Token in `UtilitiesCS/Extensions/DictionaryExtensions.cs` | Before P4-T1 | After P4-T1 |
+|---|---|---|
+| `CancelAfter(` | 1 | 0 |
+| `CreateLinkedTokenSource` | 1 | 0 |
+| `TryAddValues(key, value), token)` | 0 | 1 |
+
+The "before" figures are the ones P0-T6 measured against the base tree `bb1c7d4b` and recorded in
+`evidence/baseline/p0-t6-citation-baseline.md`; both matched the plan's stated values exactly. The
+"after" figures were measured by P4-T1 against the edited file. The third row is the positive
+control: it shows the caller's own token, rather than a linked token carrying an internal deadline,
+is now what `Task.Run` receives.
+
+The deleted lines were:
+
+```csharp
+var linkedTS = CancellationTokenSource.CreateLinkedTokenSource(token);
+linkedTS.CancelAfter(500);
+```
+
+With `CancelAfter(` at 0 occurrences, no fixed wall-clock window remains in the method, which is
+AC1's first disjunct discharged by construction rather than by observation. A future reintroduction
+would raise the count above 0 and is additionally caught by the P7-T10 AC5 diff search, which
+counts `CancelAfter` over the added lines of the anchored diff.
+
+Secondary defect also resolved: `linkedTS` was never disposed, so every call leaked a timer until
+it fired. With the linked source gone, nothing is leaked.
+
+### 2. Contract-lock test
+
+Deleting a cancellation window could in principle weaken the surviving cancellation contract. It
+does not, and P4-T2 adds a test that fails if it ever does:
+`UtilitiesCS.Test.Extensions.DictionaryExtensions_Tests.TryAddValuesAsync_PreCancelledToken_ThrowsTaskCanceledAndLeavesValueUnchanged`
+asserts that a caller token cancelled before the call still yields `TaskCanceledException` from
+`Task.Run` and that the dictionary value is unchanged. Its passing run is recorded in
+`p4-t4-ac1-pass-after.md`.
+
+### 3. Zero production call sites
+
+`TryAddValuesAsync` has no production caller. The only invocation anywhere in the repository is
+`UtilitiesCS.Test/Extensions/DictionaryExtensions_Tests.cs:244`; the other two search hits are the
+definition at `DictionaryExtensions.cs:169` and the test method name at line 237. This is decision
+D3, and it is why deletion rather than a `TimeProvider` seam is the correct fix: the window guarded
+nothing, could not interrupt a `Task.Run` body already running, and protected a bounded
+compare-and-swap loop (`TryAddValues`, lines 123-136) that performs no I/O and cannot hang.
+
+## Negative evidence record
+
+SearchScope: `docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/regression-testing/`
+SearchPatterns: `fail-before-exception.*.md`
+SearchResult: `docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/regression-testing/fail-before-exception.2026-09-08T09-50.md` (this file)
+
+## Output Summary
+
+AC1 has no deterministic failing run, for a stated and verifiable reason. The alternative proof is
+a three-part structural argument: an exact before-and-after count gate on the deleted window
+(1 to 0 for both tokens, with a positive control at 0 to 1), a contract-lock test for the
+cancellation semantics that survive, and the absence of any production call site.
diff --git a/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/regression-testing/p1-t9-seam-scoped-run.md b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/regression-testing/p1-t9-seam-scoped-run.md
new file mode 100644
index 000000000..a6a4be65a
--- /dev/null
+++ b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/regression-testing/p1-t9-seam-scoped-run.md
@@ -0,0 +1,60 @@
+# P1-T9 — Scoped run of every class the seams touch
+
+Timestamp: 2026-09-08T09-38
+Task: [P1-T9]
+Command: UtilitiesCS.Test\bin\Debug\UtilitiesCS.Test.dll /InIsolation "/Logger:trx;LogFileName=p1-t9.trx" /ResultsDirectory:coverage/trx/p1-t9 /Blame:CollectHangDump;TestTimeout=4min;HangDumpType=None "/TestCaseFilter:FullyQualifiedName~DfDeedle_COM_Tests|FullyQualifiedName~DfDeedleQfcColumnTimeoutTests|FullyQualifiedName~OlTableExtensions_Tests|FullyQualifiedName~TimeOutTask_Tests"
+EXIT_CODE: 0
+
+The filter uses `FullyQualifiedName~` joined with `|` rather than the full-run filter,
+because a run scoped to `UtilitiesCS.Test.dll` alone cannot reach the repository's only
+`[TestCategory("LiveOutlook")]` test, which lives in `TaskMaster.Test`.
+
+## TRX counters
+
+```
+total=179 executed=179 passed=179 failed=0 error=0 timeout=0 aborted=0 notExecuted=0
+```
+
+No result carried an outcome other than `Passed`.
+
+## Named outcomes
+
+| Class | Method | Outcome |
+|---|---|---|
+| `UtilitiesCS.Test.Extensions.DfDeedle_COM_Tests` | `GetEmailDataInViewAsync_SeparatesTableSnapshotFromDataFrameTransform` | `Passed` |
+| `UtilitiesCS.Test.Extensions.DfDeedleQfcColumnTimeoutTests` | `AddQfcColumnsAsync_ThreeDeadlines_InvokesColumnAdderExactlyOnce` | `Passed` |
+
+The second outcome is the load-bearing one for P1-T6: it proves the `ArmingBarrierTimeProvider`
+still drives the #798 deadline tests correctly after being moved out of
+`DfDeedleQfcColumnTimeoutTests.cs` into the shared
+`UtilitiesCS.Test/TestHelpers/ArmingBarrierTimeProvider.cs`. The move was a pure extraction with
+no member-body change, and `NewSignal` still resolves from `ColumnAdderProbe` at the source file's
+former lines 95 and 103 through the added `using UtilitiesCS.Test.TestHelpers;`.
+
+`TimeOutTask_Tests` is included in the filter because
+`UtilitiesCS.Test/Threading/TimeOutTask_Tests.cs:197,210` still call the inert `(int, int)`
+`TimeoutAfter` overloads, which this change deliberately leaves in place (D9, spec non-goal). The
+class passing proves those untouched overloads still bind after the call sites in
+`OlTableExtensions.Etl.cs` and `DfDeedle.cs` moved to the `(int, TimeProvider?)` overload.
+
+## Blame hang guard
+
+SEQUENCE_FILES: 0
+
+No `Sequence` file was written under `coverage/trx/p1-t9`, so the 4-minute blame hang timeout did
+not fire on any test.
+
+## Acceptance evaluation
+
+- `EXIT_CODE: 0`. PASS
+- Counters recorded. PASS
+- `failed` = 0. PASS
+- `Read-TrxOutcome` returned `Passed` for both named tests. Neither returned `ABSENT`, so both
+ were discovered and executed. PASS
+
+## Output Summary
+
+179 tests across the four seam-affected classes, all passed, exit 0. Both named sentinel tests
+passed. The Phase 1 seam work introduced no behaviour change detectable by the existing suite,
+which is the intended outcome: every new parameter defaults to the value that reproduces the
+previous behaviour.
diff --git a/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/regression-testing/p2-t3-red-build.md b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/regression-testing/p2-t3-red-build.md
new file mode 100644
index 000000000..cc7ba8682
--- /dev/null
+++ b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/regression-testing/p2-t3-red-build.md
@@ -0,0 +1,71 @@
+# P2-T3 — Build the test project to produce the assembly
+
+Timestamp: 2026-09-08T09-43
+Task: [P2-T3]
+Command: msbuild UtilitiesCS.Test\UtilitiesCS.Test.csproj /t:Build /m /p:Configuration=Debug /p:Platform=AnyCPU /nologo /v:q "/flp:LogFile=coverage/msbuild-p2-t3.log;Verbosity=normal"
+EXIT_CODE: 0
+
+This is a project-file build used only to produce a test assembly for the scoped P2-T4 run. It is
+not a gate, so it uses `/p:Platform=AnyCPU` without the space, which is the project-level spelling;
+the solution-level alias `"/p:Platform=Any CPU"` is used by the two solution gates.
+
+## Final observations
+
+| Observation | Value |
+|---|---|
+| Build exit code | `0` |
+| Count of lines in the file log exactly equal to ` 0 Error(s)` | `1` |
+| Warning count | `0` |
+| `Test-Path UtilitiesCS.Test\bin\Debug\UtilitiesCS.Test.dll` | `True` |
+| Assembly `LastWriteTime` | `2026-09-08T09:43:46` |
+| Task start | `2026-09-08T09:43:35` |
+| Assembly is newer than the task start | `True` |
+
+Summary lines quoted verbatim from `coverage/msbuild-p2-t3.log`:
+
+```
+ 0 Warning(s)
+ 0 Error(s)
+```
+
+The `LastWriteTime` comparison is the non-vacuity control: it proves the run produced a fresh
+assembly containing the new test class rather than leaving an earlier build in place.
+
+## Two compile errors were fixed inside this task
+
+The first two invocations of this task's command failed. Both are mechanical compile fixes to the
+file P2-T1 created, made within this task because the task's stated outcome is a building
+assembly. Neither changes the test's behaviour, its arrangement, or any P2-T1 acceptance count,
+and both were re-verified against the full P2-T1 count set afterwards.
+
+1. **`CS0104` on `Action` (2 occurrences, exit 1).** `'Action' is an ambiguous reference between
+ 'Microsoft.Office.Interop.Outlook.Action' and 'System.Action'`. The file carries
+ `using Microsoft.Office.Interop.Outlook;`, which introduces an Outlook `Action` COM type.
+ Fixed by qualifying the two gate-delegate parameters of the private `BuildExplorer` helper as
+ `System.Action`, which is the same spelling
+ `UtilitiesCS.Test/Extensions/DfDeedleQfcColumnTimeoutTests.cs` already uses for its own gate
+ delegate field.
+2. **`CS0246` on `SilentProgressTracker` (1 occurrence, exit 1).**
+ `SilentProgressTracker` is a `private sealed` nested class of `DfDeedle_COM_Tests`, so it is
+ not visible from another class. The plan's P2-T1 text specifies a `ProgressTracker` over
+ `SilentProgressTracker` as part of the builders replicated from `DfDeedle_COM_Tests`; the
+ replication was completed by adding the same nested class to `DfDeedleEtlTimeoutTests`, with
+ the three no-op `Report` overrides unchanged.
+
+Post-fix re-verification of every P2-T1 acceptance count: `[TestMethod]`=2,
+`ArmingBarrierTimeProvider`=2, `Advance(250)`=1, `ThrowAsync`=1,
+`Thread.Sleep`=0, `Task.Delay`=0, `CancelAfter`=0, `WaitOne`=0, timed-wait regex=0, line count
+209 (cap 300). All pass.
+
+## Acceptance evaluation
+
+- `EXIT_CODE: 0`. PASS
+- The log contains ` 0 Error(s)`. PASS
+- `UtilitiesCS.Test\bin\Debug\UtilitiesCS.Test.dll` exists and its `LastWriteTime` is later than
+ the task start. PASS
+
+## Output Summary
+
+Test project builds clean after two mechanical compile fixes (Outlook `Action` ambiguity, and a
+private nested `SilentProgressTracker` that had to be replicated rather than referenced). Fresh
+assembly produced at 09:43:46, 11 seconds after the task started.
diff --git a/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/regression-testing/p2-t4-ac2-fail-before.md b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/regression-testing/p2-t4-ac2-fail-before.md
new file mode 100644
index 000000000..4ed3da165
--- /dev/null
+++ b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/regression-testing/p2-t4-ac2-fail-before.md
@@ -0,0 +1,91 @@
+# P2-T4 — AC2 fail-before evidence [expect-fail]
+
+Timestamp: 2026-09-08T09-45
+Task: [P2-T4]
+Command: UtilitiesCS.Test\bin\Debug\UtilitiesCS.Test.dll /InIsolation "/Logger:trx;LogFileName=p2-t4.trx" /ResultsDirectory:coverage/trx/p2-t4 /Blame:CollectHangDump;TestTimeout=4min;HangDumpType=None "/TestCaseFilter:FullyQualifiedName~DfDeedleEtlTimeoutTests"
+EXIT_CODE: 1
+ExpectedExitCode: 1
+
+A failing test is the expected and required outcome of this task. The paired pass-after record is
+`p3-t2-ac2-pass-after.md`, produced after P3-T1 inserts the guard.
+
+## TRX counters
+
+`coverage/trx/p2-t4/p2-t4.trx` exists.
+
+```
+total=2 executed=2 passed=1 failed=1 error=0 timeout=0 aborted=0 notExecuted=0
+```
+
+Expected: `total`=2, `passed`=1, `failed`=1. Observed exactly that.
+
+## Named outcomes
+
+| Method | Expected | Observed |
+|---|---|---|
+| `GetEmailDataInViewAsync_EtlDeadlineExpires_ThrowsInvalidOperationNamingFolder` | `Failed` | `Failed` |
+| `GetEmailDataInViewAsync_ClockNeverAdvances_ReturnsOneRowFrame` | `Passed` | `Passed` |
+
+Neither returned `ABSENT`, so both tests were discovered and executed: the new file is registered
+in the project file and compiled into the assembly.
+
+## Failure message, verbatim after redaction
+
+The worktree path is redacted to `` and the account name to ``; nothing else is
+altered.
+
+```
+Expected a to be thrown, but found :
+System.NullReferenceException: Object reference not set to an instance of an object.
+ at UtilitiesCS.DfDeedle.d__8.MoveNext() in \UtilitiesCS\Extensions\DfDeedle.cs:line 179
+--- End of stack trace from previous location where exception was thrown ---
+ at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
+ at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
+ at FluentAssertions.Specialized.AsyncFunctionAssertions`2.d__15.MoveNext() in /_/Src/FluentAssertions/Specialized/AsyncFunctionAssertions.cs:line 373.
+```
+
+The TRX text contains `System.NullReferenceException` inside that result's `ErrorInfo/Message`;
+FluentAssertions reports the found exception type, which is exactly the discriminating signal the
+acceptance condition asks for. A test that merely asserted `Should().Throw()` would
+have passed here and proved nothing.
+
+## The reported line is the defect site
+
+`DfDeedle.cs:179` in the current tree is the opening line of the `LogDfTiming(` invocation whose
+dereference `tableSnapshot.Item1.GetLength(0)` sits on line 181. This is the same statement the CI
+stack trace reported as `DfDeedle.cs:186` in the pre-change tree; the site moved from 186 to 179
+because P1-T3 deleted 14 lines of static-seam declarations above it and P1-T2 added 4 lines below
+that. The .NET sequence point for a multi-line invocation is the line the statement opens on,
+which is why both traces name the opening line rather than the dereference line.
+
+The mechanism is the one the research artifact derived: the 250 ms ETL hop deadline expires on the
+injected clock, `EtlByRowAsync` throws `TimeoutException`, `EtlAsync` swallows it at its
+`catch (TimeoutException)` block, `data` stays null, and `return (data!, columnDictionary)` forces
+that null through a null-forgiving suppression into a non-nullable tuple element. The guard P3-T1
+adds goes between lines 178 and 179, ahead of the whole call.
+
+## Blame hang guard
+
+SEQUENCE_FILES: 0
+
+No `Sequence` file was written under `coverage/trx/p2-t4`, so the 4-minute blame hang timeout did
+not fire. The test reached its assertion and failed on the assertion, rather than hanging: this is
+what confirms the D5 arming order held and that the clock was advanced only after the 250 ms timer
+existed.
+
+## Acceptance evaluation
+
+- `EXIT_CODE: 1` with `ExpectedExitCode: 1`. PASS
+- Counters `total`=2, `passed`=1, `failed`=1. PASS
+- `Read-TrxOutcome` for the expiry test is `Failed` and its `ErrorInfo/Message` contains
+ `System.NullReferenceException`. PASS
+- `Read-TrxOutcome` for the green test is `Passed`. PASS
+- No `Sequence` file under the results directory, so the blame hang timeout did not fire. PASS
+- The failure message is quoted verbatim after redaction. PASS
+
+## Output Summary
+
+RED established for AC2. The new deadline-expiry test fails with `NullReferenceException` where it
+asserts `InvalidOperationException`, at the exact production site the issue reports. The companion
+green-path test passes on the same un-advanced clock, which shows the failure is caused by the
+expired deadline and not by the test arrangement.
diff --git a/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/regression-testing/p3-t2-ac2-pass-after.md b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/regression-testing/p3-t2-ac2-pass-after.md
new file mode 100644
index 000000000..7feb186fa
--- /dev/null
+++ b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/regression-testing/p3-t2-ac2-pass-after.md
@@ -0,0 +1,83 @@
+# P3-T2 — AC2 pass-after evidence
+
+Timestamp: 2026-09-08T09-48
+Task: [P3-T2]
+Command: msbuild UtilitiesCS.Test\UtilitiesCS.Test.csproj /t:Build /m /p:Configuration=Debug /p:Platform=AnyCPU /nologo /v:q "/flp:LogFile=coverage/msbuild-p3-t2.log;Verbosity=normal" ; then UtilitiesCS.Test\bin\Debug\UtilitiesCS.Test.dll /InIsolation "/Logger:trx;LogFileName=p3-t2.trx" /ResultsDirectory:coverage/trx/p3-t2 /Blame:CollectHangDump;TestTimeout=4min;HangDumpType=None "/TestCaseFilter:FullyQualifiedName~DfDeedleEtlTimeoutTests|FullyQualifiedName~DfDeedle_COM_Tests"
+EXIT_CODE: 0
+
+Paired fail-before record: `p2-t4-ac2-fail-before.md`.
+
+## Build
+
+| Observation | Value |
+|---|---|
+| Build exit code | `0` |
+| Count of lines exactly equal to ` 0 Error(s)` | `1` |
+
+```
+ 0 Warning(s)
+ 0 Error(s)
+```
+
+## Test run
+
+| Observation | Value |
+|---|---|
+| Test run exit code | `0` |
+
+```
+total=28 executed=28 passed=28 failed=0 error=0 timeout=0 aborted=0 notExecuted=0
+```
+
+`failed` = 0. No result carried an outcome other than `Passed`.
+
+## Named outcomes
+
+| Class | Method | P2-T4 (before) | P3-T2 (after) |
+|---|---|---|---|
+| `DfDeedleEtlTimeoutTests` | `GetEmailDataInViewAsync_EtlDeadlineExpires_ThrowsInvalidOperationNamingFolder` | `Failed` | `Passed` |
+| `DfDeedleEtlTimeoutTests` | `GetEmailDataInViewAsync_ClockNeverAdvances_ReturnsOneRowFrame` | `Passed` | `Passed` |
+| `DfDeedle_COM_Tests` | `GetEmailDataInViewAsync_SeparatesTableSnapshotFromDataFrameTransform` | not in scope of that run | `Passed` |
+
+The first row is the RED-to-GREEN transition AC2 requires. The only change between the two runs is
+P3-T1's guard: no test text, no mock, and no deadline value was altered. The test still asserts the
+exception type explicitly and the folder name through `.WithMessage("*Inbox*")`, so a regression to
+`NullReferenceException`, or to an exception that does not name the folder, would fail it rather
+than pass a generic assertion.
+
+The third row is the #803 sentinel, which now runs with an un-advanced `FakeTimeProvider`, so every
+deadline on its path is armed on a clock that does not move and cannot fire under host load.
+
+## Note on the P3-T1 `#nullable enable` acceptance clause
+
+P3-T1's acceptance lists `#nullable enable`=1 for `DfDeedle.cs`. The measured substring count is 2,
+and it was also 2 at the base commit `bb1c7d4b` (verified with
+`git grep -c -F '#nullable enable' bb1c7d4b -- UtilitiesCS/Extensions/DfDeedle.cs`, which returns
+2). The second hit is line 157, a pre-existing #798 comment reading
+"file's #nullable enable while the validator parameter is a non-nullable string." — it is prose,
+not a directive, and this change does not touch it.
+
+The clause was therefore resolved on its discriminator rather than on the stated number: the
+`#nullable enable` directive itself occurs on exactly one line (a whole-line regex match returns
+1, at line 23), and the substring count is identical before and after this change, so the edit
+altered the file's nullable state in no way. Satisfying the literal count of 1 would have required
+deleting an unrelated pre-existing comment, which is outside the write set's intent and is not
+requested anywhere in the plan. This is recorded as a plan-citation defect for the orchestrator;
+it changes no acceptance criterion and no other gate depends on the figure. P0-T6 did not assert
+this count, so the halt gate had no opportunity to catch it.
+
+## Acceptance evaluation
+
+- Build `EXIT_CODE: 0` with ` 0 Error(s)`. PASS
+- Test `EXIT_CODE: 0`. PASS
+- `failed` = 0. PASS
+- `Read-TrxOutcome` is `Passed` for both `DfDeedleEtlTimeoutTests` methods and for
+ `DfDeedle_COM_Tests` / `GetEmailDataInViewAsync_SeparatesTableSnapshotFromDataFrameTransform`.
+ PASS
+- The paired fail-before record is named: `p2-t4-ac2-fail-before.md`. PASS
+
+## Output Summary
+
+28 tests across the two `DfDeedle` classes, all passed, exit 0. The AC2 deadline-expiry test moved
+from `Failed` with `NullReferenceException` to `Passed` with the descriptive
+`InvalidOperationException` naming the folder, with the guard as the only intervening change.
diff --git a/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/regression-testing/p4-t4-ac1-pass-after.md b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/regression-testing/p4-t4-ac1-pass-after.md
new file mode 100644
index 000000000..974c2aa5d
--- /dev/null
+++ b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/regression-testing/p4-t4-ac1-pass-after.md
@@ -0,0 +1,75 @@
+# P4-T4 — AC1 pass-after evidence
+
+Timestamp: 2026-09-08T09-52
+Task: [P4-T4]
+Command: msbuild UtilitiesCS.Test\UtilitiesCS.Test.csproj /t:Build /m /p:Configuration=Debug /p:Platform=AnyCPU /nologo /v:q "/flp:LogFile=coverage/msbuild-p4-t4.log;Verbosity=normal" ; then UtilitiesCS.Test\bin\Debug\UtilitiesCS.Test.dll /InIsolation "/Logger:trx;LogFileName=p4-t4.trx" /ResultsDirectory:coverage/trx/p4-t4 /Blame:CollectHangDump;TestTimeout=4min;HangDumpType=None "/TestCaseFilter:FullyQualifiedName~DictionaryExtensions_Tests"
+EXIT_CODE: 0
+
+Paired fail-before record: `fail-before-exception.2026-09-08T09-50.md` (AC1 has no deterministic
+failing run; see that dossier for why, and for the structural alternative proof).
+
+## Build
+
+| Observation | Value |
+|---|---|
+| Build exit code | `0` |
+| Count of lines exactly equal to ` 0 Error(s)` | `1` |
+
+```
+ 0 Warning(s)
+ 0 Error(s)
+```
+
+## Test run
+
+| Observation | Value |
+|---|---|
+| Test run exit code | `0` |
+
+```
+total=15 executed=15 passed=15 failed=0 error=0 timeout=0 aborted=0 notExecuted=0
+```
+
+`failed` = 0.
+
+## Named outcomes
+
+| Method | Outcome |
+|---|---|
+| `TryAddValuesAsync_UpdatesExistingValue` | `Passed` |
+| `TryAddValuesAsync_PreCancelledToken_ThrowsTaskCanceledAndLeavesValueUnchanged` | `Passed` |
+
+The first is the #780 test, retained unchanged: no assertion was weakened, no tolerance added, and
+no attribute applied to serialise it. It now passes because the production defect it was tripping
+over is gone, not because the test was adjusted.
+
+The second is the new contract-lock test, which is what keeps the deletion honest: removing a
+cancellation window could in principle have removed cancellation altogether, and this test fails if
+a pre-cancelled caller token ever stops producing `TaskCanceledException` or if the value is
+mutated despite cancellation.
+
+## Duration of the #780 test
+
+DURATION_UPDATES: `00:00:00.0027633`
+
+Read from that result's `UnitTestResult/@duration`. 2.76 ms, which is under one second as the
+acceptance requires, and is consistent with the roughly 2 ms the issue reports for an isolated
+run. The failing runs recorded in #780 took about 20 s for this same test, because the 500 ms
+window fired and the `await` observed a cancellation after the pool eventually scheduled the work.
+With the window deleted there is no wall-clock deadline left in the method for load to exceed.
+
+## Acceptance evaluation
+
+- Build exit 0 with ` 0 Error(s)`. PASS
+- Test `EXIT_CODE: 0`. PASS
+- `failed` = 0. PASS
+- `Read-TrxOutcome` is `Passed` for both named methods. PASS
+- The duration of `TryAddValuesAsync_UpdatesExistingValue` read from `@duration` is under one
+ second (2.76 ms). PASS
+
+## Output Summary
+
+15 tests in `DictionaryExtensions_Tests`, all passed, exit 0. The retained #780 test runs in
+2.76 ms and the new pre-cancelled-token contract test passes, so the cancellation semantics that
+survive the deletion are locked by an assertion rather than assumed. AC1's "passes deterministically
+under 24-worker parallel coverage runs" clause is evidenced separately by the P8-T6 ten-run gate.
diff --git a/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/regression-testing/p5-t10-ac3-pass-after.md b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/regression-testing/p5-t10-ac3-pass-after.md
new file mode 100644
index 000000000..5332fcdde
--- /dev/null
+++ b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/regression-testing/p5-t10-ac3-pass-after.md
@@ -0,0 +1,109 @@
+# P5-T10 — AC3 pass-after evidence
+
+Timestamp: 2026-09-08T09-59
+Task: [P5-T10]
+Command: msbuild TaskMaster.sln /t:Rebuild /m /p:Configuration=Debug "/p:Platform=Any CPU" /p:EnableNETAnalyzers=true /p:EnforceCodeStyleInBuild=true /nologo /v:q "/flp:LogFile=coverage/msbuild-p5-t10.log;Verbosity=normal" ; then UtilitiesCS.Test\bin\Debug\UtilitiesCS.Test.dll /InIsolation "/Logger:trx;LogFileName=p5-t10.trx" /ResultsDirectory:coverage/trx/p5-t10 /Blame:CollectHangDump;TestTimeout=4min;HangDumpType=None "/TestCaseFilter:FullyQualifiedName~StackGeek_Tests|FullyQualifiedName~PrettyPrint_Tests|FullyQualifiedName~DASLFilterParserTests|FullyQualifiedName~OlTableExtensions_Tests|FullyQualifiedName~NLogTraceWriter_Test|FullyQualifiedName~Triage_OlLogicTests"
+EXIT_CODE: 0
+
+## Build
+
+| Observation | Value |
+|---|---|
+| Build exit code | `0` |
+| Count of lines exactly equal to ` 0 Error(s)` | `1` |
+| Warning count | `0` (baseline ceiling from P0-T8 is 0) |
+| Diagnostic lines matching `: (warning\|error) ` | none |
+
+```
+ 0 Warning(s)
+ 0 Error(s)
+```
+
+This proves the two out-of-project callers of the seamed members compile unchanged:
+`TaskMaster/Ribbon/TryFunctionalityInConstruction.cs:51,66` (which calls `EnumerateTable()` and
+`df.PrettyPrint()`) and
+`UtilitiesCS/EmailIntelligence/ClassifierGroups/Triage/Triage_OlLogic.cs:74` (which calls
+`parser.PrintTree(logicTree, 0)`). All three new parameters are optional and trailing, so the
+existing call sites bind to the same members with the writer defaulted to `Console.Out`.
+
+## D10 substitution decision
+
+D10_SUBSTITUTION_TRIGGERED: False
+
+The build log contains no `CS0103` or `CS0246` diagnostic naming `PrettyPrint.cs`, so the D10
+conditional substitution did not apply: `using Svg;` stays deleted and
+`using System.Windows.Input;` is retained. The three removals chosen by D10
+(`using System.Threading.Tasks;` at old line 6, the duplicate `using System.Text;` at old line 12,
+and `using Svg;` at old line 18) were each verified by a token probe before deletion — `Task`
+occurred only on its own using line, `Svg` only on its own using line, and `System.Text` appeared
+at both line 5 and line 12 — and all three were confirmed unused by this clean build.
+
+PRETTYPRINT_LINECOUNT: 680
+
+`PrettyPrint.cs` is exactly 680 lines, unchanged from baseline. The zero-net-growth budget held:
+`+1` for `using System.IO;`, `+2` for the two overloads wrapping to two lines each under
+CSharpier's 100-column width, and `-3` for the three removals.
+
+## Test run
+
+| Observation | Value |
+|---|---|
+| Test run exit code | `0` |
+
+```
+total=139 executed=139 passed=139 failed=0 error=0 timeout=0 aborted=0 notExecuted=0
+```
+
+`failed` = 0. No result carried an outcome other than `Passed`.
+
+## The six named outcomes
+
+| Method | Outcome |
+|---|---|
+| `PrintTree_WritesIndentedTreeToSuppliedWriter` (renamed) | `Passed` |
+| `DataFramePrettyHelpers_RenderRowsMarkdownAndWriterOutput` (renamed) | `Passed` |
+| `EnumerateTable_WritesFormattedOutputToSuppliedWriterAndMovesToStart` (renamed) | `Passed` |
+| `Main_RunsSampleScenarioWithoutThrowing` | `Passed` |
+| `Run_WritesScenarioToSuppliedWriter` (new) | `Passed` |
+| `PrettyPrint_NullWriter_WritesToConsoleWithoutThrowing` (new) | `Passed` |
+
+None returned `ABSENT`. `Triage_OlLogicTests` is included in the filter because
+`Triage_OlLogicTests.cs:142` already exercises the null-writer `PrintTree` path, which is why no
+extra null-writer test was added for DASL; it passed as part of the 139.
+
+## Console.SetOut elimination
+
+CONSOLE_SETOUT_TOTAL: 0
+
+`Select-String -SimpleMatch 'Console.SetOut'` over the five test files this phase touched
+(`StackGeek_Tests.cs`, `PrettyPrint_Tests.cs`, `DASLFilterParserTests.cs`,
+`OlTableExtensions_Tests.cs`, `NLogTraceWriter_Test.cs`) totals 0. Every capture-and-assert site
+now writes to a test-owned `StringWriter` passed as an argument, so no test in this assembly
+depends on process-wide `Console.Out`, and the roughly 24 unrestored `DebugTextWriter` aggressors
+can no longer break any assertion.
+
+Three of the four victim classes lost `[DoNotParallelize]` entirely
+(`StackGeek_Tests`, `PrettyPrint_Tests`, `DASLFilterParserTests`), so the AC4 ten-run gate now
+exercises the seam rather than the serialization stopgap. `OlTableExtensions_Tests` retains the
+attribute under D8 and spec Mitigation 4, with its comment rewritten so it no longer claims the
+console as the reason; that retention is filed as a follow-up in P8-T9.
+
+`NLogTraceWriter_Test`, the propagator, lost its `Console.Out` save/install/restore altogether: it
+asserts on a mock logger and never on console text, so the `DebugTextWriter` install had no purpose
+and its `[TestCleanup]` could install a foreign writer as the process-wide `Console.Out` for the
+remainder of the run.
+
+## Acceptance evaluation
+
+- Build exit 0 with ` 0 Error(s)`. PASS
+- Test `EXIT_CODE: 0`. PASS
+- `failed` = 0. PASS
+- `Read-TrxOutcome` is `Passed` for all six named tests. PASS
+- `Select-String -SimpleMatch 'Console.SetOut'` over the five test files totals 0. PASS
+
+## Output Summary
+
+Full-solution rebuild clean (0 warnings, 0 errors) and 139 tests across the six filtered classes
+all passing. The four `TextWriter` seams are in place, the shared-console dependency is eliminated
+from every capture-and-assert site, and `PrettyPrint.cs` held at exactly 680 lines with no D10
+substitution required.
diff --git a/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/regression-testing/p6-t5-etl-clock-pass.md b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/regression-testing/p6-t5-etl-clock-pass.md
new file mode 100644
index 000000000..333879e03
--- /dev/null
+++ b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/regression-testing/p6-t5-etl-clock-pass.md
@@ -0,0 +1,89 @@
+# P6-T5 — ETL clock tests and tolerance retirement
+
+Timestamp: 2026-09-08T10-04
+Task: [P6-T5]
+Command: msbuild UtilitiesCS.Test\UtilitiesCS.Test.csproj /t:Build /m /p:Configuration=Debug /p:Platform=AnyCPU /nologo /v:q "/flp:LogFile=coverage/msbuild-p6-t5.log;Verbosity=normal" ; then UtilitiesCS.Test\bin\Debug\UtilitiesCS.Test.dll /InIsolation "/Logger:trx;LogFileName=p6-t5.trx" /ResultsDirectory:coverage/trx/p6-t5 /Blame:CollectHangDump;TestTimeout=4min;HangDumpType=None "/TestCaseFilter:FullyQualifiedName~OlTableExtensions_Tests|FullyQualifiedName~OlTableExtensionsEtlClockTests"
+EXIT_CODE: 0
+
+## Build
+
+| Observation | Value |
+|---|---|
+| Build exit code | `0` |
+| Count of lines exactly equal to ` 0 Error(s)` | `1` |
+| Warning count | `0` |
+
+```
+ 0 Warning(s)
+ 0 Error(s)
+```
+
+## Test run
+
+| Observation | Value |
+|---|---|
+| Test run exit code | `0` |
+
+```
+total=85 executed=85 passed=85 failed=0 error=0 timeout=0 aborted=0 notExecuted=0
+```
+
+`failed` = 0. No result carried an outcome other than `Passed`.
+
+## Named outcomes
+
+| Class | Method | Outcome |
+|---|---|---|
+| `OlTableExtensionsEtlClockTests` | `EtlAsync_DeadlineExpires_ReturnsNullDataAndCancelsTokenSource` | `Passed` |
+| `OlTableExtensionsEtlClockTests` | `EtlAsync_ClockNeverAdvances_ReturnsTransformedRows` | `Passed` |
+| `OlTableExtensions_Tests` | `EtlAsync_WithBinaryAndObjectFieldsAndProgress_ReturnsTransformedData` | `Passed` |
+| `OlTableExtensions_Tests` | `EtlByRowAsync_PrivateHelper_ReturnsConvertedRows` | `Passed` |
+
+None returned `ABSENT`, so the newly registered file was compiled into the assembly and both of
+its tests were discovered.
+
+The third row is the test whose `Returns(120)` timing tolerance P6-T1 retired. It now runs with
+the natural row count of 1, so its deadline is the real 250 ms rather than 30 000 ms, and it is
+kept deterministic by an un-advanced `FakeTimeProvider` instead of by a mock value chosen to
+outrun the deadline. That is the AC5-compliant replacement: the deadline is not widened, it is
+placed under the test's control.
+
+The fourth row is the reflection-binding test named in P6-T2 (its method is
+`EtlByRowAsync_PrivateHelper_ReturnsConvertedRows`, and its `InvokeStaticAsync` call now sits at
+lines 1122-1135). Its passing run is the behavioural confirmation of the static check P6-T2
+recorded: the four-type array still resolves the unchanged four-parameter overload.
+
+## The two new tests
+
+`EtlAsync_DeadlineExpires_ReturnsNullDataAndCancelsTokenSource` documents the surviving `EtlAsync`
+contract rather than changing it: on expiry the `TimeoutException` is swallowed, `data` comes back
+null through the null-forgiving suppression, and `tokenSource` is cancelled. This is the first
+test in the repository to cover the `catch (TimeoutException)` block, which P0-T11 measured as
+entirely uncovered at baseline (lines 117-123 of the pre-change file).
+
+`EtlAsync_ClockNeverAdvances_ReturnsTransformedRows` is the paired green path on an un-advanced
+clock, asserting `columnInfo["Store"]` is 1 and `data[0, 1]` is the injected binary string.
+
+## Blame hang guard
+
+SEQUENCE_FILES: 0
+
+No `Sequence` file under `coverage/trx/p6-t5`, so the 4-minute blame hang timeout did not fire.
+The gated deadline test reached its assertions rather than hanging, which confirms the timer was
+armed before the clock advanced.
+
+## Acceptance evaluation
+
+- Build exit 0 with ` 0 Error(s)`. PASS
+- Test `EXIT_CODE: 0`. PASS
+- `failed` = 0. PASS
+- `Read-TrxOutcome` is `Passed` for both `OlTableExtensionsEtlClockTests` methods, for
+ `EtlAsync_WithBinaryAndObjectFieldsAndProgress_ReturnsTransformedData`, and for the reflection
+ test, whose method name is recorded above as `EtlByRowAsync_PrivateHelper_ReturnsConvertedRows`.
+ PASS
+
+## Output Summary
+
+85 tests across the two table classes, all passed, exit 0. The `Returns(120)` timing tolerance is
+retired and replaced by a controlled clock, and the previously uncovered ETL deadline-expiry path
+now has a deterministic test.
diff --git a/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/regression-testing/p8-t1-ac4-runs.md b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/regression-testing/p8-t1-ac4-runs.md
new file mode 100644
index 000000000..d9aee31e5
--- /dev/null
+++ b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/regression-testing/p8-t1-ac4-runs.md
@@ -0,0 +1,70 @@
+# P8-T1 — AC4 runs 1 and 2
+
+Timestamp: 2026-09-08T10-26
+Task: [P8-T1]
+Command: /EnableCodeCoverage /InIsolation "/Logger:trx;LogFileName=p8-t1-run.trx" /ResultsDirectory:coverage/trx/p8-t1 /Blame:CollectHangDump;TestTimeout=4min;HangDumpType=None "/TestCaseFilter:TestCategory!=LiveOutlook&FullyQualifiedName!~ShellUtilities&FullyQualifiedName!~SysImageListHelper&FullyQualifiedName!~OSBrowser"
+EXIT_CODE: 0
+EXIT_CODE (run 1): 0
+
+AC4_COMMAND_SHAPE: CI-VERBATIM, read from `p0-t12-ci-shape-probe.md`. No `/Settings:` argument is
+passed, so parallelism comes only from `[assembly: Parallelize(Workers = 0, Scope = ClassLevel)]`
+at `UtilitiesCS.Test/Properties/AssemblyInfo.cs:18-21`, exactly as in CI.
+
+## Tree state
+
+| Observation | Value |
+|---|---|
+| `git rev-parse HEAD` | `03b7bd57cacfc902a8b9f4e917ace627ffcac464` |
+| Equals `SOURCE-HEAD` from P7-T12 | `True` |
+| `git status --porcelain -- "*.cs" "*.csproj"` entry count | 0 |
+
+The ten runs are consecutive on one unchanged tree; no source file is edited between them.
+
+## Counters
+
+| Run | total | executed | passed | failed | error | aborted | timeout | notExecuted | seconds |
+|---|---|---|---|---|---|---|---|---|---|
+| 1 | 7162 | 7162 | 7162 | 0 | 0 | 0 | 0 | 0 | 70.9 |
+| 2 | 7162 | 7162 | 7162 | 0 | 0 | 0 | 0 | 0 | 54.2 |
+
+Both `total` values equal the P7-T5 `total` of 7162. Both exit codes are 0. No result in either run
+carried an outcome other than `Passed`.
+
+## Non-vacuity control (run 1)
+
+All nine C4 names read `Passed` from `p8-t1-run1.trx`. An unregistered or undiscovered test would
+read `ABSENT`, which would fail this clause; none did.
+
+| Test | Outcome |
+|---|---|
+| `TryAddValuesAsync_PreCancelledToken_ThrowsTaskCanceledAndLeavesValueUnchanged` | `Passed` |
+| `GetEmailDataInViewAsync_EtlDeadlineExpires_ThrowsInvalidOperationNamingFolder` | `Passed` |
+| `GetEmailDataInViewAsync_ClockNeverAdvances_ReturnsOneRowFrame` | `Passed` |
+| `EtlAsync_DeadlineExpires_ReturnsNullDataAndCancelsTokenSource` | `Passed` |
+| `EtlAsync_ClockNeverAdvances_ReturnsTransformedRows` | `Passed` |
+| `Run_WritesScenarioToSuppliedWriter` | `Passed` |
+| `PrettyPrint_NullWriter_WritesToConsoleWithoutThrowing` | `Passed` |
+| `TryAddValuesAsync_UpdatesExistingValue` | `Passed` |
+| `GetEmailDataInViewAsync_SeparatesTableSnapshotFromDataFrameTransform` | `Passed` |
+
+## `.coverage` cleanup
+
+4 `*.coverage` files found under `coverage/trx/p8-t1` (two per run), all deleted after the counters
+were read. 0 remain. They are large binaries and are never committed.
+
+## Local worker count
+
+`[Environment]::ProcessorCount` is 24, which is what `Workers = 0` resolves to on this workstation.
+
+## Acceptance evaluation
+
+- Both TRX files exist. PASS
+- Both runs report `failed`=0, `error`=0, `aborted`=0, `timeout`=0, and `executed`=`total`. PASS
+- Both `total` values equal the P7-T5 `total` (7162). PASS
+- Both exit codes are 0. PASS
+- `Read-TrxOutcome` on `p8-t1-run1.trx` is `Passed` for all nine C4 names. PASS
+
+## Output Summary
+
+AC4 runs 1 and 2 of 10: 7162 tests each, all passed, both exit 0, 70.9 s and 54.2 s. The
+non-vacuity control confirms all nine tracked tests were discovered and executed.
diff --git a/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/regression-testing/p8-t2-ac4-runs.md b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/regression-testing/p8-t2-ac4-runs.md
new file mode 100644
index 000000000..b7ed210d7
--- /dev/null
+++ b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/regression-testing/p8-t2-ac4-runs.md
@@ -0,0 +1,42 @@
+# P8-T2 — AC4 runs 3 and 4
+
+Timestamp: 2026-09-08T10-29
+Task: [P8-T2]
+Command: /EnableCodeCoverage /InIsolation "/Logger:trx;LogFileName=p8-t2-run.trx" /ResultsDirectory:coverage/trx/p8-t2 /Blame:CollectHangDump;TestTimeout=4min;HangDumpType=None "/TestCaseFilter:TestCategory!=LiveOutlook&FullyQualifiedName!~ShellUtilities&FullyQualifiedName!~SysImageListHelper&FullyQualifiedName!~OSBrowser"
+EXIT_CODE: 0
+EXIT_CODE (run 3): 0
+
+AC4_COMMAND_SHAPE: CI-VERBATIM. No `/Settings:` argument.
+
+## Tree state
+
+| Observation | Value |
+|---|---|
+| `git rev-parse HEAD` | `03b7bd57cacfc902a8b9f4e917ace627ffcac464` |
+| Equals `SOURCE-HEAD` from P7-T12 | `True` |
+| `git status --porcelain -- "*.cs" "*.csproj"` entry count | 0 |
+
+## Counters
+
+| Run | total | executed | passed | failed | error | aborted | timeout | notExecuted | seconds |
+|---|---|---|---|---|---|---|---|---|---|
+| 3 | 7162 | 7162 | 7162 | 0 | 0 | 0 | 0 | 0 | 55.0 |
+| 4 | 7162 | 7162 | 7162 | 0 | 0 | 0 | 0 | 0 | 51.9 |
+
+Both `total` values equal the P7-T5 `total` of 7162. Both exit codes are 0. No result carried an
+outcome other than `Passed`.
+
+## `.coverage` cleanup
+
+4 files found, all deleted, 0 remain.
+
+## Acceptance evaluation
+
+- Both TRX files exist. PASS
+- Both runs report `failed`=0, `error`=0, `aborted`=0, `timeout`=0, `executed`=`total`. PASS
+- Both `total` values equal the P7-T5 `total`. PASS
+- Both exit codes are 0. PASS
+
+## Output Summary
+
+AC4 runs 3 and 4 of 10: 7162 tests each, all passed, both exit 0, 55.0 s and 51.9 s.
diff --git a/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/regression-testing/p8-t3-ac4-runs.md b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/regression-testing/p8-t3-ac4-runs.md
new file mode 100644
index 000000000..d241c0aae
--- /dev/null
+++ b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/regression-testing/p8-t3-ac4-runs.md
@@ -0,0 +1,42 @@
+# P8-T3 — AC4 runs 5 and 6
+
+Timestamp: 2026-09-08T10-31
+Task: [P8-T3]
+Command: /EnableCodeCoverage /InIsolation "/Logger:trx;LogFileName=p8-t3-run.trx" /ResultsDirectory:coverage/trx/p8-t3 /Blame:CollectHangDump;TestTimeout=4min;HangDumpType=None "/TestCaseFilter:TestCategory!=LiveOutlook&FullyQualifiedName!~ShellUtilities&FullyQualifiedName!~SysImageListHelper&FullyQualifiedName!~OSBrowser"
+EXIT_CODE: 0
+EXIT_CODE (run 5): 0
+
+AC4_COMMAND_SHAPE: CI-VERBATIM. No `/Settings:` argument.
+
+## Tree state
+
+| Observation | Value |
+|---|---|
+| `git rev-parse HEAD` | `03b7bd57cacfc902a8b9f4e917ace627ffcac464` |
+| Equals `SOURCE-HEAD` from P7-T12 | `True` |
+| `git status --porcelain -- "*.cs" "*.csproj"` entry count | 0 |
+
+## Counters
+
+| Run | total | executed | passed | failed | error | aborted | timeout | notExecuted | seconds |
+|---|---|---|---|---|---|---|---|---|---|
+| 5 | 7162 | 7162 | 7162 | 0 | 0 | 0 | 0 | 0 | 51.3 |
+| 6 | 7162 | 7162 | 7162 | 0 | 0 | 0 | 0 | 0 | 70.7 |
+
+Both `total` values equal the P7-T5 `total` of 7162. Both exit codes are 0. No result carried an
+outcome other than `Passed`.
+
+## `.coverage` cleanup
+
+4 files found, all deleted, 0 remain.
+
+## Acceptance evaluation
+
+- Both TRX files exist. PASS
+- Both runs report `failed`=0, `error`=0, `aborted`=0, `timeout`=0, `executed`=`total`. PASS
+- Both `total` values equal the P7-T5 `total`. PASS
+- Both exit codes are 0. PASS
+
+## Output Summary
+
+AC4 runs 5 and 6 of 10: 7162 tests each, all passed, both exit 0, 51.3 s and 70.7 s.
diff --git a/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/regression-testing/p8-t4-ac4-runs.md b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/regression-testing/p8-t4-ac4-runs.md
new file mode 100644
index 000000000..86e5749b0
--- /dev/null
+++ b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/regression-testing/p8-t4-ac4-runs.md
@@ -0,0 +1,126 @@
+# P8-T4 — AC4 runs 7 and 8
+
+Timestamp: 2026-09-08T10-34
+Task: [P8-T4]
+Command: /EnableCodeCoverage /InIsolation "/Logger:trx;LogFileName=p8-t4-run.trx" /ResultsDirectory:coverage/trx/p8-t4 /Blame:CollectHangDump;TestTimeout=4min;HangDumpType=None "/TestCaseFilter:TestCategory!=LiveOutlook&FullyQualifiedName!~ShellUtilities&FullyQualifiedName!~SysImageListHelper&FullyQualifiedName!~OSBrowser"
+EXIT_CODE: 0
+EXIT_CODE (run 7): 1
+
+## VERDICT: ACCEPTANCE NOT MET
+
+Run 7 reported one failing test. The pair acceptance requires `failed`=0 on both runs, so this
+task does not pass and is left unchecked in the plan. No re-run was performed: re-running until
+green is what AC4 exists to prevent, and doing it here would destroy the evidence.
+
+## Tree state
+
+| Observation | Value |
+|---|---|
+| `git rev-parse HEAD` | `03b7bd57cacfc902a8b9f4e917ace627ffcac464` |
+| Equals `SOURCE-HEAD` from P7-T12 | `True` |
+| `git status --porcelain -- "*.cs" "*.csproj"` entry count | 0 |
+
+The tree is unchanged from the source commit, so the failure is not attributable to an edit
+between runs.
+
+## Counters
+
+| Run | total | executed | passed | failed | error | aborted | timeout | notExecuted | seconds |
+|---|---|---|---|---|---|---|---|---|---|
+| 7 | 7162 | 7162 | 7161 | **1** | 0 | 0 | 0 | 0 | 73.9 |
+| 8 | 7162 | 7162 | 7162 | 0 | 0 | 0 | 0 | 0 | 69.4 |
+
+## The failing test
+
+```
+UtilitiesCS.Test.NewtonsoftHelpers.SDILReader.MethodBodyReader_Tests.GetBodyCode_ReturnsConcatenatedInstructions
+```
+
+Failure message, verbatim after redaction:
+
+```
+Expected bodyCode "0000 : nop
+0001 : 1879067923
+0006 : stloc.0
+0007 : br.s 0009
+0009 : ldloc.0
+0010 : ret
+" to contain "ldstr".
+ at UtilitiesCS.Test.NewtonsoftHelpers.SDILReader.MethodBodyReader_Tests.GetBodyCode_ReturnsConcatenatedInstructions() in \UtilitiesCS.Test\NewtonsoftHelpers\SDILReader\MethodBodyReader_Tests.cs:line 74
+```
+
+Duration 0.12 s. All nine C4 names still read `Passed` on this run.
+
+## This is a different defect, in files outside the write set
+
+The failing test is not one of the nine C4 names, and it is not one of the three failure modes
+this item repairs. Neither the test file
+(`UtilitiesCS.Test/NewtonsoftHelpers/SDILReader/MethodBodyReader_Tests.cs`) nor the production
+file it exercises (`UtilitiesCS/NewtonsoftHelpers/SDIL Reader/MethodBodyReader.cs` and
+`ILGlobals.cs`) appears anywhere in the 20-path write set, and neither was touched by this change.
+
+## Root cause, established by reading
+
+`ILGlobals` holds unsynchronised process-wide mutable statics:
+
+```
+UtilitiesCS/NewtonsoftHelpers/SDIL Reader/ILGlobals.cs:112 public static Dictionary Cache = new Dictionary();
+UtilitiesCS/NewtonsoftHelpers/SDIL Reader/ILGlobals.cs:117 public static OpCode[] multiByteOpCodes = null!;
+UtilitiesCS/NewtonsoftHelpers/SDIL Reader/ILGlobals.cs:118 public static OpCode[] singleByteOpCodes = null!;
+UtilitiesCS/NewtonsoftHelpers/SDIL Reader/ILGlobals.cs:121 public static void LoadOpCodes()
+UtilitiesCS/NewtonsoftHelpers/SDIL Reader/ILGlobals.cs:123 singleByteOpCodes = new OpCode[0x100];
+```
+
+`LoadOpCodes()` first reassigns `singleByteOpCodes` to a freshly allocated, all-default array and
+then fills it in a loop. There is no lock, no `Lazy`, and no `volatile`.
+
+Two test classes call it, and **neither carries `[DoNotParallelize]`**, so under the assembly's
+class-level parallel scope they can execute concurrently on different workers:
+
+- `UtilitiesCS.Test/NewtonsoftHelpers/SDILReader/ILGlobals_Tests.cs` calls `ILGlobals.LoadOpCodes()`
+ at lines 15, 26, 37 and 47.
+- `UtilitiesCS.Test/NewtonsoftHelpers/SDILReader/MethodBodyReader_Tests.cs` calls it at line 364.
+
+When `MethodBodyReader.ConstructInstructions` reads
+`ILGlobals.singleByteOpCodes[(int)value]` (line 105) while a sibling class is between the
+reassignment and the fill, it receives `default(OpCode)`. `default(OpCode)` has an empty `Name`
+and `OperandType` 0, which is `InlineBrTarget`, so the switch at line 119 takes the branch
+`metadataToken = ReadInt32(...); metadataToken += position; instruction.Operand = metadataToken;`
+and stores a raw integer instead of resolving the string through
+`module.ResolveString(metadataToken)` at line 192.
+
+The observed output is exactly that signature: the line reads `0001 : 1879067923` — two spaces
+where the opcode name should be (the empty `Name` of `default(OpCode)`) followed by a raw token
+value rather than `ldstr "hello"`.
+
+## Attribution
+
+This is a **pre-existing latent race**, not one introduced by this change:
+
+- Both racing classes were already parallel-eligible before this change; neither is in the write
+ set and neither gained or lost a `[DoNotParallelize]` attribute here.
+- The static state involved is `ILGlobals`, which this change does not touch.
+
+Stated conservatively: this change did move three classes (`StackGeek_Tests`, `PrettyPrint_Tests`,
+`DASLFilterParserTests`) from the sequential phase into the parallel set, which raises the parallel
+density of the run and can therefore alter the probability that any latent race surfaces. It does
+not create this race, and it does not make these two classes newly concurrent with each other.
+
+The defect was not observed in the P0-T10 or P0-T12 baseline runs, but those are two single runs of
+an intermittently failing test and do not establish absence.
+
+## Acceptance evaluation
+
+- Both TRX files exist. PASS
+- Both runs report `failed`=0. **FAIL** — run 7 reported `failed`=1.
+- Both `total` values equal the P7-T5 `total` (7162). PASS
+- Both exit codes are 0. **FAIL** — run 7 exited 1.
+
+## Output Summary
+
+AC4 runs 7 and 8 of 10. Run 8 was clean; run 7 failed one test,
+`MethodBodyReader_Tests.GetBodyCode_ReturnsConcatenatedInstructions`, through an unsynchronised
+static-state race in `ILGlobals` between two test classes that are both outside this item's write
+set. The task's acceptance is not met and it remains unchecked. Consequence: the AC4 ten-run gate
+cannot report zero failures on ten consecutive runs, so AC4 is not checked off. Recorded for
+follow-up triage.
diff --git a/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/regression-testing/p8-t5-ac4-runs.md b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/regression-testing/p8-t5-ac4-runs.md
new file mode 100644
index 000000000..da548d495
--- /dev/null
+++ b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/regression-testing/p8-t5-ac4-runs.md
@@ -0,0 +1,43 @@
+# P8-T5 — AC4 runs 9 and 10
+
+Timestamp: 2026-09-08T10-39
+Task: [P8-T5]
+Command: /EnableCodeCoverage /InIsolation "/Logger:trx;LogFileName=p8-t5-run.trx" /ResultsDirectory:coverage/trx/p8-t5 /Blame:CollectHangDump;TestTimeout=4min;HangDumpType=None "/TestCaseFilter:TestCategory!=LiveOutlook&FullyQualifiedName!~ShellUtilities&FullyQualifiedName!~SysImageListHelper&FullyQualifiedName!~OSBrowser"
+EXIT_CODE: 0
+EXIT_CODE (run 9): 0
+
+AC4_COMMAND_SHAPE: CI-VERBATIM. No `/Settings:` argument.
+
+## Tree state
+
+| Observation | Value |
+|---|---|
+| `git rev-parse HEAD` | `03b7bd57cacfc902a8b9f4e917ace627ffcac464` |
+| Equals `SOURCE-HEAD` from P7-T12 | `True` |
+| `git status --porcelain -- "*.cs" "*.csproj"` entry count | 0 |
+
+## Counters
+
+| Run | total | executed | passed | failed | error | aborted | timeout | notExecuted | seconds |
+|---|---|---|---|---|---|---|---|---|---|
+| 9 | 7162 | 7162 | 7162 | 0 | 0 | 0 | 0 | 0 | 54.2 |
+| 10 | 7162 | 7162 | 7162 | 0 | 0 | 0 | 0 | 0 | 58.8 |
+
+Both `total` values equal the P7-T5 `total` of 7162. Both exit codes are 0. No result carried an
+outcome other than `Passed`.
+
+## `.coverage` cleanup
+
+4 files found, all deleted, 0 remain.
+
+## Acceptance evaluation
+
+- Both TRX files exist. PASS
+- Both runs report `failed`=0, `error`=0, `aborted`=0, `timeout`=0, `executed`=`total`. PASS
+- Both `total` values equal the P7-T5 `total`. PASS
+- Both exit codes are 0. PASS
+
+## Output Summary
+
+AC4 runs 9 and 10 of 10: 7162 tests each, all passed, both exit 0, 54.2 s and 58.8 s. This pair
+passes its own acceptance; the ten-run gate as a whole does not, because run 7 (P8-T4) failed.
diff --git a/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/regression-testing/p8-t6-ac4-ten-run.md b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/regression-testing/p8-t6-ac4-ten-run.md
new file mode 100644
index 000000000..1aa02d619
--- /dev/null
+++ b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/evidence/regression-testing/p8-t6-ac4-ten-run.md
@@ -0,0 +1,120 @@
+# P8-T6 — AC4 ten-run aggregate
+
+Timestamp: 2026-09-08T10-39
+Task: [P8-T6]
+Command: PROC-TRX `Read-TrxCounters` and `Times/@start`,`@finish` over the ten TRX files under `coverage/trx/p8-t1` through `coverage/trx/p8-t5`
+EXIT_CODE: 0
+
+## VERDICT: 9 of 10 runs clean. AC4 IS NOT SATISFIED.
+
+AC4 requires zero failures on ten consecutive runs. Run 7 reported one failure, so the criterion
+is not met and AC4 is left unchecked in spec.md. P8-T7, the AC4 check-off task, is therefore not
+performed.
+
+## The ten runs
+
+| Run | total | executed | passed | failed | error | aborted | timeout | seconds |
+|---|---|---|---|---|---|---|---|---|
+| 1 | 7162 | 7162 | 7162 | 0 | 0 | 0 | 0 | 70.9 |
+| 2 | 7162 | 7162 | 7162 | 0 | 0 | 0 | 0 | 54.2 |
+| 3 | 7162 | 7162 | 7162 | 0 | 0 | 0 | 0 | 55.0 |
+| 4 | 7162 | 7162 | 7162 | 0 | 0 | 0 | 0 | 51.9 |
+| 5 | 7162 | 7162 | 7162 | 0 | 0 | 0 | 0 | 51.3 |
+| 6 | 7162 | 7162 | 7162 | 0 | 0 | 0 | 0 | 70.7 |
+| 7 | 7162 | 7162 | **7161** | **1** | 0 | 0 | 0 | 73.9 |
+| 8 | 7162 | 7162 | 7162 | 0 | 0 | 0 | 0 | 69.4 |
+| 9 | 7162 | 7162 | 7162 | 0 | 0 | 0 | 0 | 54.2 |
+| 10 | 7162 | 7162 | 7162 | 0 | 0 | 0 | 0 | 58.8 |
+
+Exactly ten rows. All ten `total` values are identical at 7162, matching the P7-T5 `total`.
+Wall-clock times range from 51.3 s to 73.9 s. Total gate duration about 10 minutes.
+
+## The one failure
+
+Run 7 (`coverage/trx/p8-t4/p8-t4-run1.trx`):
+
+```
+UtilitiesCS.Test.NewtonsoftHelpers.SDILReader.MethodBodyReader_Tests.GetBodyCode_ReturnsConcatenatedInstructions
+Expected bodyCode "0000 : nop
+0001 : 1879067923
+...
+" to contain "ldstr".
+```
+
+It is caused by an unsynchronised process-wide static in
+`UtilitiesCS/NewtonsoftHelpers/SDIL Reader/ILGlobals.cs`: `LoadOpCodes()` reassigns
+`singleByteOpCodes` to a fresh all-default array before filling it, and two test classes that both
+call it (`ILGlobals_Tests` and `MethodBodyReader_Tests`) carry no `[DoNotParallelize]`, so a reader
+can observe the array mid-initialisation. Full derivation in `p8-t4-ac4-runs.md`.
+
+This is a **different defect** from the three this item repairs, in files outside the 20-path write
+set, and it is pre-existing: neither racing class was touched by this change and neither gained or
+lost a parallelism attribute. It is recorded for follow-up triage rather than fixed here, because
+fixing it would require editing files outside the declared write set.
+
+## The three repaired failure modes did not recur
+
+This was checked exhaustively rather than sampled: 13 tracked tests were read from each of the ten
+TRX files by `Read-TrxOutcome`, giving 130 assertions, and **all 130 returned `Passed`**. None
+returned `ABSENT`, `NO-RESULT`, `Failed` or `NotExecuted`. The 13 are the nine C4 names plus the
+four AC3 conversions (`DataFramePrettyHelpers_RenderRowsMarkdownAndWriterOutput`,
+`PrintTree_WritesIndentedTreeToSuppliedWriter`,
+`EnumerateTable_WritesFormattedOutputToSuppliedWriterAndMovesToStart`,
+`Main_RunsSampleScenarioWithoutThrowing`).
+
+Across all ten runs, every one of the nine C4 names read `Passed`, including the two sentinels that
+motivated this item:
+
+- `DictionaryExtensions_Tests.TryAddValuesAsync_UpdatesExistingValue` (#780)
+- `DfDeedle_COM_Tests.GetEmailDataInViewAsync_SeparatesTableSnapshotFromDataFrameTransform` (#803, #594 item 1)
+
+and the four `Console.Out` victim tests (#594 items 2 and 3), three of which now run without
+`[DoNotParallelize]`. On the evidence of ten consecutive runs, the three defects this change
+targets did not recur.
+
+## Provenance and load
+
+| Observation | Value |
+|---|---|
+| `SOURCE-HEAD` recorded by all five pair tasks | `03b7bd57cacfc902a8b9f4e917ace627ffcac464` (identical in all five) |
+| `AC4_COMMAND_SHAPE` | CI-VERBATIM |
+| Local worker count (`[Environment]::ProcessorCount`) | 24 |
+
+`Workers = 0` in `UtilitiesCS.Test/Properties/AssemblyInfo.cs:18-21` resolves to the processor
+count, so these runs used 24 class-level workers.
+
+## Filter deviation, stated honestly
+
+The filter used is:
+
+```
+TestCategory!=LiveOutlook&FullyQualifiedName!~ShellUtilities&FullyQualifiedName!~SysImageListHelper&FullyQualifiedName!~OSBrowser
+```
+
+The three `FullyQualifiedName!~` clauses beyond `TestCategory!=LiveOutlook` are a documented local
+environmental exclusion, not a property of this change: one of the 23 shell-icon tests fails per
+run on this workstation with an invalid Win32 icon handle, independently of this fix, per the
+`SHELL_ICON_EXCLUSION: REQUIRED` verdict in the #798 baseline evidence. Those classes are covered
+by CI, which runs them unfiltered.
+
+The CI run on the pull request supplies the unfiltered single-run form. One CI run is not ten, and
+ten local runs are not the unfiltered form; both facts are stated here because neither alone
+discharges AC4.
+
+## Acceptance evaluation
+
+- Exactly ten rows. PASS
+- Every `failed`, `error`, `aborted`, `timeout` is 0. **FAIL** — run 7 has `failed`=1.
+- Every `passed` equals its `total`. **FAIL** — run 7 has `passed`=7161, `total`=7162.
+- All ten `total` values are identical and equal the P7-T5 `total`. PASS
+- The five `SOURCE-HEAD` values are identical. PASS
+- The artifact contains no account name, machine name or absolute path. Verified by P8-T8. PASS
+- `Timestamp:`, `Command:`, `EXIT_CODE:`, `Output Summary:` present. PASS
+
+## Output Summary
+
+Ten consecutive full nine-assembly `/InIsolation` runs on the unchanged source commit
+`03b7bd57`, 7162 tests each, 24 workers. Nine runs were completely clean; run 7 failed one test
+through a pre-existing unsynchronised-static race in `ILGlobals`, which lies outside this item's
+write set. The three defects this item repairs did not recur in any of the ten runs. AC4's literal
+condition of zero failures on ten consecutive runs is not met, so AC4 remains unchecked.
diff --git a/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/feature-audit.2026-09-08T11-30.md b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/feature-audit.2026-09-08T11-30.md
new file mode 100644
index 000000000..48617a897
--- /dev/null
+++ b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/feature-audit.2026-09-08T11-30.md
@@ -0,0 +1,254 @@
+# Feature Audit — utilitiescs-test-determinism (Issue #811)
+
+- Date: 2026-09-08T11-30
+- Branch: `bug/utilitiescs-test-determinism-780-803-594-811`
+- Head: `3805ca89f0c72e2bb448726695f18f420bd260f7`
+- Base: `bb1c7d4b60f7b782227956f36859314d5c47bb03`
+- Work mode: `full-bug` (marker at `issue.md` line 12)
+- Acceptance-criteria source: **`spec.md` only**, lines 292-296
+
+Blocking findings in this artifact: **0**. (The branch total is 1, recorded as F-1 in
+`policy-audit.2026-09-08T11-30.md`.)
+
+## AC Source Resolution
+
+`issue.md` line 12 records `- Work Mode: full-bug`. Per
+`.claude/skills/acceptance-criteria-tracking/SKILL.md`, `full-bug` resolves the acceptance-criteria
+source to `spec.md` only. `issue.md` lines 67-71 carry an AC-shaped checkbox list with identical
+wording; it is **not** the authority under this work mode and was not audited as such. No
+`user-story.md` exists, which is correct for `full-bug`.
+
+`spec.md` has exactly five criteria under `## Acceptance Criteria` at lines 292-296.
+
+## Acceptance Criteria Evaluation
+
+| AC | Criterion (abbreviated) | Verdict | Checkbox state in `spec.md` |
+|---|---|---|---|
+| AC1 | `TryAddValuesAsync` no longer cancels on a fixed wall-clock window; the test passes deterministically under 24-worker parallel coverage runs | PASS | `[x]` — correct |
+| AC2 | `DfDeedle_COM_Tests` no longer mutates process-wide static seams another class can observe; the null snapshot element is guarded with a descriptive failure | PASS | `[x]` — correct |
+| AC3 | The `Console.Out` races are removed by eliminating the shared-console dependency | PASS | `[x]` — correct |
+| AC4 | A full nine-assembly `/InIsolation` run with `TestCategory!=LiveOutlook` reports zero failures on ten consecutive runs, recorded as evidence | **FAIL** | `[ ]` — correct |
+| AC5 | No test is stabilized by a sleep, a retry, or a timing tolerance | PASS | `[x]` — correct |
+
+No checkbox required a change. Every criterion this reviewer evaluated PASS was already checked;
+the one criterion evaluated FAIL was already unchecked. This reviewer made no edit to `spec.md`.
+
+### AC1 — PASS
+
+Both halves verified independently against the post-change source rather than against the executor's
+report.
+
+Mechanism: `UtilitiesCS/Extensions/DictionaryExtensions.cs` no longer creates a linked token source
+and no longer calls `CancelAfter(500)`. The body is now
+`return await Task.Run(() => dictionary.TryAddValues(key, value), token);`, so cancellation is
+governed solely by the caller's token. No `TimeProvider` was needed because the window guarded
+nothing: a token supplied to `Task.Run` can only cancel work that has not yet started, and the body
+is a bounded compare-and-swap loop that performs no I/O.
+
+Blast radius verified: a repository-wide search over `*.cs` for `TryAddValuesAsync` returns four
+hits — the definition at `DictionaryExtensions.cs:169`, one invocation at
+`DictionaryExtensions_Tests.cs:244`, one at `:266`, and two test method names. There are **zero
+production call sites**, so the deletion cannot alter production behavior. The spec's claim on this
+point is confirmed rather than assumed.
+
+Determinism verified: `DictionaryExtensions_Tests.TryAddValuesAsync_UpdatesExistingValue` — the
+sentinel test named in issue #780 — read `Passed` in all ten AC4 runs under 24 class-level workers
+with coverage instrumentation (`evidence/regression-testing/p8-t6-ac4-ten-run.md`, exhaustive
+per-run check, not a sample).
+
+Surviving contract locked: the new
+`TryAddValuesAsync_PreCancelledToken_ThrowsTaskCanceledAndLeavesValueUnchanged` asserts that a
+pre-cancelled caller token still produces `TaskCanceledException` and leaves the dictionary value
+untouched, so a future edit cannot silently drop cancellation altogether.
+
+RED-first note: `spec.md` states, and this reviewer accepts, that AC1 admits no deterministic
+fail-before test — the original failure is load-dependent and reproducing it would require a sleep,
+which AC5 forbids. The RED artifact for AC1 is therefore the contract-shape test rather than a
+reproduction, and the spec says so in advance rather than after the fact.
+
+### AC2 — PASS
+
+**Clause 1, static-seam mutation.** The two ETL delegate statics are gone.
+`DfDeedle.TableEtlInvoker` and `DfDeedle.StoreTableEtlInvoker` are replaced by a single
+`private static readonly DefaultTableEtl` plus an optional `etl` parameter on
+`GetEmailDataInView(Explorer, ...)` and both `FromDefaultFolder` overloads. The three tests in
+`DfDeedle_COM_Tests` that previously swapped and restored those statics now pass `etl:` directly and
+their `try`/`finally` blocks are deleted. Mutation of process-wide ETL state by that class is
+eliminated, not merely restored.
+
+`DfDeedle.MessageBoxInvoker` survives as a mutated static in four tests. `spec.md` line 135 settles
+this deliberately, and this reviewer verified the exposure argument rather than accepting it: the
+only production readers are `DfDeedle.QfcColumns.cs:27,170,194`, and a repository-wide search shows
+exactly three test classes reach those paths — `DfDeedle_COM_Tests` itself (whose tests are
+serialised by `ExecutionScope.ClassLevel`), `DfDeedleQfcColumnTimeoutTests` and the new
+`DfDeedleEtlTimeoutTests`, both `[DoNotParallelize]`. The criterion's qualifier "in a way another
+class can observe" is therefore satisfied. The residual is that this holds only while both reader
+classes retain their attribute, and nothing enforces that; recorded as C-5 in
+`code-review.2026-09-08T11-30.md`, not as an AC shortfall.
+
+**Clause 2, the null-snapshot guard.** `DfDeedle.GetEmailDataInViewAsync` now tests
+`tableSnapshot.data is null` immediately after the `EtlAsync` call and throws
+`InvalidOperationException` naming the folder and stating that the table ETL timed out or was
+cancelled before returning rows. The guard is placed before the `LogDfTiming` statement that
+dereferences `tableSnapshot.Item1` — the statement the CI stack trace reported — and therefore also
+before the pre-existing `ValidateRequiredEmailColumns` guard, which inspects the column map only and
+would not have caught this.
+
+RED-first evidence exists for AC2 and is the strongest evidence in the item:
+`evidence/regression-testing/p2-t4-ac2-fail-before.md` and
+`fail-before-exception.2026-09-08T09-50.md` record the pre-fix `NullReferenceException`;
+`p3-t2-ac2-pass-after.md` records the post-fix `InvalidOperationException`. The assertion names the
+exception type explicitly and matches the folder name (`WithMessage("*Inbox*")`), so a regression to
+`NullReferenceException` fails the test rather than passing a generic throw assertion.
+
+Sentinel determinism: `GetEmailDataInViewAsync_SeparatesTableSnapshotFromDataFrameTransform` — the
+test named in #803 and #594 — read `Passed` in all ten runs, now driven by an un-advanced
+`FakeTimeProvider` so no deadline on its path can fire under host load.
+
+### AC3 — PASS
+
+The criterion says "two" races; `spec.md` lines 59-64 records the discrepancy and derives four
+capture-and-assert sites twice by two independent search strategies. This reviewer confirmed all
+four are converted in the diff:
+
+| Site | Conversion | `[DoNotParallelize]` |
+|---|---|---|
+| `StackGeek_Tests.Main_RunsSampleScenarioWithoutThrowing` | split into a null-writer default test plus `Run_WritesScenarioToSuppliedWriter` asserting on a `StringWriter` | removed |
+| `PrettyPrint_Tests.DataFramePrettyHelpers_RenderRowsMarkdownAndConsoleOutput` | renamed `...AndWriterOutput`; passes `writer` to both `PrettyPrint` overloads | removed |
+| `DASLFilterParserTests.PrintTree_WritesIndentedTreeToConsole` | renamed `...ToSuppliedWriter`; passes `writer` to `PrintTree` | removed |
+| `OlTableExtensions_Tests.EnumerateTable_WritesFormattedOutputAndMovesToStart` | renamed `...ToSuppliedWriterAndMovesToStart`; passes `output` to `EnumerateTable` | retained, with a corrected rationale |
+
+The fifth site, the propagator `NLogTraceWriter_Test`, had its `Console.Out` save at
+`TestInitialize` and its restoring `TestCleanup` deleted entirely, together with the now-unused
+`originalOut` field. That removes the mechanism by which it could capture a sibling class's writer
+and later install it process-wide.
+
+In every one of the four converted tests, no `Console.SetOut` call and no `Console.Out` read remains.
+The shared-console dependency is eliminated, which is what the criterion asks for, rather than
+serialised.
+
+Retention of the fourth attribute is not a shortfall. `spec.md` Mitigation 4 pre-authorises exactly
+this conservative fallback for `OlTableExtensions_Tests`, on the ground that removing the attribute
+from an 1846-line COM-mock class with ten tests driving a 2000 ms window carries risk unrelated to
+the console. The comment was rewritten so it no longer claims a reason that is now false. The class
+is still converted to the seam, which is the criterion's substance.
+
+Determinism verified: all four converted tests read `Passed` in all ten AC4 runs, three of them now
+running inside the parallel set rather than the serialized tail.
+
+### AC4 — FAIL
+
+The criterion requires zero failures on ten consecutive full nine-assembly `/InIsolation` runs.
+Nine runs were clean. Run 7 reported `passed`=7161 against `total`=7162.
+
+The failing test was
+`UtilitiesCS.Test.NewtonsoftHelpers.SDILReader.MethodBodyReader_Tests.GetBodyCode_ReturnsConcatenatedInstructions`,
+failing on `Expected bodyCode ... to contain "ldstr"`.
+
+This reviewer verified the cause independently by reading the source, and separately corroborated it
+from the failure output. The full derivation, including why the observed single-entry degradation
+and the unresolved `0x70……`-range metadata token uniquely identify a mid-initialisation read of
+`ILGlobals.singleByteOpCodes`, is recorded as F-1 in `policy-audit.2026-09-08T11-30.md`. Summary:
+`ILGlobals.LoadOpCodes()` reassigns the table to an all-default array at line 123 before filling it
+at line 137, with no lock or `Lazy`; two test classes call it and neither carries
+`[DoNotParallelize]`.
+
+Attribution, reached independently and stated in full in section 4.2 of
+`code-review.2026-09-08T11-30.md`: the defect is pre-existing and wholly outside this item. Neither
+racing class is in the write set; neither gained nor lost a parallelism attribute; the vulnerable
+window is byte-identical. The change did move three classes into the parallel set, but instantaneous
+concurrency is capped at 24 workers and the eligible pool already exceeds 400 classes, so the pool
+was worker-saturated before and after; the three short in-memory classes perturb dispatch order
+without raising density. No directional increase in surfacing probability can be established, and
+the evidence does not measure the base rate.
+
+Disposition: **the AC4 shortfall is Non-blocking for merge.** The branch removes three failure modes
+and leaves one; blocking it would leave four in `main`. Fixing `ILGlobals` inside this item would
+breach the minimal-fix requirement of CLAUDE.md's Bugfix Workflow. What is Blocking is that the
+finding has no durable follow-up artifact (F-1).
+
+Process quality note: the executor did not re-run until green. `p8-t4-ac4-runs.md` states "No re-run
+was performed: re-running until green is what AC4 exists to prevent, and doing it here would destroy
+the evidence." That is the correct discipline and is worth recording, because the opposite behavior
+would itself have been a blocking finding.
+
+### AC5 — PASS
+
+The criterion constrains technique, not only outcome. Verified against the diff independently of the
+executor's search:
+
+- Zero `Thread.Sleep`, `Task.Delay`, `CancelAfter`, `SpinWait`, `Stopwatch`, timed `.Wait(n)`,
+ `new CancellationTokenSource()` or `Returns(120)` occurrences among the added lines.
+- Zero `catch` blocks in the two new test files; both use `finally` only, to release gates.
+- The three surviving `.Wait` references (`gateA.Wait`, `gateB.Wait`, `gate.Wait`) are
+ `ManualResetEventSlim.Wait()` **method groups passed as delegate arguments**, invoked inside mock
+ callbacks so the production code blocks until the test itself releases the gate in `finally`. An
+ untimed wait cannot mask a timing defect: it places no bound on how long the code under test may
+ take, and the outcome is decided by the injected clock rather than elapsed wall time. This is the
+ same pattern the pre-existing `DfDeedleQfcColumnTimeoutTests` already uses.
+- The pre-existing tolerance at `OlTableExtensions_Tests.cs:960-963` — `GetRowCount()` returning 120
+ with the comment "so the timeout cannot fire under test-host contention" — is **retired**, not
+ merely absent from added lines. Both tokens count 0 in the final tree; each counted 1 at the base
+ commit. It is replaced by an un-advanced `FakeTimeProvider`.
+
+The distinction that matters here is between widening a deadline and controlling the clock. The
+change consistently does the latter: green-path tests use a clock that never moves, so the deadline
+is unreachable rather than enlarged, and production timing is untouched because a `null`
+`timeProvider` resolves to `TimeProvider.System`.
+
+## Baseline Comparison
+
+| Dimension | Baseline (`bb1c7d4b`) | Head (`3805ca89`) | Direction |
+|---|---|---|---|
+| Repository line coverage | 86.0109% | 86.0424% | improved |
+| Repository branch coverage | 66.3058% | 66.3978% | improved |
+| Uncovered lines, `OlTableExtensions.Etl.cs` | 26 | 12 | improved |
+| Uncovered lines, `DfDeedle.cs` | 2 | 0 | improved |
+| `EtlAsync` method-span line rate | 0.7000 | 0.9796 | improved |
+| Mutable process-wide statics on `DfDeedle` | 3 | 1 | improved |
+| `[DoNotParallelize]` classes among the four console victims | 4 | 1 | improved |
+| Serialized classes, net across the change | n/a | minus 3, plus 2 | net minus 1 |
+| Timing tolerances in the touched tests | 1 | 0 | improved |
+| `DateTime.Now` calls on the ETL log path | 1 | 0 | improved |
+| Known intermittent failure modes in `UtilitiesCS.Test` | 4 (3 in scope, `ILGlobals` latent) | 1 (`ILGlobals`) | improved |
+| Analyzer warnings, full solution | 0 | 0 | held |
+| Nullable / warnings-as-errors diagnostics | 0 | 0 | held |
+| Files over the 500-line cap among the write set | 3 | 3 (all held or shrank) | held |
+| Test total | not recorded at base | 7162 | +9 added tests |
+
+## Deliverables Against `spec.md` Item List
+
+`spec.md` enumerates a 20-item write set (8 production files, 11 test files, 1 project file). The
+diffstat confirms exactly those 20 source paths changed and no others. No file outside the declared
+write set was modified. The three new files listed at items 11, 12 and 14 exist and are wired into
+the project via three `` items, which is required because these are legacy non-SDK
+`packages.config` projects where an unlisted `.cs` file is silently not compiled.
+
+Non-goals held: issue #592 is untouched; the 250 ms per-row budget is unchanged; the 2000 ms
+`GetTableInViewAsync` window is unchanged; the inert `(int, int)` `TimeoutAfter` overloads remain in
+`TimeOutTask.cs`, which is byte-identical at 1011 lines; `EtlAsync`'s tuple contract is unchanged;
+the roughly 24 unrestored `DebugTextWriter` aggressors are untouched. Two of these were filed as
+`docs/features/potential/` follow-up entries.
+
+## Acceptance Criteria Status
+
+```
+### Acceptance Criteria Status
+- Source: docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/spec.md
+- Total AC items: 5
+- Checked off (delivered): 4
+- Remaining (unchecked): 1
+- Items remaining: AC4: A full nine-assembly /InIsolation run with TestCategory!=LiveOutlook reports zero failures on ten consecutive runs, recorded as evidence.
+```
+
+Newly checked off by this reviewer: none. All four PASS criteria were already checked, and the one
+FAIL criterion was already unchecked. The AC state in `spec.md` is accurate as delivered.
+
+## Verdict
+
+**Feature verdict: PASS with one outstanding acceptance criterion and one blocking process finding.**
+
+4 of 5 acceptance criteria are met with verified evidence. AC4 is not met, for a reason this
+reviewer independently attributes to a pre-existing defect outside the write set. The single
+blocking item before merge is F-1: file and promote the `ILGlobals` race so the finding survives the
+merge. Remediation inputs are recorded in `remediation-inputs.2026-09-08T11-30.md`.
diff --git a/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/issue.md b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/issue.md
new file mode 100644
index 000000000..d70847b9e
--- /dev/null
+++ b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/issue.md
@@ -0,0 +1,82 @@
+# utilitiescs-test-determinism-780-803-594 (Issue #811)
+
+- Date captured: 2026-09-07
+- Author: Dan Moisan
+- Status: Promoted -> docs/features/active/utilitiescs-test-determinism-780-803-594/ (Issue #811)
+
+> Automation note: Keep the section headings below unchanged; the promotion tooling maps each of them into the GitHub bug issue template.
+
+- Issue: #811
+- Issue URL: https://github.com/drmoisan/TaskMaster/issues/811
+- Last Updated: 2026-09-08
+- Work Mode: full-bug
+
+## Summary
+
+Consolidates the `UtilitiesCS.Test` nondeterminism filed as #780, #803, and #594 into one item so the required `mstest-coverage` check stops failing on unrelated pull requests. #803 is a duplicate of the `DfDeedle_COM_Tests` `NullReferenceException` already in #594: `GetEmailDataInViewAsync_SeparatesTableSnapshotFromDataFrameTransform` fails under parallel load because the class mutates static seams and production code at `DfDeedle.cs:186` dereferences `tableSnapshot.Item1` without a null check. #780 is `DictionaryExtensions.TryAddValuesAsync` cancelling its inner `Task.Run` after a hard-coded 500 ms wall-clock window, which the thread pool exceeds under coverage instrumentation with 24 class workers. #594 also carries two `Console.Out` races between concurrently executing tests. #592 (QuickFiler pump-host 60 s expiry) is deliberately not included: it is a different assembly and needs its own investigation.
+
+## Environment
+
+- OS/version: Windows 11 Pro 10.0.26200 locally; GitHub Actions windows runner in CI
+- Runtime: .NET Framework 4.8.1 test host, VSTest 18.9.0, MSTest 4.4.0, assembly-level parallelization with 24 class-scope workers
+- Command/flags used: `vstest.console.exe /EnableCodeCoverage /InIsolation /Logger:trx "/TestCaseFilter:TestCategory!=LiveOutlook"`; the repository `mstest-coverage` job
+- Data source or fixture: in-memory dictionaries; mocked `Table` and `MAPIFolder` in `DfDeedle_COM_Tests`
+
+## Steps to Reproduce
+
+1. Run the full suite under coverage with parallel class workers. Observe, intermittently: `TryAddValuesAsync_UpdatesExistingValue` failing with `TaskCanceledException` after ~20 s (passes in ~2 ms alone); `GetEmailDataInViewAsync_SeparatesTableSnapshotFromDataFrameTransform` failing with `NullReferenceException` at `DfDeedle.cs:186`; two `Console.Out` races.
+2. Re-run the identical head commit with no change and observe all checks passing. Observed on PR #802 at `b2132349` (7107 of 7108 passed, then green on re-run) and during PR #779 verification on 2026-09-04.
+
+## Expected Behavior
+
+- Every test in `UtilitiesCS.Test` passes deterministically regardless of class ordering and parallel load.
+- `TryAddValuesAsync` does not fail production callers on thread-pool scheduling latency.
+- Production code at `DfDeedle.cs:186` does not dereference a possibly-null snapshot element.
+- A full nine-assembly `/InIsolation` run reports zero failures on ten consecutive runs.
+
+## Actual Behavior
+
+Intermittent failures as above, each blocking a required check on an unrelated pull request and inviting re-run-until-green.
+
+## Logs / Screenshots
+
+- [x] Attached minimal logs or screenshot
+- Snippet (#780, 2026-09-04): `System.Threading.Tasks.TaskCanceledException: A task was canceled. at UtilitiesCS.DictionaryExtensions.d__10\`2.MoveNext() in UtilitiesCS\Extensions\DictionaryExtensions.cs:line 179`. Local run 4767 tests, 4766 passed, the failing test alone took 21 s.
+- Snippet (#803, PR #802 run 1): `System.NullReferenceException at UtilitiesCS.DfDeedle.GetEmailDataInViewAsync, UtilitiesCS/Extensions/DfDeedle.cs line 186`, Total 7108, Passed 7107, Failed 1.
+
+## Impact / Severity
+
+- [ ] Blocker
+- [ ] High
+- [x] Medium
+- [ ] Low
+
+Medium: a nondeterministic failure in a required check blocks unrelated pull requests and erodes the signal the gate exists to provide. Production callers of `TryAddValuesAsync` may also receive a spurious cancellation under starvation.
+
+## Suspected Cause / Notes
+
+- #780: wall-clock timeout in production code measures scheduling latency, not the operation; the fix is to remove the hard-coded window or inject a `TimeProvider` so tests use `FakeTimeProvider`, per `.claude/rules/csharp.md` time-seam guidance.
+- #803 / #594 item 1: static seam mutation in `DfDeedle_COM_Tests` races other classes; the production null dereference at `DfDeedle.cs:186` turns a race into an NRE. Fix both: make the seam instance-scoped or serialize the class with `[DoNotParallelize]` on a documented basis, and guard the snapshot dereference.
+- #594 items 2 and 3: eliminate the shared-console dependency rather than serializing with sleeps or retries.
+- Superseded issues: #780, #803, #594 (close with a pointer to this issue).
+
+## Proposed Fix / Validation Ideas
+
+Acceptance criteria:
+
+- [ ] AC1: `TryAddValuesAsync` no longer cancels on a fixed wall-clock window, or the window is driven by an injected `TimeProvider`; the test passes deterministically under 24-worker parallel coverage runs.
+- [ ] AC2: `DfDeedle_COM_Tests` no longer mutates process-wide static seams in a way another class can observe, and `DfDeedle.cs:186` guards the null snapshot element with a descriptive failure.
+- [ ] AC3: The two `Console.Out` races are removed by eliminating the shared-console dependency.
+- [ ] AC4: A full nine-assembly `/InIsolation` run with `TestCategory!=LiveOutlook` reports zero failures on ten consecutive runs, recorded as evidence.
+- [ ] AC5: No test is stabilized by a sleep, a retry, or a timing tolerance.
+
+Validation:
+
+- [ ] Unit coverage areas: `DictionaryExtensions.TryAddValuesAsync` with `FakeTimeProvider`; `DfDeedle.GetEmailDataInViewAsync` null-snapshot path.
+- [ ] Integration scenario to retest: ten consecutive full-suite runs locally and one CI run.
+- [ ] Manual verification notes: none.
+
+## Next Step
+
+- [ ] Promote to GitHub issue (bug-report template)
+- [ ] Move to active fix folder / branch
diff --git a/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/plan.2026-09-07T22-03.md b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/plan.2026-09-07T22-03.md
new file mode 100644
index 000000000..800d8f05d
--- /dev/null
+++ b/docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/plan.2026-09-07T22-03.md
@@ -0,0 +1,592 @@
+# 2026-09-07-utilitiescs-test-determinism-780-803-594 (Plan)
+
+- **Issue:** #811
+- **Parent (optional):** none
+- **Owner:** drmoisan
+- **Last Updated:** 2026-09-08T09-06
+- **Status:** Approved for execution. Executor preflight returned PREFLIGHT: ALL CLEAR on round 3, after the round-1 delta (nine defects) and the round-2 delta (four defects) were applied. The MCP plan validator returns ok with no warnings in either channel. Re-anchored at 2026-09-08T09-06 after origin/main was merged into this branch as merge commit bb1c7d4b60f7b782227956f36859314d5c47bb03. Two classes of fact moved and nothing else did: the anchor commit itself, and `UtilitiesCS.Test/UtilitiesCS.Test.csproj`, which gained three `` items from the already-merged items #809 and #812. Those three items shift the project file from 982 to 985 lines and move two of its three cited item positions. Every other write-set citation and line count was re-measured against the merged tree and is unchanged, including all sixteen `.cs` line counts and `TimeOutTask.cs`.
+- **Version:** 1.3
+- **Work Mode:** full-bug
+- **Base commit:** bb1c7d4b60f7b782227956f36859314d5c47bb03 (the origin/main merge commit on this branch; the pre-merge anchor was 04a54e681bd21e841e124c016df30672ee701b75, which is no longer a valid diff anchor because main advanced 31 commits behind this branch)
+- **Branch:** bug/utilitiescs-test-determinism-780-803-594-811
+
+**Fail-closed evidence rule:** Every evidence-producing task names its artifact path. If any required
+baseline artifact, QA artifact, or coverage-comparison artifact is missing or incomplete, the verdict
+is BLOCKED or INCOMPLETE, never PASS. An unchecked task may not be checked off without its artifact.
+
+**Evidence accounting rule:** Each command-step artifact carries `Timestamp:`, `Command:`,
+`EXIT_CODE:`, and `Output Summary:`. Artifacts for gates whose expected exit code is non-zero also
+carry `ExpectedExitCode:`. One artifact per non-zero-capable gate.
+
+**Requirements authority.** spec.md is the sole acceptance-criteria source for this `full-bug` item
+and carries exactly five criteria, AC1 through AC5, under `## Acceptance Criteria` at spec.md line
+291. user-story.md is absent and stays absent. The research artifact
+`research/root-cause.2026-09-07T22-10.md` is authoritative for mechanism; spec.md is authoritative
+for scope and acceptance; where they differ, spec.md wins.
+
+---
+
+## Write Set Under Change
+
+The definitive list is spec.md `#### Files/modules to change:` (items 1 to 20). Repository-relative
+paths, with the line count measured in this worktree at the base commit:
+
+### Production (8), all carry `#nullable enable`
+
+1. `UtilitiesCS/Extensions/DictionaryExtensions.cs` (282)
+2. `UtilitiesCS/OutlookObjects/Table/OlTableExtensions.Etl.cs` (474; 26 lines of headroom under the cap)
+3. `UtilitiesCS/Extensions/DfDeedle.cs` (314)
+4. `UtilitiesCS/Extensions/DfDeedle.FrameUtilities.cs` (276)
+5. `UtilitiesCS/OutlookObjects/Filter DASL/DASLFilterParser.cs` (122)
+6. `UtilitiesCS/HelperClasses/PrettyPrint.cs` (680; PRE-EXISTING cap violation; zero net growth)
+7. `UtilitiesCS/OutlookObjects/Table/OlTableExtensions.TableAccess.cs` (430)
+8. `UtilitiesCS/ReusableTypeClasses/Other/StackGeek.cs` (199)
+
+### Tests (11), none carries a whole-file `#nullable enable`
+
+9. `UtilitiesCS.Test/Extensions/DictionaryExtensions_Tests.cs` (296)
+10. `UtilitiesCS.Test/Extensions/DfDeedle_COM_Tests.cs` (869; PRE-EXISTING cap violation; must end strictly below 869)
+11. `UtilitiesCS.Test/Extensions/DfDeedleEtlTimeoutTests.cs` (NEW)
+12. `UtilitiesCS.Test/OutlookObjects/Table/OlTableExtensionsEtlClockTests.cs` (NEW)
+13. `UtilitiesCS.Test/Extensions/DfDeedleQfcColumnTimeoutTests.cs` (exactly 500; may not gain one line)
+14. `UtilitiesCS.Test/TestHelpers/ArmingBarrierTimeProvider.cs` (NEW; receives the moved helper)
+15. `UtilitiesCS.Test/ReusableTypeClasses/StackGeek_Tests.cs` (276)
+16. `UtilitiesCS.Test/HelperClasses/PrettyPrint_Tests.cs` (408)
+17. `UtilitiesCS.Test/OutlookObjects/Filter DASL/DASLFilterParserTests.cs` (125)
+18. `UtilitiesCS.Test/OutlookObjects/Table/OlTableExtensions_Tests.cs` (1855; PRE-EXISTING cap violation; must end strictly below 1855)
+19. `UtilitiesCS.Test/HelperClasses/NLogTraceWriter_Test.cs` (119)
+
+### Project file (1)
+
+20. `UtilitiesCS.Test/UtilitiesCS.Test.csproj` (985; Compile items only; the 500-line cap does not reach `*.csproj`)
+
+Total: 20 paths. Feature-folder artifacts under
+`docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811/` (written ``
+below) and the two follow-up entries under `docs/features/potential/` are additionally created or
+updated by this plan and are outside the source write set.
+
+Not touched, and every gate is written so that it cannot require touching them:
+UtilitiesCS/Threading/TimeOutTask.cs (1011 lines; the inert `(int, int)` overloads stay),
+UtilitiesCS/Extensions/DfDeedle.QfcColumns.cs (its line-96 doc comment names `TableEtlInvoker` and
+becomes stale; recorded as a follow-up in P8-T9, not edited), the roughly 24 test classes that install
+a `DebugTextWriter` with no restore, any `.github/workflows/` file, any `packages.config`, any
+`.runsettings`, `.editorconfig`, and `BannedSymbols.txt`.
+
+---
+
+## Decisions record (settled; not re-opened by the executor)
+
+- **D1. Production timing is unchanged, byte-for-byte.** `TimeOutTask.cs:824` (`(this Task, int millisecondsTimeout, int repeatAttempts)`) calls `task.TimeoutAfter(millisecondsTimeout)` at line 834, which binds to the `(this Task, int, TimeProvider? timeProvider = null)` overload at line 862. That overload is not `async` and returns the task itself (line 873) or a proxy that a timer later faults; it never throws synchronously, so the `catch (TimeoutException)` at line 836 cannot fire and the retry at line 841 is dead. Replacing `.TimeoutAfter(ms, attempts)` with `.TimeoutAfter(ms, timeProvider)` where `timeProvider` is null is therefore exactly today's behaviour on the system clock. No deadline value changes: 250 ms x rowCount stays at `OlTableExtensions.Etl.cs:81`, 1000 ms stays at `DfDeedle.cs:208`, 3000 ms stays in `DfDeedle.QfcColumns.cs`.
+- **D2. The AC2 citation.** The issue and AC2 cite `DfDeedle.cs:186`. Line 186 opens the `LogDfTiming(` call; the dereference `tableSnapshot.Item1.GetLength(0)` is on line 188 inside that call. The guard is inserted between line 185 and line 186, ahead of the whole call.
+- **D3. `TryAddValuesAsync` has zero production call sites.** The only invocation in the repository is `UtilitiesCS.Test/Extensions/DictionaryExtensions_Tests.cs:244`; the other two hits are the definition (`DictionaryExtensions.cs:169`) and the test method name (line 237). `TryAddValues` (lines 123-136) is a bounded compare-and-swap loop that returns `false` when the key is absent; it cannot hang. The 500 ms window is deleted (AC1 first disjunct), not re-seamed.
+- **D4. The RED test needs the seam first.** A test that passes `timeProvider:` to `GetEmailDataInViewAsync` cannot compile until the parameter exists. Phase 1 lands every seam with no behaviour change; Phase 2 adds the regression test, which then fails at assertion time with `NullReferenceException`; Phase 3 adds the guard. This is the declaration-then-red ordering, and it keeps the whole test assembly compiling at every task.
+- **D5. Deterministic arming order in the RED test.** On the failing path two timers are armed on the injected clock: first the 3000 ms column-add deadline inside `AddQfcColumnsAsync` (`DfDeedle.QfcColumns.cs:127`), then the 250 ms ETL hop deadline (`OlTableExtensions.Etl.cs:245`). Whether the first timer is armed at all depends on a race between the pool thread running `AddQfcColumns` and the caller reaching `TimeoutAfter`. The test removes the race with two test-owned gates: `Columns.Add("SentOn")`, the first call `AddQfcColumns` makes (`DfDeedle.QfcColumns.cs:40`), blocks on gate A; `Table.GetNextRow()` blocks on gate B. The test awaits `Armed` (timer 1, deterministic because the adder is blocked), re-arms, releases gate A, awaits `Armed` (timer 2, deterministic because the row read is blocked), then advances the clock by 250 ms. Both gates are released in `finally` so the orphaned `Task.Run` bodies complete. `FakeTimeProvider` arms timers relative to its current time, so advancing before timer 2 exists would hang the test; this ordering is what prevents that.
+- **D6. AC1 has no deterministic RED.** The original failure is load-dependent and cannot be reproduced without a sleep, which AC5 forbids. AC1's fail-before evidence is a `fail-before-exception` dossier (P4-T3) whose alternative proof is the structural count gate on `CancelAfter(` (1 before, 0 after), plus the contract-lock test that a pre-cancelled token still yields `TaskCanceledException`. The spec Test Strategy's "token cancelled after the work is observed to start" test is not authored: `TryAddValues` offers no observation point without a custom operator type, and the test would pass identically before and after the fix, so it has no discriminating power. This is a recorded deviation from the spec's Test Strategy prose; it changes no acceptance criterion.
+- **D7. `DfDeedle` static seams.** `TableEtlInvoker` (`DfDeedle.cs:69-72`) and `StoreTableEtlInvoker` (`DfDeedle.cs:81-84`) are deleted and replaced by an optional trailing delegate parameter on `GetEmailDataInView(Explorer)` and on both `FromDefaultFolder` overloads. One `private static readonly` default delegate `DefaultTableEtl` replaces both (they are the same lambda `t => ((Outlook.Table)t).ETL()`). It is written in the same one-line-initializer shape as the statics it replaces so the per-file coverage accounting in P7-T6 sees the same line shape. `MessageBoxInvoker` (`DfDeedle.cs:54-60`) stays static per spec.md; its restored-in-`finally`, reader-free status is documented in the test-class header.
+- **D8. `[DoNotParallelize]` disposition.** Removed from `StackGeek_Tests` (line 15), `PrettyPrint_Tests` (line 19) and `DASLFilterParserTests` (line 14), whose comments name the console redirect as the sole reason. Retained on `OlTableExtensions_Tests` (line 20) under spec.md Mitigation 4, with the comment at lines 17-19 rewritten so it no longer claims the console: the class is 1855 lines of COM-mock tests, ten of its tests drive `GetTableInViewAsync`/`RunWithTimeout` (a 2000 ms wall-clock window), and it has never been soaked under class-level parallelism. Removal after a soak is a follow-up (P8-T9).
+- **D9. `OlTableExtensions.Etl.cs` headroom.** The change adds one signature line, two comment lines and removes one local, for a net of about +3 (474 to about 477). The dead `EtlAsyncOld` (lines 132-169) is NOT deleted: doing so would also delete its test at `OlTableExtensions_Tests.cs:986-1017` and widen the diff for no acceptance gain. Consequence: `EtlAsyncOld` retains `var attempts = 3;` (line 148), `.TimeoutAfter(milliseconds, attempts)` (line 158) and `DateTime.Now` (line 163), so every gate over those tokens in this file is an exact-count gate, never a zero-hit gate.
+- **D10. `PrettyPrint.cs` zero-growth budget.** Adding `using System.IO;` costs +1 and each seamed overload wraps to two lines under CSharpier's 100-column width (+2). Three removals pay for it: line 6 `using System.Threading.Tasks;` (the token `Task` does not occur anywhere in the file), line 12 `using System.Text;` (duplicates line 5), line 18 `using Svg;` (the token `Svg` occurs only on line 18). If, and only if, the P5-T10 build reports a CS0103 or CS0246 in `PrettyPrint.cs`, line 18 is restored and line 15 `using System.Windows.Input;` (no `System.Windows.Input` type name occurs in the file; the two `Key` hits at lines 174 and 535 are `kvp.Key`/`x.Key` property reads) is removed instead, and the artifact records the substitution. The gate is the outcome: the file's line count is at most 680 after every task.
+- **D11. AC4 is local evidence, not a CI gate.** `.github/workflows/_mstest-coverage.yml:86-92` discovers assemblies with a recursive `*.Test.dll` search (not a hard-coded list) and the job carries `timeout-minutes: 30`; ten consecutive full runs do not fit. The local gate enumerates the nine assemblies explicitly (no recursive discovery, so the dot-claude worktree problem never arises), extends the filter with the shell-icon exclusion that the #798 baseline recorded as `SHELL_ICON_EXCLUSION: REQUIRED` on this host (one of 23 shell-icon tests fails per run with an invalid Win32 icon handle, independent of this fix; CI runs them unfiltered), omits `/Settings:` so parallelism comes from `[assembly: Parallelize(Workers = 0, Scope = ClassLevel)]` at `UtilitiesCS.Test/Properties/AssemblyInfo.cs:18-21` exactly as in CI, and is split into five two-run tasks so each fits inside one 600000 ms tool invocation. P0-T12 establishes once, before any source change, whether the CI-verbatim shape is locally viable.
+- **D12. AC5 has no automated enforcement.** `BannedSymbols.txt` (7 entries) bans `Thread.Sleep` and `Task.Delay` but not `CancelAfter`, `TimeoutAfter` or `WaitOne`, and `.editorconfig:548` holds RS0030 at `suggestion`, so no toolchain step fails on a new sleep. P7-T10 is an explicit search over the anchored diff's added lines. The pre-existing tolerance at `OlTableExtensions_Tests.cs:960-963` (`Returns(120)` with the comment "so the timeout cannot fire under test-host contention") is retired in P6-T1.
+- **D13. New test files must be registered.** `UtilitiesCS.Test.csproj` is a legacy non-SDK project with explicit `` items (in the merged base tree the `DfDeedle` family sits at lines 189-192, `TestHelpers\` at 74-77, and `OutlookObjects\Table\OlTableExtensions_Tests.cs` at 541). An unregistered test file still builds and is silently absent from the assembly. Every task that creates a test file is immediately followed by the task that registers it, and P8-T1 carries a non-vacuity control naming every new test.
+- **D14. The reflection call at `OlTableExtensions_Tests.cs:1124-1137`** binds `EtlByRowAsync` by an explicit four-type array (`Table`, `Dictionary>`, `Dictionary`, `CancellationToken`), selecting the four-parameter overload at `OlTableExtensions.Etl.cs:171`. P1-T1 changes only the seven-parameter overload at line 229. P6-T2 re-checks the call after all edits, because a reflection binding is invisible to the compiler.
+- **D15. Coverage measurement.** Baseline and final full-suite runs use the repository wrapper's own argument shape (`scripts/vscode/Invoke-MSTestWithCoverage.ps1:70-76`): `dotnet-coverage collect ... -- /Settings:scripts\vscode\TaskMaster.cli.runsettings /InIsolation /TestCaseFilter:...`, issued directly because the wrapper hard-codes its filter at line 76 and cannot take the shell-icon exclusion, and because its discovery at line 301 rejects every assembly under a dot-claude path when run from an agent worktree. Raw Cobertura documents stay under the gitignored `coverage/` directory (`.gitignore:144`) and are NOT committed; per-file figures are recorded in the artifacts. The repository-wide root `line-rate` is not reproducible run-to-run on this pipeline (the merged denominator moves), so it is reported under a two-branch comparability rule and the blocking coverage gates are per-file and per-changed-line.
+- **D16. The base anchor was moved by the origin/main merge.** After this plan cleared preflight, origin/main advanced 31 commits ahead of the original anchor `04a54e681bd21e841e124c016df30672ee701b75` and was merged into this branch as `bb1c7d4b60f7b782227956f36859314d5c47bb03`. The original anchor is no longer a usable diff anchor: an anchored `git diff --name-only` over the merged tree would enumerate main's own source files as well as this item's, so the P7-T9, P7-T12 and P8-T12 write-set gates would be unsatisfiable against it. `$Base` is therefore the merge commit. The merge changed exactly one write-set file, `UtilitiesCS.Test/UtilitiesCS.Test.csproj`, which gained three `` items (`TestHelpers\UiThreadStateScope.cs`, `OutlookObjects\Folder\FolderPredictorArchiveRootDegradationTests.cs`, `Threading\UiThreadInitContract_Tests.cs`) from the already-merged items #809 and #812. Every project-file line count in this plan was raised by three (985 in the merged base tree, 986 after P1-T7, 987 after P2-T2, 988 after P6-T4) and the item citations moved to 74-77, 189-192 and 541. Item #798, which this plan's `DfDeedle` citations depend on, merged as `4a29d7e7` BEFORE the original anchor, so it was already present when the plan was authored and its files are unaffected. All sixteen write-set `.cs` line counts and `TimeOutTask.cs` were re-measured against the merged tree and are identical to the values P0-T6 asserts, so P0-T6 is unchanged; it carries no project-file line count. If origin/main advances again mid-execution, the executor stops at the phase boundary and returns to the orchestrator for a fresh reconciliation rather than re-anchoring on its own.
+
+---
+
+## Plan-wide conventions
+
+### C1. Shell preamble (every fenced PowerShell block)
+
+Every command block is executed as one `pwsh -NoProfile -File coverage/plan811-helper.ps1` invocation.
+The helper path is fixed, gitignored (`coverage/*`), rewritten in place for every task, registered
+in no project file, asserted by no acceptance condition, and is not a deliverable. No shell variable
+survives between tasks; every block begins with this preamble verbatim:
+
+```powershell
+ $ErrorActionPreference = 'Stop'
+ $Root = (git rev-parse --show-toplevel)
+ Set-Location -LiteralPath $Root
+ if ((git rev-parse --abbrev-ref HEAD) -ne 'bug/utilitiescs-test-determinism-780-803-594-811') { throw 'wrong worktree' }
+ $LASTEXITCODE = 0
+ $Base = 'bb1c7d4b60f7b782227956f36859314d5c47bb03'
+ $Feature = 'docs/features/active/2026-09-07-utilitiescs-test-determinism-780-803-594-811'
+ $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
+ $msbuild = & $vswhere -latest -products * -find 'MSBuild\**\Bin\MSBuild.exe' | Select-Object -First 1
+ $dotnet = Join-Path $Root '.dotnet-sdk\dotnet.exe'
+ $Assemblies = @(
+ 'QuickFiler.Test\bin\Debug\QuickFiler.Test.dll', 'SVGControl.Test\bin\Debug\SVGControl.Test.dll',
+ 'Tags.Test\bin\Debug\Tags.Test.dll', 'TaskMaster.Test\bin\Debug\TaskMaster.Test.dll',
+ 'TaskTree.Test\bin\Debug\TaskTree.Test.dll', 'TaskVisualization.Test\bin\Debug\TaskVisualization.Test.dll',
+ 'ToDoModel.Test\bin\Debug\ToDoModel.Test.dll', 'UtilitiesCS.Test\bin\Debug\UtilitiesCS.Test.dll',
+ 'VBFunctions.Test\bin\Debug\VBFunctions.Test.dll')
+ $FullFilter = 'TestCategory!=LiveOutlook&FullyQualifiedName!~ShellUtilities&FullyQualifiedName!~SysImageListHelper&FullyQualifiedName!~OSBrowser'
+ $Blame = '/Blame:CollectHangDump;TestTimeout=4min;HangDumpType=None'
+```
+
+Lines inside fenced blocks are indented four spaces so that no line begins at column 0 with `#`.
+Backslash-bearing literals are written with single backslashes inside single-quoted PowerShell
+strings; no doubled backslash appears anywhere in this document.
+
+### C2. Toolchain commands (verbatim from CLAUDE.md; order is binding; restart from step 1 on any failure or rewrite)
+
+1. `dotnet tool run csharpier format .` (invoked as `& $dotnet tool run csharpier format .`), verified with `dotnet tool run csharpier check .`
+2. msbuild TaskMaster.sln /t:Rebuild /m /p:Configuration=Debug "/p:Platform=Any CPU" /p:EnableNETAnalyzers=true /p:EnforceCodeStyleInBuild=true
+3. msbuild TaskMaster.sln /t:Rebuild /m /p:Configuration=Debug "/p:Platform=Any CPU" /p:TreatWarningsAsErrors=true
+4. `vstest.console.exe` over the nine assemblies with coverage (D15 form for the coverage steps; CI-verbatim `/EnableCodeCoverage` form for AC4)
+
+Facts that make a gate vacuous or unsatisfiable if ignored:
+
+- `/t:Rebuild` is mandatory for the two solution gates. A warm `/t:Build` returns exit 0 with `CoreCompile` skipped and runs no analyzers.
+- `/p:Nullable=enable` is never added. CI omits it; adding it conscripts every un-annotated file.
+- `"/p:Platform=Any CPU"` is a solution-level alias. Project-file builds (used only to produce a test assembly for a scoped run, never as a gate) use `/p:Platform=AnyCPU` without the space.
+- A successful msbuild prints the substring `error` in switch names and summary text. Build gates assert the exit code plus the verbatim summary line `0 Error(s)` read from a normal-verbosity file log written to `coverage/msbuild-.log`, never the absence of the substring.
+- `csharpier format` exits 0 whether or not it rewrote anything, and `Formatted N files in Mms.` is a processed-file count, not a rewrite count. Every format task records SHA-256 hashes of the 19 write-set `.cs` files before and after, defines rewritten-count as the number of hash differences, and is paired with `csharpier check .`, whose success line begins with the literal `Checked ` and ends with `ms.` (observed on this host: `Checked 1587 files in 7826ms.`).
+- Every `git diff` is anchored to `$Base`. `$Base` is the merge commit that brought origin/main onto this branch, and it is also the value of `HEAD` at the start of execution, because no commit occurs before P7-T12. P3-T3's numstat over spec.md stays written against `HEAD`, which therefore resolves to the same commit as `$Base` when that task runs; spec.md is tracked at both, so the numstat is well-defined against either. Every name-listing diff is paired with `git add --intent-to-add` for the three new test files and a `git status --porcelain --untracked-files=all` span in the same task.
+- The dot-claude agent-memory tree is tracked and may be dirty from preparation agents. Every diff, status and name-listing gate is scoped with the pathspec exclusion written in plain prose as ":(exclude).claude".
+- A bare `/Logger:trx` names the file after the account and machine. Every vstest invocation passes `"/Logger:trx;LogFileName=.trx"` (double-quoted; an unquoted semicolon terminates the argument) and `/ResultsDirectory:coverage/trx/` (gitignored, one directory per task). TRX and `.coverage` files are never committed: a TRX carries the account token in `runUser=` and the machine name in every `computerName=`.
+- No artifact, plan line, or commit message contains an absolute host path, the account name, or the machine name. Tokens are derived at run time (`$acct = [regex]::Escape((Split-Path -Leaf $env:USERPROFILE))`, `$mach = [regex]::Escape($env:COMPUTERNAME)`) and never written out.
+- Every token count in this plan is `@(Select-String -LiteralPath -CaseSensitive -SimpleMatch '').Count` unless the clause says "with regex", in which case `-Pattern` replaces `-SimpleMatch` and `-CaseSensitive` is still supplied. `Select-String` is case-insensitive by default, so the switch is load-bearing: without it `TimeProvider` also matches `timeProvider`.
+- A test-scoped vstest run against UtilitiesCS.Test\bin\Debug\UtilitiesCS.Test.dll alone cannot reach the repository's only `[TestCategory("LiveOutlook")]` test (it lives in TaskMaster.Test), so scoped filters use `FullyQualifiedName~` joined with `|`; every nine-assembly run carries `$FullFilter`.
+
+### C3. Reusable procedures (copied verbatim into the helper when a task names them)
+
+**PROC-TRX** (read the counters and named results out of a TRX):
+
+```powershell
+ function Read-TrxCounters([string]$Path) {
+ [xml]$trx = Get-Content -LiteralPath $Path -Raw
+ $c = $trx.TestRun.ResultSummary.Counters
+ [pscustomobject]@{ total=[int]$c.total; executed=[int]$c.executed; passed=[int]$c.passed; failed=[int]$c.failed; error=[int]$c.error; timeout=[int]$c.timeout; aborted=[int]$c.aborted; notExecuted=[int]$c.notExecuted }
+ }
+ function Read-TrxOutcome([string]$Path, [string]$ClassName, [string]$MethodName) {
+ [xml]$trx = Get-Content -LiteralPath $Path -Raw
+ $ns = @{ t = 'http://microsoft.com/schemas/VisualStudio/TeamTest/2010' }
+ $ut = Select-Xml -Xml $trx -Namespace $ns -XPath "//t:UnitTest[starts-with(t:TestMethod/@className,'$ClassName') and t:TestMethod/@name='$MethodName']"
+ if (-not $ut) { return 'ABSENT' }
+ $id = $ut.Node.GetAttribute('id')
+ $r = Select-Xml -Xml $trx -Namespace $ns -XPath "//t:UnitTestResult[@testId='$id']"
+ if (-not $r) { return 'NO-RESULT' }
+ return $r.Node.GetAttribute('outcome')
+ }
+ function Read-TrxMessage([string]$Path, [string]$ClassName, [string]$MethodName) {
+ [xml]$trx = Get-Content -LiteralPath $Path -Raw
+ $ns = @{ t = 'http://microsoft.com/schemas/VisualStudio/TeamTest/2010' }
+ $ut = Select-Xml -Xml $trx -Namespace $ns -XPath "//t:UnitTest[starts-with(t:TestMethod/@className,'$ClassName') and t:TestMethod/@name='$MethodName']"
+ if (-not $ut) { return 'ABSENT' }
+ $id = $ut.Node.GetAttribute('id')
+ $m = Select-Xml -Xml $trx -Namespace $ns -XPath "//t:UnitTestResult[@testId='$id']/t:Output/t:ErrorInfo/t:Message"
+ if (-not $m) { return 'NO-MESSAGE' }
+ return $m.Node.InnerText
+ }
+```
+
+**PROC-COV** (per-file line map from a raw Cobertura document; both node axes merged by line number keeping the maximum `hits`, which is the repository helper's own dedup rule):
+
+```powershell
+ function Get-FileLineMap([xml]$Doc, [string]$Suffix) {
+ $map = @{}
+ foreach ($cls in $Doc.SelectNodes('//class')) {
+ $fn = $cls.GetAttribute('filename')
+ if (-not $fn.EndsWith($Suffix)) { continue }
+ $nodes = @($cls.SelectNodes('./lines/line')) + @($cls.SelectNodes('./methods/method/lines/line'))
+ foreach ($ln in $nodes) {
+ $n = [int]$ln.GetAttribute('number'); $h = [int]$ln.GetAttribute('hits')
+ if (-not $map.ContainsKey($n) -or $map[$n] -lt $h) { $map[$n] = $h }
+ }
+ }
+ return $map
+ }
+ function Get-FileSummary([xml]$Doc, [string]$Suffix) {
+ $m = Get-FileLineMap $Doc $Suffix
+ $valid = $m.Count; $covered = @($m.Values | Where-Object { $_ -gt 0 }).Count
+ "suffix=$Suffix valid=$valid covered=$covered uncovered=$($valid - $covered)"
+ }
+ function Get-MethodLineRate([xml]$Doc, [string]$Suffix, [string]$Method) {
+ foreach ($cls in $Doc.SelectNodes('//class')) {
+ if (-not $cls.GetAttribute('filename').EndsWith($Suffix)) { continue }
+ foreach ($mth in $cls.SelectNodes('./methods/method')) {
+ if ($mth.GetAttribute('name') -eq $Method) { return [double]$mth.GetAttribute('line-rate') }
+ }
+ }
+ return 'ABSENT'
+ }
+```
+
+The eight production suffixes, written with single backslashes exactly as the collector emits them:
+`UtilitiesCS\Extensions\DictionaryExtensions.cs`, `UtilitiesCS\OutlookObjects\Table\OlTableExtensions.Etl.cs`,
+`UtilitiesCS\Extensions\DfDeedle.cs`, `UtilitiesCS\Extensions\DfDeedle.FrameUtilities.cs`,
+`UtilitiesCS\OutlookObjects\Filter DASL\DASLFilterParser.cs`, `UtilitiesCS\HelperClasses\PrettyPrint.cs`,
+`UtilitiesCS\OutlookObjects\Table\OlTableExtensions.TableAccess.cs`, `UtilitiesCS\ReusableTypeClasses\Other\StackGeek.cs`.
+
+**PROC-CHANGED** (post-image added line numbers of one file from the anchored diff):
+
+```powershell
+ function Get-AddedLines([string]$Base, [string]$Path) {
+ $out = @()
+ foreach ($l in (git diff --unified=0 $Base -- $Path)) {
+ if ($l -match '^@@ -\d+(,\d+)? \+(\d+)(,(\d+))? @@') {
+ $start = [int]$matches[2]; $len = if ($matches[4]) { [int]$matches[4] } else { 1 }
+ if ($len -gt 0) { $out += ($start..($start + $len - 1)) }
+ }
+ }
+ return $out
+ }
+```
+
+**PROC-HASH** (SHA-256 of the 19 write-set `.cs` files): `Get-FileHash -Algorithm SHA256` over
+items 1 to 19 of the Write Set, printed as ` ` lines; the three new files are skipped
+while absent.
+
+### C4. Literals this plan creates (quoted here so a search for them is a real assertion)
+
+`TimeoutAfter(milliseconds, timeProvider)`, `TimeoutAfter(timeout, timeProvider)`,
+`TimeoutAfter(1000, timeProvider)`, `timed out with a timeout of`, `TimeProvider? timeProvider = null`,
+`DefaultTableEtl`, `was not produced`, `TextWriter? writer = null`, `Run(Console.Out)`,
+`public static void Run(`, `not been soaked under class-level parallelism`,
+`class ArmingBarrierTimeProvider`, `Extensions\DfDeedleEtlTimeoutTests.cs`,
+`OutlookObjects\Table\OlTableExtensionsEtlClockTests.cs`, `TestHelpers\ArmingBarrierTimeProvider.cs`,
+`using UtilitiesCS.Test.TestHelpers;`, `using Microsoft.Extensions.Time.Testing;`,
+`MessageBoxInvoker is the one remaining static seam`.
+
+New test methods (fully qualified; the nine names P7-T5 and P8-T1 assert, seven new plus two retained sentinels):
+
+- `UtilitiesCS.Test.Extensions.DictionaryExtensions_Tests.TryAddValuesAsync_PreCancelledToken_ThrowsTaskCanceledAndLeavesValueUnchanged` (new)
+- `UtilitiesCS.Test.Extensions.DfDeedleEtlTimeoutTests.GetEmailDataInViewAsync_EtlDeadlineExpires_ThrowsInvalidOperationNamingFolder` (new)
+- `UtilitiesCS.Test.Extensions.DfDeedleEtlTimeoutTests.GetEmailDataInViewAsync_ClockNeverAdvances_ReturnsOneRowFrame` (new)
+- `UtilitiesCS.Test.OutlookObjects.Table.OlTableExtensionsEtlClockTests.EtlAsync_DeadlineExpires_ReturnsNullDataAndCancelsTokenSource` (new)
+- `UtilitiesCS.Test.OutlookObjects.Table.OlTableExtensionsEtlClockTests.EtlAsync_ClockNeverAdvances_ReturnsTransformedRows` (new)
+- `UtilitiesCS.Test.ReusableTypeClasses.StackGeek_Tests.Run_WritesScenarioToSuppliedWriter` (new)
+- `UtilitiesCS.Test.HelperClasses.PrettyPrint_Tests.PrettyPrint_NullWriter_WritesToConsoleWithoutThrowing` (new)
+- `UtilitiesCS.Test.Extensions.DictionaryExtensions_Tests.TryAddValuesAsync_UpdatesExistingValue` (retained, the #780 test)
+- `UtilitiesCS.Test.Extensions.DfDeedle_COM_Tests.GetEmailDataInViewAsync_SeparatesTableSnapshotFromDataFrameTransform` (retained, the #803 test)
+
+Renamed test methods (behaviour now targets a supplied writer): `PrintTree_WritesIndentedTreeToSuppliedWriter`
+(was `PrintTree_WritesIndentedTreeToConsole`), `DataFramePrettyHelpers_RenderRowsMarkdownAndWriterOutput`
+(was `DataFramePrettyHelpers_RenderRowsMarkdownAndConsoleOutput`),
+`EnumerateTable_WritesFormattedOutputToSuppliedWriterAndMovesToStart`
+(was `EnumerateTable_WritesFormattedOutputAndMovesToStart`). `Main_RunsSampleScenarioWithoutThrowing` keeps its name.
+
+---
+
+### Phase 0 — Policy reads, toolchain bootstrap, and baselines
+
+- [x] [P0-T1] Read the policy files in the `policy-compliance-order` sequence and record the read in `/evidence/baseline/phase0-instructions-read.md`: CLAUDE.md, then `.claude/rules/general-code-change.md`, `.claude/rules/general-unit-test.md`, `.claude/rules/csharp.md`, `.claude/rules/tonality.md`, `.claude/rules/plan-acceptance-gates.md`, then `/issue.md`, `/spec.md`, `/research/root-cause.2026-09-07T22-10.md`. Acceptance: the artifact carries `Timestamp:`, `Policy Order:` listing the nine paths in that order, the line `Work Mode: full-bug` read from issue.md line 12, the line `AC Source: spec.md lines 292-296 (AC1..AC5)`, and `user-story.md: ABSENT (expected)` after `Test-Path` on `/user-story.md` returns `False`.
+
+- [x] [P0-T2] Record worktree identity and the anchor commit in `/evidence/baseline/p0-t2-worktree-identity.md`: run the C1 preamble; record `git rev-parse HEAD`, `git rev-parse --abbrev-ref HEAD`, `git cat-file -t bb1c7d4b60f7b782227956f36859314d5c47bb03`, `git merge-base HEAD bb1c7d4b60f7b782227956f36859314d5c47bb03`, and `git status --porcelain --untracked-files=all -- . ":(exclude).claude"`. Acceptance: the branch name equals `bug/utilitiescs-test-determinism-780-803-594-811`; `cat-file -t` prints `commit`; the merge-base output equals `bb1c7d4b60f7b782227956f36859314d5c47bb03` (so the anchor is an ancestor of HEAD; it is HEAD itself until P7-T12 commits); the scoped porcelain lists only paths under `/`; the artifact carries `BASE-SHA: bb1c7d4b60f7b782227956f36859314d5c47bb03` on its own line and no host path. Also record `git rev-parse origin/main` and `git merge-base --is-ancestor origin/main HEAD` (exit 0), which together assert that the merged main is still contained in this branch; if that check fails, main advanced again and the orchestrator re-runs the reconciliation before any source edit.
+
+- [x] [P0-T3] Bootstrap the repo-local SDK and restore the tool manifest, recording `/evidence/baseline/p0-t3-toolchain-bootstrap.md`: if `Test-Path .dotnet-sdk\dotnet.exe` is `False`, run `pwsh -NoProfile -File scripts/vscode/Install-RepoDotNetSdk.ps1` (global.json pins 8.0.205 under `.dotnet-sdk`; a fresh agent worktree lacks it); then run `& $dotnet tool restore` at the worktree root. Acceptance: `& $dotnet --version` prints `8.0.205`; the tool-restore output contains the line `Tool 'csharpier' (version '1.2.6') was restored. Available commands: csharpier`; both commands exit 0; the artifact records which branch (install or already-present) applied.
+
+- [x] [P0-T4] Restore NuGet packages for the packages.config projects, recording `/evidence/baseline/p0-t4-package-restore.md`: run `pwsh -NoProfile -File scripts/vscode/Invoke-Restore.ps1` (msbuild `/t:Restore /p:RestorePackagesConfig=true`; parameters `-SolutionPath`, `-Configuration`, `-Platform` default to TaskMaster.sln, Debug, Any CPU). Acceptance: exit 0; `Test-Path packages\Microsoft.Bcl.TimeProvider.10.0.11\lib` and `Test-Path packages\Microsoft.Extensions.TimeProvider.Testing.10.9.0\lib` both print `True` (the seam packages spec.md relies on; no package is added by this plan); the artifact records the count of directories directly under `packages\`.
+
+- [x] [P0-T5] Resolve the three external tools and record `/evidence/baseline/p0-t5-tool-resolution.md`: `$vstest`, `$msbuild` (both from the C1 preamble) and `Get-Command dotnet-coverage`. Acceptance: `$vstest` and `$msbuild` are non-empty and `Test-Path` on each prints `True`; `dotnet-coverage --version` exits 0 and prints a version string beginning with a digit; if `Get-Command dotnet-coverage` fails, run `dotnet tool install --global dotnet-coverage` and re-check, recording that the install branch applied. Redact every resolved path to `` or `` in the artifact.
+
+- [x] [P0-T6] Verify this plan's citations against the tree and record `/evidence/baseline/p0-t6-citation-baseline.md`, using `(Get-Content -LiteralPath ).Count` for line counts and `@(Select-String -LiteralPath -CaseSensitive -SimpleMatch '').Count` for token counts (case-sensitive, per C2). Acceptance: every value below matches exactly; on any mismatch the artifact records `CITATION-MISMATCH: ` and execution stops with the plan returned for revision (this is the only halt this plan permits, and it precedes every source edit).
+ - Line counts: `UtilitiesCS/Extensions/DictionaryExtensions.cs`=282; `UtilitiesCS/OutlookObjects/Table/OlTableExtensions.Etl.cs`=474; `UtilitiesCS/Extensions/DfDeedle.cs`=314; `UtilitiesCS/Extensions/DfDeedle.FrameUtilities.cs`=276; `UtilitiesCS/OutlookObjects/Filter DASL/DASLFilterParser.cs`=122; `UtilitiesCS/HelperClasses/PrettyPrint.cs`=680; `UtilitiesCS/OutlookObjects/Table/OlTableExtensions.TableAccess.cs`=430; `UtilitiesCS/ReusableTypeClasses/Other/StackGeek.cs`=199; `UtilitiesCS.Test/Extensions/DictionaryExtensions_Tests.cs`=296; `UtilitiesCS.Test/Extensions/DfDeedle_COM_Tests.cs`=869; `UtilitiesCS.Test/Extensions/DfDeedleQfcColumnTimeoutTests.cs`=500; `UtilitiesCS.Test/ReusableTypeClasses/StackGeek_Tests.cs`=276; `UtilitiesCS.Test/HelperClasses/PrettyPrint_Tests.cs`=408; `UtilitiesCS.Test/OutlookObjects/Filter DASL/DASLFilterParserTests.cs`=125; `UtilitiesCS.Test/OutlookObjects/Table/OlTableExtensions_Tests.cs`=1855; `UtilitiesCS.Test/HelperClasses/NLogTraceWriter_Test.cs`=119; `UtilitiesCS/Threading/TimeOutTask.cs`=1011; the three new files ABSENT.
+ - Token counts: in `DictionaryExtensions.cs`: `CancelAfter(`=1, `CreateLinkedTokenSource`=1. In `OlTableExtensions.Etl.cs`: `var attempts = 3;`=2, `TimeoutAfter(milliseconds, attempts)`=2, `TimeoutAfter(timeout, attempts)`=2, `timed out {attempts} times`=2, `DateTime.Now`=2, `TimeProvider`=0. In `DfDeedle.cs`: `TableEtlInvoker`=3, `TimeoutAfter(1000, 2)`=2, `TimeProvider`=0, `was not produced`=0. In `DfDeedle.FrameUtilities.cs`: `StoreTableEtlInvoker`=1. In `DASLFilterParser.cs`: `Console.WriteLine`=1, `TextWriter`=0. In `PrettyPrint.cs`: `Console.WriteLine`=2, `TextWriter`=0. In `OlTableExtensions.TableAccess.cs`: `Console.WriteLine`=5, `TextWriter`=0. In `StackGeek.cs`: `Console.WriteLine`=7, `public static void Run(`=0. In `DfDeedle_COM_Tests.cs`: `TableEtlInvoker`=14, `FakeTimeProvider`=0. In `DfDeedleQfcColumnTimeoutTests.cs`: `class ArmingBarrierTimeProvider`=1. In `StackGeek_Tests.cs`: `Console.SetOut`=3, `DoNotParallelize`=1. In `PrettyPrint_Tests.cs`: `Console.SetOut`=3, `DoNotParallelize`=2 (the line-14 comment mention and the line-19 attribute). In `DASLFilterParserTests.cs`: `Console.SetOut`=3, `DoNotParallelize`=1. In `OlTableExtensions_Tests.cs`: `Console.SetOut`=2, `DoNotParallelize`=1, `Returns(120)`=1, `cannot fire under test-host`=1, `redirects Console.Out`=1, `FakeTimeProvider`=0. In `NLogTraceWriter_Test.cs`: `Console.SetOut`=2, `originalOut`=3, `TestCleanup`=2. In `UtilitiesCS.Test/UtilitiesCS.Test.csproj`: `Extensions\DfDeedleEtlTimeoutTests.cs`=0, `OutlookObjects\Table\OlTableExtensionsEtlClockTests.cs`=0, `TestHelpers\ArmingBarrierTimeProvider.cs`=0.
+
+- [x] [P0-T7] Baseline toolchain step 1 in `/evidence/baseline/p0-t7-csharpier-check.md`: run `& $dotnet tool run csharpier check .` at the worktree root. Acceptance: the artifact carries `Timestamp:`, `Command: dotnet tool run csharpier check .`, `EXIT_CODE: `, `Output Summary:` quoting the summary line verbatim (it begins with `Checked ` and ends with `ms.`), and a `PRE-EXISTING DRIFT:` line listing every path the tool reported as unformatted, or `PRE-EXISTING DRIFT: none` when the exit code is 0. Expected on this host: exit 0 and `none` (observed one day earlier by #798).
+
+- [x] [P0-T8] Baseline toolchain step 2 in `/evidence/baseline/p0-t8-msbuild-analyzers.md`: run `& $msbuild TaskMaster.sln /t:Rebuild /m /p:Configuration=Debug "/p:Platform=Any CPU" /p:EnableNETAnalyzers=true /p:EnforceCodeStyleInBuild=true /nologo /v:q "/flp:LogFile=coverage/msbuild-p0-t8.log;Verbosity=normal"`. Acceptance: `EXIT_CODE: 0`; the file log contains the line ` 0 Error(s)` and the artifact quotes the `Warning(s)` and `Error(s)` lines verbatim and records `BASELINE_ANALYZER_WARNINGS: `; `Test-Path UtilitiesCS.Test\bin\Debug\UtilitiesCS.Test.dll` prints `True` afterwards.
+
+- [x] [P0-T9] Baseline toolchain step 3 in `/evidence/baseline/p0-t9-msbuild-nullable.md`: run `& $msbuild TaskMaster.sln /t:Rebuild /m /p:Configuration=Debug "/p:Platform=Any CPU" /p:TreatWarningsAsErrors=true /nologo /v:q "/flp:LogFile=coverage/msbuild-p0-t9.log;Verbosity=normal"`. Acceptance: `EXIT_CODE: 0`; the file log contains ` 0 Error(s)`; the artifact states in one sentence that `/p:Nullable=enable` was not supplied and why (CLAUDE.md C#1.3).
+
+- [x] [P0-T10] Baseline toolchain step 4, full nine-assembly coverage run, in `/evidence/baseline/p0-t10-vstest-coverage.md` (PROC-TRX): create `coverage/trx/p0-t10`, then run `dotnet-coverage collect --output coverage/p0-baseline.cobertura.xml --output-format cobertura --settings coverage.config -- $vstest $Assemblies /Settings:scripts\vscode\TaskMaster.cli.runsettings /InIsolation "/Logger:trx;LogFileName=p0-t10.trx" /ResultsDirectory:coverage/trx/p0-t10 $Blame "/TestCaseFilter:$FullFilter"`. Acceptance: `EXIT_CODE: ` recorded (0 when no test failed; 1 when `PREEXISTING_FAILURE_SET:` is non-empty; the baseline is a capture and is not remediated); `coverage/trx/p0-t10/p0-t10.trx` exists and its counters are recorded as `total= executed= passed= failed= error= timeout= aborted= notExecuted=`; `failed` plus `error` plus `aborted` plus `timeout` equals 0 OR the artifact lists every failing test name under `PREEXISTING_FAILURE_SET:` (a failure of `TryAddValuesAsync_UpdatesExistingValue` or `GetEmailDataInViewAsync_SeparatesTableSnapshotFromDataFrameTransform` here is the defect under repair and is recorded, not remediated); `coverage/p0-baseline.cobertura.xml` exists and the artifact records its root `line-rate`, `lines-covered`, `lines-valid`, `branch-rate`, `branches-covered`, `branches-valid` as numbers (`line-rate` is a fraction, not a percentage). This is the mandatory numeric coverage baseline. The run was measured at 54 s on this host for the wrapper form; invoke with the tool's 600000 ms timeout.
+
+- [x] [P0-T11] Baseline per-file coverage for the eight production files and the `Main` method in `/evidence/baseline/p0-t11-per-file-coverage.md` (PROC-COV over `coverage/p0-baseline.cobertura.xml`): for each of the eight suffixes record the `Get-FileSummary` line (`valid=`, `covered=`, `uncovered=`); record `Get-MethodLineRate` for suffix `UtilitiesCS\ReusableTypeClasses\Other\StackGeek.cs` method `Main`; for `EtlAsync`, which is `async` and emits no `` element in this pipeline's Cobertura output, record `ETLASYNC_SPAN: 66-130` (declaration line through closing brace, read from the baseline file) and `ETLASYNC_SPAN_RATE: /` computed over the `Get-FileLineMap` entries for suffix `UtilitiesCS\OutlookObjects\Table\OlTableExtensions.Etl.cs` whose line number lies in that span. Acceptance: all eight suffixes match at least one `class` element (`valid` greater than 0); `Get-MethodLineRate` for `Main` is a number, not `ABSENT`; the `EtlAsync` span `valid` is greater than 0; the artifact records `BASELINE_UNCOVERED: =` for each of the eight, which P7-T6 compares against.
+
+- [x] [P0-T12] Probe the CI-verbatim AC4 command shape once against the pre-change tree and record `/evidence/baseline/p0-t12-ci-shape-probe.md` (PROC-TRX): create `coverage/trx/p0-t12`, then run `& $vstest $Assemblies /EnableCodeCoverage /InIsolation "/Logger:trx;LogFileName=p0-t12.trx" /ResultsDirectory:coverage/trx/p0-t12 $Blame "/TestCaseFilter:$FullFilter"` (no `/Settings:`; parallelism from the assembly attribute, as in CI). Acceptance: `coverage/trx/p0-t12/p0-t12.trx` exists; counters recorded; wall-clock recorded as `PROBE_SECONDS: `; the artifact records exactly one of `AC4_COMMAND_SHAPE: CI-VERBATIM` (every failing test, if any, is one of `TryAddValuesAsync_UpdatesExistingValue`, `GetEmailDataInViewAsync_SeparatesTableSnapshotFromDataFrameTransform`, `Main_RunsSampleScenarioWithoutThrowing`, `DataFramePrettyHelpers_RenderRowsMarkdownAndConsoleOutput`, `PrintTree_WritesIndentedTreeToConsole`, `EnumerateTable_WritesFormattedOutputAndMovesToStart`, i.e. the three failure modes this item repairs) or `AC4_COMMAND_SHAPE: RUNSETTINGS-FALLBACK` (any other failure; the artifact lists the names, and every P8 run then adds `/Settings:TaskMaster.runsettings`, which carries the repo-root coverage module exclusions and class-level parallelism for every assembly, a heavier load than CI). `.coverage` files under `coverage/trx/p0-t12` are deleted after the counters are read.
+
+### Phase 1 — Clock and delegate seams with no behaviour change
+
+- [x] [P1-T1] Edit `UtilitiesCS/OutlookObjects/Table/OlTableExtensions.Etl.cs`: (a) add a trailing parameter `TimeProvider? timeProvider = null` to `EtlAsync` (signature lines 66-73) preceded by a two-line `//` comment stating that null resolves to the system clock and tests inject `FakeTimeProvider`; (b) delete `var attempts = 3;` at line 82; (c) in the `EtlByRowAsync` call at lines 98-106 replace the `attempts,` argument with `timeProvider,`; (d) line 114 becomes `.TimeoutAfter(milliseconds, timeProvider);`; (e) the log at lines 119-121 becomes `logger.Error($"{nameof(EtlAsync)} timed out with a timeout of {milliseconds} milliseconds. Canceling");` (drops the false retry count and the banned `DateTime.Now`); (f) in the private seven-parameter `EtlByRowAsync` at lines 229-237 replace `int attempts` with `TimeProvider? timeProvider`, and lines 245 and 259 become `.TimeoutAfter(timeout, timeProvider);`. `EtlAsyncOld` (lines 132-169) and the four-parameter `EtlByRowAsync` (line 171) are not edited (D9, D14). Acceptance, by `Select-String -SimpleMatch` counts on this file: `TimeoutAfter(milliseconds, timeProvider)`=1, `TimeoutAfter(timeout, timeProvider)`=2, `TimeoutAfter(milliseconds, attempts)`=1 (the retained `EtlAsyncOld` line), `TimeoutAfter(timeout, attempts)`=0, `var attempts = 3;`=1, `timed out {attempts} times`=1, `DateTime.Now`=1, `TimeProvider? timeProvider = null`=1, `timed out with a timeout of`=1; line count at most 480.
+
+- [x] [P1-T2] Edit `UtilitiesCS/Extensions/DfDeedle.cs` `GetEmailDataInViewAsync` (lines 139-144): add a trailing parameter `TimeProvider? timeProvider = null` with a two-line `//` comment; line 176 becomes `await AddQfcColumnsAsync(table, currentFolder!, token, 0, timeProvider: timeProvider);`; the `EtlAsync` call at lines 180-185 gains the named argument `timeProvider: timeProvider`; line 208 becomes `.TimeoutAfter(1000, timeProvider);`. No guard is added in this task. Acceptance by counts on this file: `TimeoutAfter(1000, timeProvider)`=1, `TimeoutAfter(1000, 2)`=1 (the retained commented-out line 214), `timeProvider: timeProvider`=2, `TimeProvider? timeProvider = null`=1, `was not produced`=0.
+
+- [x] [P1-T3] Edit `UtilitiesCS/Extensions/DfDeedle.cs` to replace the two ETL statics: delete lines 62-84 (`TableEtlInvoker` and `StoreTableEtlInvoker` with their doc comments); add, in their place, one `private static readonly Func columnInfo)> DefaultTableEtl = t => ((Outlook.Table)t).ETL();` with a doc comment stating it is the production ETL and that `object` avoids CS1769; change `GetEmailDataInView(Explorer activeExplorer)` (line 86) to `GetEmailDataInView(Explorer activeExplorer, Func