Skip to content

refactor(782): consolidate the PR #778 post-merge review residuals into one delivery - #789

Merged
drmoisan merged 28 commits into
mainfrom
refactor/pr-778-post-merge-review-residuals-782
Sep 6, 2026
Merged

refactor(782): consolidate the PR #778 post-merge review residuals into one delivery#789
drmoisan merged 28 commits into
mainfrom
refactor/pr-778-post-merge-review-residuals-782

Conversation

@drmoisan

@drmoisan drmoisan commented Sep 6, 2026

Copy link
Copy Markdown
Owner

Suggested title

refactor(782): consolidate the PR #778 post-merge review residuals into one delivery

Summary

  • Consolidates every actionable finding from the three-phase post-merge code review of PR fix(UtilitiesCS): guard UiThread dispatcher null-race in ProgressTrackerAsync #778 into a single Refactor delivery, so that none is lost when the Bug: uithread-dispatcher-null-race-progresstrackerasync #584 feature folder is archived. The review returned zero Blocking findings; this closes the 7 Should-fix items, 25 Nits, and 2 optional cleanups it left behind.
  • Makes UiThread.Dispatcher read its backing field exactly once, documents the deliberate non-lazy contract in XML comments, and routes both not-initialized throw sites through one shared internal const message that names only the public Init() entry point and states the STA requirement.
  • Reduces the UtilitiesCS.Test manipulation of the UiThread._dispatcher static from four independent reflection sites to a single disposable install scope with one reflection acquisition, and moves QuickFiler.Test onto the existing UiThreadDispatcherFixture.Current accessor.
  • Removes the last unshut dispatcher on a pooled MTA worker thread, splits the one test file that exceeded the repository's 500-line limit, and corrects comments and reason strings that still described the pre-fix(UtilitiesCS): guard UiThread dispatcher null-race in ProgressTrackerAsync #778 NullReferenceException mechanism.
  • Repairs nine internal inconsistencies in the Bug: uithread-dispatcher-null-race-progresstrackerasync #584 feature folder's audits and evidence, including a misstated formatter command, an evidence count that was wrong by four, and fifteen EXIT_CODE: fields that did not parse as integers.
  • Withdraws one finding, C03, after execution measured a reproducible regression that it caused; the withdrawal is recorded with its bisect and promoted forward rather than dropped.

Why

PR #778 changed UiThread.Dispatcher from a null-returning accessor to one that throws InvalidOperationException. The subsequent three-phase review confirmed the fix and found no functional regression, but produced 26 code findings plus a group of documentation and evidence defects. Individually none justified its own delivery. Collectively they were about to be archived along with the #584 feature folder and lost.

Two of the findings were latent defects in the new code rather than hygiene: a test that created a dispatcher on a pooled MTA thread and never shut it down, and a double read of the non-volatile static inside the new getter, where the guard and the return value could observe different values.

What Changed

Production, 5 files, +52/-15

  • UtilitiesCS/Threading/UiThread.cs — single-read getter (C02); internal const string DispatcherNotInitializedMessage shared with the sibling throw site (C20); message names only Init() (C06) and states the STA/UI-thread requirement (C09, message half); <summary>, <remarks> and <exception> XML documentation on a file that previously carried none (C08); a comment recording why this accessor deliberately does not self-heal where its two siblings do (C05).
  • UtilitiesCS/OutlookObjects/Folder/WpfDispatcherYield.cs — throw routed through the shared constant; the false comment corrected (C20).
  • UtilitiesCS/Threading/ProgressTracker.cs, ProgressTrackerAsync.cs — the InvokeAsync lambdas use the already-captured dispatcher instead of re-reading the static (C23).
  • TaskMaster/Ribbon/RibbonViewer.EngineCommands.cs — the two now-dead dispatcher != null comparisons removed (C01).

Tests, 10 files plus the test project, +690/-385

  • New UtilitiesCS.Test/TestHelpers/UiThreadDispatcherScope.cs, one disposable install scope holding the only reflection acquisition; the four previous sites migrate to it and it restores the prior value even when that value was null (C12, C13).
  • UtilitiesCS.Test/Threading/ProgressTracker_Tests.cs split into a partial class pair, following the four-file TimeOutTask_Tests precedent, so both halves sit under the 500-line limit and every fully-qualified test name is preserved (C16, C15). The new file is registered as a <Compile Include>.
  • UiThread_Tests.cs obtains its sentinel dispatcher on a dedicated STA thread inside a using, so BeginInvokeShutdown and the thread join run on every exit path including a failing assertion (C10, C11).
  • New regression tests for the WpfDispatcherYield production fallback and for both ProgressTrackerAsync.InitializeAsync and the synchronous ProgressTracker.Initialize (C21, C26).
  • EmailMoveMonitorTests.cs reads through UiThreadDispatcherFixture.Current instead of its own FieldInfo, so the order-independence guard fails rather than silently degrading if the field is renamed (C18, C25).
  • Cleanup added to IdleActionQueue_Tests, with the class serialized because the cleanup mutates process-global Application.Idle state shared with two sibling classes (C14).
  • Stale NullReferenceException prose corrected in IdleAsyncQueue_Tests.cs and QfcItemController.InitializationTests.Part2.cs (C19, S2-1).

Documentation and evidence, 23 files in the #584 folder

Ordering prose the timestamps contradict (S3-1); the formatter-command cells, which claimed a whole-tree run where the plan ran CSharpier over six explicit paths (S3-2); the evidence count corrected from 34 to the tracked figure of 38 (S3-3); a filename/Timestamp: mismatch noted in place rather than by renaming a committed file (S3-4); fifteen non-integer EXIT_CODE: fields normalized (S3-5); spec.md status and file lists reconciled (S3-6); call-site counts corrected to 49 live reads across 25 production files, with the review body's 26-file figure recorded as a divergence rather than silently adopted (S3-7); six evaluative spans replaced with neutral phrasing (S3-8); and the ProgressTrackerAsync synchronization follow-up recorded as discharged by the shared install scope rather than by the new test (S3-9).

Architecture / How It Fits Together

UiThread remains a static holder for UI-thread state captured once at host startup. The change tightens two things around it without altering the initialized path.

First, the not-initialized precondition now has one message with one owner. UiThread.Dispatcher and WpfDispatcherYield's local guard both throw InvalidOperationException(UiThread.DispatcherNotInitializedMessage). The constant is internal, both throw sites are in the same assembly, and UtilitiesCS.Test reaches it through the existing InternalsVisibleTo grant. The WpfDispatcherYield message loses its caller-specific tail as a direct consequence, which is intended and is asserted by the tests rather than left to inspection.

Second, test manipulation of the process-global _dispatcher static is funnelled through UiThreadDispatcherScope, which is the only place in UtilitiesCS.Test that acquires the FieldInfo. It is IDisposable, restores the captured prior value on dispose including when that value was null, and offers an explicit InstallNull() so a test can establish a known prior rather than depending on whatever a sibling assembly left behind. That matters because QuickFiler.Test and UtilitiesCS.Test share a test host in the full-suite run.

UiThread.Init() itself is byte-identical to its base form. See Follow-ups.

Verification

Completed

The full C# toolchain passed in a single uninterrupted pass, in the order CLAUDE.md requires:

  • dotnet tool run csharpier format . — exit 0, Formatted 1583 files, before and after tree images byte-identical.
  • dotnet tool run csharpier check . — exit 0, Checked 1583 files.
  • msbuild TaskMaster.sln /t:Rebuild /m /p:Configuration=Debug "/p:Platform=Any CPU" /p:EnableNETAnalyzers=true /p:EnforceCodeStyleInBuild=true — exit 0, 0 Warning(s), 0 Error(s).
  • msbuild TaskMaster.sln /t:Rebuild /m /p:Configuration=Debug "/p:Platform=Any CPU" /p:TreatWarningsAsErrors=true — exit 0, 0 Warning(s), 0 Error(s), all 19 projects recompiled so the gate was not vacuous.
  • Nine test assemblies under dotnet-coverage collectTotal tests: 7000, Passed: 7000, Failed: 0.

Coverage did not regress. First-party line coverage is 112351/132961 and branch 26498/33480, byte-equal to the baseline on all four counters with equal denominators. Changed-line coverage over the implementation phases was 7 of 7 executable lines.

Two review cycles were run against the branch. Both returned PASS with zero Blocking findings. The second re-executed every gate independently rather than reading the reported figures, re-aggregated four Cobertura documents under two selections, and read the TRX counters directly.

UtilitiesCS/Threading/UiThread.cs reads 77.11% to 76.83% on modified-file coverage. That is arithmetic, not a regression: the baseline and post-change uncovered line sets are the identical 19 lines, and the movement comes from a covered three-line wrapped throw collapsing to one line against a fixed residue in untouched host-bound members. No line moved from covered to uncovered.

Recommended

  • Re-run the four toolchain commands above.
  • vstest.console.exe over the nine test assemblies with /InIsolation.

Note for local reproduction: this repository pins its SDK through global.json, so dotnet must be invoked with the repo-local .dotnet-sdk on PATH and DOTNET_ROOT set. Local runs also exclude four UtilitiesCS.Test shell-icon classes that stall SHGetFileInfo on some workstations; CI covers them, so the CI total exceeds the 7000 figure above.

Backward Compatibility / Migration Notes

No public API signature changed. UiThread.Dispatcher keeps its non-nullable declared type and its private setter.

One observable change: the InvalidOperationException message text. It now names only the public Init() rather than the private Initialize(), states the STA/UI-thread requirement, and is shared by both throw sites. WpfDispatcherYield's previous message carried a caller-specific tail, "before yielding folder tree work", which the shared constant necessarily drops. Both assertions on that message were converted from a wildcard to an exact comparison against the constant, and the conversion was verified by mutation: appending the removed tail at the throw site makes the test fail, and reverting makes it pass.

No file was renamed and no evidence file's Timestamp: was altered.

Risks and Mitigations

  • Shared message constant reduces diagnostic specificity at the WpfDispatcherYield site. Intended per the review finding. Mitigated by the exact-match assertions, which now detect a caller-specific tail reappearing at either site.
  • The new install scope serializes test writers of a process-global static. The scope restores the prior value on every exit path including the null case, and the affected classes carry [DoNotParallelize]. The full nine-assembly suite passes.
  • The ProgressTracker_Tests split could have dropped a test. Mitigated by asserting all 24 original fully-qualified test names are present and passing after the split; the partial class shape preserves every name.
  • Rollback is a plain revert of this branch. No migration, no data change, no configuration change.

Review Guide

Suggested order:

  1. UtilitiesCS/Threading/UiThread.cs — the shared constant, the single-read getter, and the XML documentation.
  2. UtilitiesCS/OutlookObjects/Folder/WpfDispatcherYield.cs — the second throw site.
  3. UtilitiesCS.Test/TestHelpers/UiThreadDispatcherScope.cs — the new seam, and in particular its dispose semantics.
  4. The four migrated reflection sites and EmailMoveMonitorTests.cs.
  5. The ProgressTracker_Tests split, which is the largest diff and is a mechanical move.
  6. The Bug: uithread-dispatcher-null-race-progresstrackerasync #584 documentation corrections, which are independent of the code and can be read separately.

The ProgressTracker_Tests.cs / ProgressTracker_ReportAndViewerTests.cs pair is the noisy part of the diff: 288 added and 249 removed lines that are almost entirely a move.

The delivery's own audit trail is under docs/features/active/2026-09-05-pr-778-post-merge-review-residuals-782/, including the plan, the research record, the evidence tree, and both review cycles' artifacts.

Follow-ups

  • Issue Bug: uithread-init-accepts-non-sta-callers #787 carries the behavioural half of finding C09: UiThread.Init() accepts a call from any thread and installs a non-pumping dispatcher into set-once process-global state. Deliberately out of scope here, because it is a production behavior change that breaks one existing test call site.
  • Issue Bug: uithread-init-latch-not-rearmed-after-failed-initialize #788 carries finding C03, which this delivery withdrew. C03 asked that the single-shot latch be re-armed when Initialize() throws so a failed initialization could be retried. Implementing it caused DictionaryExtensions_Tests.TryAddValuesAsync_UpdatesExistingValue to fail reproducibly, bisected to the single re-arm line: with it, UtilitiesCS.Test plus TaskMaster.Test returned 5179/5180 at a 21-second duration; without it, 5180/5180. The mechanism is that the UiSyncContext and AutoScaleFactor getters call Init() lazily, so re-arming turns a one-time expensive WinForms failure into a repeated one on every accessor read, starving the thread pool. The entry records the measurement and three candidate approaches so a future attempt does not repeat the naive form.
  • Two items belong upstream in the drm-copilot repository rather than here, and are recorded in the delivery's evidence: finding S4-1, stale notes under .claude/agent-memory/task-researcher/, and the S3-1 request to define Timestamp: semantics in the evidence-and-timestamp-conventions skill. Nothing under .claude/ is modified by this branch.
  • Two of this delivery's own documents, the plan and the research record, embed an absolute host path in prose describing where a command ran. Left in place deliberately: both are completed historical records, 827 documents already on main carry the same pattern, and no repository rule codifies the prohibition. Disclosed rather than silently dropped.
  • Findings C04, C07, C17, C22, C24 and S4-2 were refuted by the review and need no action.

GitHub Auto-close

drmoisan and others added 28 commits September 5, 2026 20:38
… folder

Promote the consolidated PR #778 post-merge review findings to issue #782,
create the active refactor feature folder, and untrack the stale
orchestrator checkpoint that had been committed from the #704 recovery
worktree (artifacts/ is gitignored; the checkpoint is session-local state).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016RGpFtBp79mAkJp2vGwmqU
Adds the research record for issue #782, which verifies every finding
from the three-phase post-merge review of PR #778 against the current
tree and reports seven divergences from the requirements source.

Adds spec.md (12 acceptance criteria) and user-story.md, required for
full-feature work mode, plus a local verbatim copy of the PR #778 body
so the finding text is available without network access.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016RGpFtBp79mAkJp2vGwmqU
Adds the 9-phase, 101-task atomic plan for issue #782 after two
preflight revision rounds, and amends the spec's AC5 evidence method.

AC5 previously named the token GetField("_dispatcher", which matches no
single line anywhere in the tree because CSharpier wraps all six
acquisition sites. The criterion could not fail. It now names the
wrap-tolerant token "_dispatcher" scoped to *.cs files, matching the
plan tasks that verify it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016RGpFtBp79mAkJp2vGwmqU
Applies 46 defects reported across four atomic-executor preflight
rounds. The plan now clears preflight with PREFLIGHT: ALL CLEAR and
CONVERGENCE: NO FURTHER ROUNDS EXPECTED.

The substantive corrections were: the thirteen Phase 0 baseline
artifacts were never staged, which made both terminal porcelain gates
unpassable; the shared test seam used nullable annotations that raise
CS8632 in this repository and would have failed the nullable build; a
mandated document edit recorded 2026-09-05 as the PR #778 merge date
where the merge commit dates to 2026-09-04; three baseline counts were
pinned as hard equalities with no record-and-continue escape; and three
tasks justified a TestResults removal on the premise that CSharpier
would process its output, which is false because CSharpier 1.2.6
honours .gitignore.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016RGpFtBp79mAkJp2vGwmqU
- add a shared dispatcher-not-initialized message and a strict single-read `UiThread.Dispatcher` guard; align the `UiThread` assertion with the new `Init()` wording
- route `WpfDispatcherYield` and `ProgressTracker*` through the shared or already-captured dispatcher and remove dead null checks in ribbon command invalidation
- capture Phase 0 baseline evidence and update the #782 plan/spec to record corrected baselines and the documented omission of the C03 latch re-arm

Refs: #782
Execution reached P1-T9 and could not pass it. The plan's implementation
of finding C03, which re-arms the UiThread.Init() single-shot latch so a
failed Initialize() can be retried, causes
DictionaryExtensions_Tests.TryAddValuesAsync_UpdatesExistingValue to
fail reproducibly. The executor bisected it to the single re-arm line:
with it, UtilitiesCS.Test plus TaskMaster.Test returns 5179/5180 with
that test failing at a 21-second duration; without it, 5180/5180. The
branch base passes 6992/6992 both before and after.

The UiSyncContext and AutoScaleFactor getters call Init() lazily when
their backing field is null, and Initialize() constructs a
SyncContextForm and shows it. Without the re-arm the latch stays set
after a first failure and later Init() calls are cheap no-ops; with it,
every read of either accessor retries the WinForms construction and
throws again, starving the thread pool and defeating a 500 ms
CancelAfter.

C03 is withdrawn under the omission branch AC2 already carries. The
retry semantics will be promoted as a separate follow-up entry, in the
same way the issue already carved out the C09 behavioral half.

Also folds in four execution-time corrections: the CoreCompile gate now
asserts a deterministic per-project sub-count instead of an unstable
header equality; two baseline figures are corrected to the executor's
measurements; the hook-blocked Remove-Item form is replaced; and the
coverage line-counting selection is pinned.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011ucgeqsVLVSVbmJfkDzcBs
The branch was rebased from a007f72 onto main at 77c6d31 mid-run,
which orphaned the pre-782-base tag and staled every Phase 0 baseline.
The tag is re-anchored to 736c2cf, the last documentation-only commit
before the implementation commit, and verified as an ancestor of HEAD
whose source tree is byte-identical to origin/main.

The baselines were re-measured at that anchor and the affected Phase 0
tasks are unchecked for re-recording: csharpier now reports 1581 files,
the suite reports 6997 passing, and first-party coverage is
112355/132967 line and 26500/33480 branch. The 34-commit main advance
touches no file in this delivery's write set.

A further preflight round removed five defects. Two were blocking and
of the same class: both promotion-state gates searched
docs/features/potential/promoted without excluding this delivery's own
promoted entry, which carries the tokens they match and an issue line
naming #782, so each would have fired its positive branch before any
promotion occurred. One of the two would have checked off AC8 on the
strength of an unrelated 2026-08-07 WebView2 entry. The third blocking
defect was a preamble that still told the executor to create the anchor
tag at the branch tip, which P0-T2 now prohibits because it would make
every anchored gate compare a tree against itself.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011ucgeqsVLVSVbmJfkDzcBs
…dead guards

Addresses issue #782 findings C01, C02, C05, C06, C08, C09-message, C20, and C23. Adds the shared UiThread.DispatcherNotInitializedMessage constant, rewrites the Dispatcher getter to read the backing field once, corrects the WpfDispatcherYield comment and message, updates the breaking UiThread_Tests assertion, applies the lambda-capture fix in both ProgressTracker files, and removes the two dead null comparisons in RibbonViewer.EngineCommands.cs.

Finding C03 is deliberately absent from that list. SD18 withdraws it after a measured, bisected regression, so this phase changes no line on its account and naming it would misdescribe the commit. The omission is recorded in the Phase 6 code-review artifact.

Also carries the Phase 0 baseline evidence, re-recorded under SD23 against the re-anchored pre-782-base at 736c2cf.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011ucgeqsVLVSVbmJfkDzcBs
…ributes

Addresses issue #782 findings C15 and C16. Moves the P74 Report/child/root-close region out of UtilitiesCS.Test/Threading/ProgressTracker_Tests.cs into a new partial-class file ProgressTracker_ReportAndViewerTests.cs, bringing the 514-line source file to 272 lines and the new file to 260. Splits the combined [TestClass, DoNotParallelize] attribute into two separate attribute lines.

The region was extracted mechanically rather than retyped, so no test method name, attribute, or assertion changed. A scoped run of the 24 ProgressTracker_Tests cases confirms all 24 reassemble under their original fully-qualified names and pass.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011ucgeqsVLVSVbmJfkDzcBs
…install scope

Phase 3 of issue #782. Replaces five hand-rolled reflective acquisitions of the
private static UiThread dispatcher backing field with a single shared install
scope, and corrects the documentation that described the pre-#778 mechanism.

- C12/C13: UtilitiesCS.Test/TestHelpers/UiThreadDispatcherScope.cs now owns the
  only reflection site in that assembly. IdleAsyncQueue_Tests reimplements its
  ForceDispatcherNull/RestoreDispatcher helpers over the scope and drops its own
  DispatcherField() helper.
- C10: the populated-branch UiThread_Tests case obtains its sentinel dispatcher
  from a dedicated STA host that shuts down and joins, instead of calling
  Dispatcher.CurrentDispatcher on a pooled MTA worker.
- C11: the null-branch assertion lambda is expression-bodied.
- C18/C25: EmailMoveMonitorTests reads the existing QuickFiler.Test fixture
  accessor and takes a typed Dispatcher snapshot; the two stale "avoid
  WindowsBase" clauses are removed.
- C19: the three P27-T2 passages now describe the synchronous
  InvalidOperationException path rather than a NullReferenceException.

Repository-wide reflective acquisitions of the backing field fall from six to
two, the two being the UtilitiesCS.Test scope and the QuickFiler.Test fixture
that a separate assembly must keep.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011ucgeqsVLVSVbmJfkDzcBs
Phase 4 of issue #782.

- C14/SD7: IdleActionQueue_Tests gains [DoNotParallelize] and a [TestCleanup]
  that reuses the existing ResetStaticState() helper and additionally detaches
  the queue's idle handler, which is rebuilt over the private static method
  because the registered delegate instance is not nameable from the test.
- C21: new WpfDispatcherYieldTests.YieldAsync_ProductionFallbackWithoutDispatcher_ThrowsNamingInit
  exercises the production fallback provider from a dedicated fresh thread, so
  the thread-affinitized lookup cannot pre-empt it. The class gains
  [DoNotParallelize] because it now installs into the process-global static.
- C26: two new tests pin the guard on both initialization entry points -
  ProgressTrackerAsync.InitializeAsync faults its returned task, and
  ProgressTracker.Initialize throws synchronously, so the assertions differ in
  shape.
- C20: the existing strict-contract assertion now pins the message with
  WithMessage("*UiThread.Init()*").
- S2-1: the QfcItemController Arrange comment no longer claims both dispatcher
  cases fail the same way; it now distinguishes the accessor throw from the
  never-pumped parked dispatcher.

Fail-before and pass-after evidence for all three new tests is recorded under
evidence/regression-testing/, together with an exception dossier for C10 and
C02, whose hazards cannot be demonstrated by a deterministic failing test
without violating the test-independence and no-timing-construct rules.

Nine-assembly locally-filtered run: 7000 total, 7000 passed, 0 failed, against
a recorded baseline of 6997 plus the three added tests.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011ucgeqsVLVSVbmJfkDzcBs
Phase 5 of issue #782. All edits are made in place; no #584 evidence file is
renamed and no existing Timestamp: value is altered.

- S3-6: #584 spec Status moves from Draft to Merged (PR #778, merge commit
  1c3b210, 2026-09-04), and the three disagreeing file lists are reconciled -
  the in-scope list is extended to the authoritative six-file Write Set and the
  "Files/modules to change" section now cross-references the Write Set instead
  of carrying a third enumeration.
- S3-7/SD10: the three ~40/~62 call-site estimates are replaced with the
  verified figure, stated against the pre-782-base tag so it stays true after
  Phase 1 removes two live reads.
- S3-1: four passages asserting an execution order the recorded timestamps do
  not establish are restated without the ordering claim; the conclusion never
  depended on it, because the sibling positive test passed in the same run.
- S3-2: the two formatter command cells now record the six-path invocation that
  actually ran, Appendix B is labelled as a reference rather than a transcript,
  and a new section 8 gap entry records the deviation and its whole-tree
  read-only mitigation.
- S3-3: the evidence-artifact count is corrected from 34 to 38.
- S3-4: the issue-update mirror gains a note explaining why its filename
  timestamp and its Timestamp: field differ and why neither is changed.
- S3-5/SD3: fifteen EXIT_CODE fields are normalized to a single integer with
  the per-command breakdown preserved below; the two gates whose success
  outcome is non-zero additionally declare ExpectedExitCode.
- S3-8: seven evaluative spans prohibited by the tonality rule are replaced
  with evidence-first wording.
- S3-9/SD9: #584 finding F5 is dispositioned in both artifacts as discharged by
  C12 and C13 rather than C26, with the record that it was never promoted.

The P5-T14 gate artifact records one deviation for the caller's attention: the
P5-T10 premise that all ten remaining per-command values were 0 does not hold
for three files, whose zero-match search commands recorded 1. The instruction
was followed literally and every original value is preserved verbatim below the
normalized field.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011ucgeqsVLVSVbmJfkDzcBs
Phase 6 of issue #782.

- evidence/other/code-review.2026-09-05T23-00.md carries a disposition row for
  every finding identifier - C01 through C26, S2-1, S3-1 through S3-9, S4-1 and
  S4-2 - naming the file that changed or the recorded reason none did, and the
  commit that carried it. It additionally records nine labelled entries: the
  C03 omission with its measured regression, bisect and mechanism; the SD5
  message-tail change; the SD4 retained test-method naming inaccuracy; the SD10
  file-count divergence from the PR #778 review body; the SD9 attribution of
  #584 finding F5; the two SD14 supersessions of spec Constraint 8 clauses; the
  SD7 justification for the added serialization attribute; and the SD17
  coverage-collection deviation.
- evidence/other/upstream-followups-drm-copilot.2026-09-05T23-02.md records the
  two items that belong to drm-copilot: the S4-1 stale agent-memory notes and
  the S3-1 request to define Timestamp: semantics. Both live under .claude/,
  which is overwritten by push-down, so neither is edited here.
- evidence/qa-gates/p6-t3-dotclaude-untouched.md records the .claude/ gate. Its
  committed-history condition holds with zero lines. Its worktree condition does
  not hold: two paths under .claude/agent-memory/atomic-planner/ are dirty,
  written by the planner at 22:17 before this executor's first commit at
  22:32:36. P6-T3 is left unchecked and the residue is reported rather than
  worked around.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011ucgeqsVLVSVbmJfkDzcBs
Phase 7 pass 1. The whole-tree `dotnet tool run csharpier format .` removed one
blank line between the closing #endregion and the class's closing brace in
UtilitiesCS.Test/Threading/ProgressTracker_Tests.cs.

The residue is from the Phase 2 split, which moved the trailing region out of
this file. P2-T1's format run was scoped to the newly created part and did not
re-format the part the split left behind, so this whole-tree run is the first to
reach it.

The Phase 7 loop restarts from P7-T1 because a tracked file was rewritten.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011ucgeqsVLVSVbmJfkDzcBs
Phase 7 of issue #782. The loop ran twice.

Pass 1 did not close: the whole-tree formatter rewrote
UtilitiesCS.Test/Threading/ProgressTracker_Tests.cs, committed as 4744892, and
the loop restarted from step 1.

Pass 2 closed clean, all five steps green with no tracked-file rewrite after
step 1:

- format: Formatted 1583 files, before- and after-images byte-identical
- format check: exit 0, Checked 1583 files, baseline 1581 plus exactly the two
  files this delivery creates
- analyzer build: exit 0, 0 Warning(s), 0 Error(s), 18 project build-output
  lines against a recorded baseline of 18
- nullable build: exit 0, 0 Warning(s), 0 Error(s), 18 CoreCompileInputs.cache
  deletion lines against a recorded baseline of 18; the aggregate CoreCompile
  token count is recorded as an observation and is not gated, per SD19
- tests with coverage: exit 0, 7000 total, 7000 passed, 0 failed, 0 skipped

Coverage, first-party, all-descendant .//line selection pinned by SD22 so both
sides come from one method:

- line 112363/132961 = 84.51%, against a baseline of 112355/132967 = 84.50%
- branch 26500/33480 = 79.15%, against a baseline of 26500/33480 = 79.15%
- lines-valid differ by 6, or 0.0045%, so the two runs are comparable and the
  aggregate comparison is asserted rather than waived
- changed-line coverage is 7 of 7 executable added production lines, 100%, and
  the uncovered enumeration is empty
- RibbonViewer.EngineCommands.cs contributes zero executable changed lines
  because RibbonViewer.cs declares the partial type [ExcludeFromCodeCoverage];
  that is recorded rather than reported as a zero-coverage row

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011ucgeqsVLVSVbmJfkDzcBs
…mary

Phase 8 of issue #782.

Fifteen of the seventeen acceptance criteria are checked off in their source
documents. The two that remain unchecked were each resolved through their
task's explicitly gated Branch B and carry a verbatim deferral line:

- AC8, the C09 behavioural follow-up promotion. The filtered promoted-entry
  search returned zero files. Both mandatory exclusions fired as the plan
  predicted: the webview2 entry matches "apartment state" on line 86 under
  issue #476, and this delivery's own promoted entry matches "non-STA" on lines
  63 and 107 under issue #782.
- AC-U1, the pull request body. No pr_body_782.md exists.

Both are owned by the orchestrator and performed outside this plan.

evidence/other/ac-status-summary.2026-09-05T23-15.md is the single status
summary. It carries the two gated branch records with their full command output
and 17 criterion rows, each row's recorded checkbox state verified line by line
against the state actually present in spec.md and user-story.md, and every
cited artifact path confirmed present on disk.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011ucgeqsVLVSVbmJfkDzcBs
Phase 8 closure check for issue #782.

The format-check condition holds: Checked 1583 files, identical to the count
evidence/qa-gates/p7-t2-format-check.md records, confirming the Phase 8 Markdown
edits did not disturb the Phase 7 clean pass.

The additional confirmation holds: this task's subtracted porcelain output
contains no path under the Write Set and no path under the feature folder.

The byte-identity comparison does not hold. Two paths under
.claude/agent-memory/atomic-planner/ appear on the closing side and not on the
recorded baseline side, because the concurrent write landed after P0-T2 captured
the baseline image and before this task captured the closing one. P8-T20 is left
unchecked and the residue is reported rather than committed, deleted, or
reverted.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011ucgeqsVLVSVbmJfkDzcBs
Terminal task of the issue #782 plan. This commit performs no promotion; it
records which state the promotion is in.

Branch B: the filtered promoted-entry search returned zero files, so the
UiThread.Init() latch re-arm has not yet been promoted. The owner is the
orchestrator, which performs promotion outside this plan.

The mandatory exclusion fired as the plan predicted. This delivery's own
promoted entry, 2026-09-05-pr-778-post-merge-review-residuals.md, matches the
unfiltered pattern on line 56 with the token "single-shot latch" and carries
- Issue: #782 on line 7. Both were re-derived against the current tree. Without
the exclusion, Branch A would fire against this delivery's own issue number and
record a promoted state that is false.

C03 was withdrawn under SD18 after a measured regression, so the follow-up is
not a deferred implementation of the same change: it asks for the retry
semantics by a mechanism that does not re-arm the latch the UiSyncContext and
AutoScaleFactor accessors consume, which is a different design.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011ucgeqsVLVSVbmJfkDzcBs
The P8-T21 plan line previously stated the repeated comparison's outcome before
that comparison had been run. This commit replaces the prediction with the
observed figures: Checked 1583 files, CHECK_EXIT_CODE=0, PORCELAIN_RAW_COUNT=2,
SUBTRACTED_COUNT=2, WRITESET_OR_FEATURE_PATHS_IN_SUBTRACTED=0,
BYTE_IDENTICAL=False.

The plan file sits inside the four-path subtraction set, so amending it cannot
change the subtracted comparison and this record terminates rather than
requiring a further round.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011ucgeqsVLVSVbmJfkDzcBs
Both P6-T3 acceptance conditions now hold with zero lines. The two
.claude/agent-memory/atomic-planner/ paths that failed the porcelain
condition at the 23-03 capture were written by the atomic-planner, not
by this executor, and were cleared by the orchestrator with
`git checkout -- .claude/` and `git clean -fd .claude/`.

The artifact retains the superseded failing capture and its attribution
so the gate history stays auditable. Committed ahead of P8-T20 because
the artifact path is not in that task's four-path subtraction set.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011ucgeqsVLVSVbmJfkDzcBs
All P8-T20 acceptance conditions hold. The format check reproduces the
Phase 7 count exactly (Checked 1583 files, exit 0), and the porcelain
output is empty, so the four-path subtracted comparison is byte-identical
to the empty baseline image recorded in p0-t2-base-ref.md.

The byte-identity condition previously failed by the two
.claude/agent-memory/atomic-planner/ paths recorded in P6-T3, which the
orchestrator has since cleared. The artifact retains the superseded
failing capture and its attribution so the gate history stays auditable.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011ucgeqsVLVSVbmJfkDzcBs
Promotes both deferred follow-ups through the MCP promotion lifecycle,
which discharges AC8's first clause.

Issue #787 carries the C09 behavioural half: UiThread.Init() accepts a
call from any thread and installs a non-pumping dispatcher into
set-once process-global state. Promoted as a bug because it is a
missing precondition on an existing contract, not a new capability.

Issue #788 carries finding C03, which this delivery withdrew under
SD18. The entry records the measured regression, the bisect that
attributes it to the single re-arm line, the interaction with the two
lazy accessors that causes it, and three candidate approaches, so a
future attempt does not repeat the naive form. Recording the
withdrawal only as prose in a feature folder would have lost it when
the folder is archived.

AC8's second clause was already satisfied by the upstream follow-up
record written in Phase 6. The acceptance-criteria status summary is
appended rather than rewritten, so the executor's deferral record
stays intact as the true state at the moment its gate ran.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011ucgeqsVLVSVbmJfkDzcBs
Records the review of the branch against origin/main at 77c6d31.
Verdict is PASS with zero blocking findings.

The review re-ran the toolchain independently rather than accepting the
reported figures, and re-derived every coverage number from the raw
Cobertura documents. It also established the fact that makes the
UiThread.cs modified-file coverage row waivable: the baseline and
post-change uncovered line sets are identical, so the 77.11% to 76.83%
movement is arithmetic from a covered three-line wrapped throw
collapsing to one line against a fixed 19-line residue in untouched
host-bound members, not a regression. No delivery artifact had recorded
that comparison.

Four remediation inputs are recorded. Two are procedural coverage
triggers. Two are Should-fix accuracy defects in this delivery's own
artifacts and are addressed next.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011ucgeqsVLVSVbmJfkDzcBs
… input record

Addresses findings R3 and R4 of the feature review recorded in
remediation-inputs.2026-09-05T23-48.md. Both are Should-fix accuracy defects in this
delivery's own audit artifacts; neither blocks the pull request, which the review
cleared with PASS and zero blocking findings.

R3. spec.md AC10, spec.md AC11, the Behavioral Contract WpfDispatcherYield bullet, the
Write Set test-file row, and the code-review entry (b) claimed that the removal of the
WpfDispatcherYield message tail was pinned by the C20 WithMessage assertion. It was not:
both assertions were the wildcard "*UiThread.Init()*", which also matches the pre-782
message because that message likewise contains UiThread.Init(). Both assertions now read
WithMessage(UiThread.DispatcherNotInitializedMessage), which FluentAssertions compares
against the entire message, and the prose is corrected to state exactly what that form
establishes: a tail appended at a throw site fails the assertion at that site, and neither
assertion detects an edit to the constant's own wording. The change is observed rather than
derived: appending the removed tail at the WpfDispatcherYield throw site fails
YieldAsync_WithoutDispatcher_RemainsStrict while the sibling test still passes, and both
pass once the mutation is reverted.

R4. evidence/baseline/p0-t7-coverage.md recorded the re-measured first-party figures
112355 and 26500 while naming the earlier collection's output document as its input. The
artifact now records both collections with their own inputs and figures, marks the
re-measurement authoritative, states that its output document is not retained, and supplies
a reproduction procedure.

R1 and R2 are accepted and waived respectively, as maintainer decisions following the
reviewer's recommendations. No file was changed for either item. The dispositions are
recorded in evidence/other/r1-r2-maintainer-disposition.2026-09-06T00-15.md.

No production .cs file is changed. Full local toolchain passed in one uninterrupted pass:
csharpier format and check (1583 files, exit 0), analyzer build (0 warnings, 0 errors),
nullable build (0 warnings, 0 errors), and the nine-assembly run at 7000 passing with 0
failures. Those are locally-filtered figures with the four shell-icon classes excluded,
not CI figures.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011ucgeqsVLVSVbmJfkDzcBs
Adds the three Phase 5 artifacts that could not be part of the first commit:
r-p5-t3-staged-set.md records that commit's staged set and cannot be a member of the
set it records, and r-p5-t5-post-commit-verification.md and r-p5-t6-closure.md both
record the first commit's SHA and so could not exist before it. A second commit is used
rather than an amend for that reason.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011ucgeqsVLVSVbmJfkDzcBs
All 53 tasks of remediation-plan.2026-09-06T00-15.md are checked off. The check-offs for
[P5-T2] onward were written after the staging that produced commit b91dd85, so the plan
file's completion state could not be part of either earlier commit.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011ucgeqsVLVSVbmJfkDzcBs
The re-audit after remediation returns PASS with zero blocking
findings. It re-executed every gate rather than reading the reported
figures, reproduced all four coverage counters from the raw Cobertura
documents, and read the TRX counters directly, including the
falsification run that now backs the corrected message-pinning claim
with an observation rather than a derivation.

Fixes the two attribution defects the re-audit raised.

The R1/R2 disposition record was titled a maintainer disposition, but
the deciding party is the orchestrator and no maintainer ratification
was sought. The heading is corrected and the consequence stated: the
R2 coverage waiver rests on orchestrator judgment plus the reviewer's
identical-uncovered-line-set evidence, not on the ratification
CLAUDE.md requires for a formal COM/VSTO exemption. The filename keeps
its original token because the remediation plan's acceptance conditions
reference that exact path.

AC-U2 permitted two production behavior changes, one of which was
withdrawn under finding C03. The criterion was satisfied by a narrower
change than it allowed for, which is true but read as though the
withdrawn behavior had shipped. It now says so explicitly and points at
issue #788.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011ucgeqsVLVSVbmJfkDzcBs
AC-U1 requires one branch and one pull request delivering all in-scope
findings, with the body mapping every finding identifier to the file
that changed or to the recorded reason it did not. It was correctly
unchecked until the pull request existed. Pull request 789 satisfies
it, and the criterion now records which sections carry the mapping and
which identifiers each covers.

This is the last open acceptance criterion. Both sources are now fully
checked: 12 of 12 in spec.md and 5 of 5 in user-story.md.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011ucgeqsVLVSVbmJfkDzcBs
@drmoisan
drmoisan merged commit 7c8ac9a into main Sep 6, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Refactor: pr-778-post-merge-review-residuals

1 participant