Skip to content

Commit test-evidence projections instead of raw documents, and remove identity leaks from the vscode MSTest tooling - #881

Merged
drmoisan merged 15 commits into
mainfrom
bug/test-evidence-projection-convention-and-identity-leak-tooling-873
Sep 13, 2026
Merged

drmoisan merged 15 commits into
mainfrom
bug/test-evidence-projection-convention-and-identity-leak-tooling-873

Conversation

@drmoisan

Copy link
Copy Markdown
Owner

Suggested title

Commit test-evidence projections instead of raw documents, and remove identity leaks from the vscode MSTest tooling

Summary

  • The two vscode MSTest entry points no longer leave raw evidence as the committed artifact. The coverage entry point now writes a package-level JaCoCo projection beside the raw Cobertura document and both entry points write a test-result summary derived from the raw test-result document.
  • The raw coverage document is discarded under a stated invariant: it is kept when the resolved output directory is the repository coverage directory, and discarded when it is any other directory. The discard runs only after the threshold assertion, the projection write and the reconciliation assertion have completed.
  • The projection is reconciled against its source rather than trusted. Assert-JacocoProjectionReconciliation throws unless the summed package LINE counters equal the source document's root covered-lines and valid-lines attributes.
  • Both entry points now pass an explicit results directory and an explicit trx log file name, so the test console stops emitting a test-result document named from the operator account and host.
  • Four configuration and documentation corrections remove identity leaks that live in configuration rather than in historical evidence, and CLAUDE.md gains a section stating the permitted committed test-evidence formats.
  • All 23 acceptance criteria in the feature spec are satisfied and checked off.

Why

The maintainer recorded a committed test-evidence convention on #671 on 2026-09-11. Before this change the two entry points committed raw evidence: a raw test-result document carrying the operator account name, the host name and the absolute checkout path across six attributes, and a raw Cobertura document measured in tens of megabytes.

This item implements that convention and removes the identity leaks that sit in configuration. The tooling half of #602 and the configuration leaks tracked under #728 are in scope. The historical sweep of evidence already committed from earlier features is deliberately not in scope, and is discussed under Follow-ups.

CLAUDE.md is the home for the convention text because the evidence-and-timestamp conventions document and the atomic-plan contract are both push-down owned, so an edit to either would be reverted on the next push-down. The repository-root instruction file is owned here and is loaded into every agent session.

What Changed

Core logic

  • scripts/vscode/Invoke-MSTestWithCoverage.Projection.ps1 — new. Holds ConvertTo-JacocoPackageProjection (pure writer), Assert-JacocoProjectionReconciliation (pure assertion) and Test-RawCoverageDocumentRetained (pure predicate). It is a new part file rather than an addition to the helpers file because the helpers file had 29 lines of headroom against the repository's 500-line ceiling.
  • scripts/vscode/Invoke-MSTest.TrxSummary.ps1 — new. Holds Get-TrxRunSummary (pure reader over a test-result document string) and Format-TrxRunSummary (pure formatter). Dot-sourced by both entry points.
  • scripts/vscode/Invoke-MSTestWithCoverage.ps1 — the argument builder gains the results-directory and trx-logger switches on the test-console segment; the main function writes the projection, runs the reconciliation assertion, applies the conditional discard and writes the test-result summary.
  • scripts/vscode/Invoke-MSTest.ps1 — the same two switches on the plain path, plus summary write and raw-document discard.
  • scripts/vscode/Invoke-MSTestWithCoverage.Helpers.ps1 — one added dot-source line.

Tests

Four new Pester files and three existing files repaired. The three repairs are forced by the signature changes rather than chosen: adding two mandatory parameters breaks parameter binding at every existing call site, and array assertions change because the returned arrays gain two elements. Invoke-MSTest.RunSettings.Tests.ps1 was already 496 lines against the 500-line ceiling, so its call sites were converted to splatting, which is net line-reducing.

Configuration and documentation

  • TaskMaster/TaskMaster.csproj — the publish-destination element now carries the repository-relative value the other project file already uses for the same property.
  • .vscode/settings.json — the Power Query additional-symbols array element now begins with the editor workspace-folder variable and contains no drive letter.
  • CLAUDE.md — a new ## Committed Test Evidence Format section, and both test-console toolchain steps now name the explicit results-directory switch and log-file-name form.
  • Six agent-memory documents — five token substitutions plus one deliberate rewrite, and two added rules in the shared hygiene document.

Architecture / How It Fits Together

The design keeps the new logic pure and testable and leaves the entry points as thin wiring.

The coverage entry point resolves its output path and results directory by joining the supplied parameters against the repository root, runs the collector, post-processes the raw Cobertura document, then calls the projection writer on the post-processed content. The reconciliation assertion runs against the same post-processed source, so a projection that disagrees with its source fails the run rather than being written silently. The summary reader parses the test-result document and the formatter renders it. Only then does the retention predicate decide whether the raw document survives.

The ordering matters and is asserted in-process by a call-order test, because an end-to-end run emits no ordered progress output from which an order could be read.

Verification

Completed

PowerShell gates, recorded in this feature folder's evidence tree:

  • Format, analyzer and Pester gates pass. The analyzer comparison is set-based against the Phase 0 baseline rather than exit-code-based, because the analyzer exits 1 on any warning and a pre-existing plural-noun warning already exists in the helpers file.
  • New-code line coverage for the two new part files is at or above 90 percent for each, derived per file from the emitted JaCoCo document rather than from the runner's aggregate percentage.
  • Every PowerShell file in the change is at most 500 lines, and the helpers file grew by one line.

C# gates, re-run after this branch merged origin/main:

  • dotnet tool run csharpier check . — exit 0, 1627 files checked.
  • Analyzer rebuild — exit 0, 0 warnings, 0 errors.
  • Nullable rebuild — exit 0, 0 warnings, 0 errors.

All three match their Phase 0 baselines exactly. The merge altered C# compilation inputs, so these were re-run rather than carried forward from the pre-merge measurement.

End-to-end observation, two full runs of the coverage entry point over every discovered Debug test assembly:

  • Default output: 7222 tests, 7222 passed, 0 failed. Raw document retained, projection written beside it, reconciliation exact at 56066 covered and 65416 valid lines, test-result summary present.
  • Output pointed at a directory other than the repository coverage directory: 7222 tests, 7222 passed, 0 failed. Raw document discarded, projection and summary retained.
  • Neither run produced a test-result document bearing the default account-and-host name. Zero matches over both results-directory listings and zero over the changed-path union.

Repository coverage measured during the first run, against the CLAUDE.md floors of 80 percent line and 75 percent branch: first-party line coverage 85.71 percent and branch coverage 79.87 percent. Both clear.

Recommended

  • pwsh -NoProfile -File scripts/vscode/Invoke-MSTestWithCoverage.ps1 to reproduce the retention path.
  • The same entry point dot-sourced, calling Invoke-MSTestWithCoverageMain with -CoverageOutput and -ResultsDirectory pointed at a directory other than the repository coverage directory, to reproduce the discard path.

Backward Compatibility / Migration Notes

  • No public API is removed. Both argument builders gain two parameters, which is a breaking change for any in-repo caller; all in-repo callers are updated in this change and the three affected test files are repaired here.
  • The coverage entry point's ResultsDirectory parameter is exposed on the main function rather than on the script's top-level parameter block. A caller needing to override it dot-sources the script, which suppresses auto-invocation, and calls Invoke-MSTestWithCoverageMain directly.
  • Behaviour change for operators: the raw coverage document is no longer guaranteed to survive a run whose output is directed outside the repository coverage directory. Retention inside that directory is preserved deliberately, because the repository ignore file's own comment records that the editor coverage extension reads it.

Risks and Mitigations

  • Risk: the projection could silently disagree with the document it summarizes. Mitigation: the reconciliation assertion throws on disagreement and names both the expected and observed totals; both end-to-end runs reconciled exactly.
  • Risk: the discard could run before the projection is written, destroying the only copy. Mitigation: an in-process call-order test pins the discard after the threshold assertion, the projection write and the reconciliation assertion.
  • Risk: the three repaired test files could mask a regression through mechanical edits. Mitigation: no new describe block was added to any of them; new behaviour is asserted in the four new test files instead, so each existing file grows by the minimum the signature change forces.
  • Rollback: the change is additive in two new part files plus wiring, so reverting the wiring in the two entry points restores the previous behaviour without touching the new files.

Review Guide

Suggested order:

  1. scripts/vscode/Invoke-MSTestWithCoverage.Projection.ps1 and scripts/vscode/Invoke-MSTest.TrxSummary.ps1 — the new pure logic, and the place where correctness is decided.
  2. The wiring in scripts/vscode/Invoke-MSTestWithCoverage.ps1 and scripts/vscode/Invoke-MSTest.ps1, particularly the ordering around the discard.
  3. The four new test files.
  4. The three repaired test files. Invoke-MSTest.RunSettings.Tests.ps1 carries a splatting conversion across many call sites and is the noisiest diff in the change; it is mechanical.
  5. The configuration and documentation corrections.

Largely mechanical and safe to skim: the feature folder's evidence tree, which is the bulk of the changed-file count.

Note on the diff size: this branch merged origin/main, so the changed-path union against the original base anchor spans 195 paths, of which 91 are inherited from two items that merged to main after the anchor and are not authored here. The P6-T4 evidence artifact reconciles that figure against the earlier 100-path inventory in full.

Follow-ups

  • The historical sweep of test-result documents already committed from earlier features is tracked separately. More than one hundred are tracked and at least one still carries an unredacted default name, so it is deliberately excluded here; a tree-wide scan would fail regardless of this change. That sweep should run after this merges so a fresh run does not reintroduce the prefix.
  • Citing the new convention section from the atomic-plan contract's evidence tasks is an upstream change, because that contract file is push-down owned and an edit here would be reverted.
  • No feature-review artifacts (code-review, feature-audit, policy-audit) were produced for this item.

GitHub Auto-close

  • None

The context bundle reports GitHub validation unavailable and lists no verified autoclose issue, so no auto-close bullet is emitted. This work implements issue #873. The issue numbers #602, #646, #662, #671, #718 and #728 appear in the feature documents as background references and as scope boundaries; they are not auto-close targets for this pull request and should remain open.

drmoisan and others added 15 commits September 12, 2026 12:49
…jection-convention-and-identity-leak-tooling-873
…spec footprint

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…file

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…nd conditional discard

Phase 3 of the atomic plan. Get-DotnetCoverageArgumentList and
Invoke-DotnetCoverageCollection gain a mandatory results directory and log file
name; the builder appends the results-directory switch and the trx logger switch
to the inner test-console segment, after the argument separator, leaving the
existing /Settings:, /InIsolation and /TestCaseFilter: elements unchanged in
content and relative order.

Invoke-MSTestWithCoverageMain dot-sources the summary part file explicitly,
builds the JaCoCo projection from the post-processed content, writes it beside
the coverage output, runs the reconciliation assertion, writes the test-result
summary beside the test-result document, and only then applies the conditional
discard. A missing, unreadable or unparseable test-result document is a
non-fatal warning that suppresses both the summary write and the discard.

The two test files the signature change breaks are repaired in this same phase
so no gate runs a test path this change broke. The shared call-site file
converts ten coverage-family call sites to splatting from four argument sets, so
it absorbs two extra arguments per site and measures 491 lines against the
500-line ceiling with all 28 of its tests intact.

Toolchain: PoshQC format ok, analyzer diagnostic set unchanged at 16 with no
entry for any file this phase touched, whole-folder Pester 128 passed 0 failed 0
skipped. AC4, AC5 and AC14 checked off.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…d trx summary (#873 Phase 4)

Get-VsTestArgumentList gains two mandatory parameters, ResultsDirectory and
LogFileName, and appends a results-directory switch and a trx logger switch
carrying the explicit log file name, so the test-result document lands at a
path a later step can predict and read.

Invoke-MSTestMain defaults the results directory to coverage\test-results and
the log file name to mstest-run.trx, resolves both against the repository root,
dot-sources the summary part file, and after the existing exit-code check writes
the run summary beside the document and discards the raw document. A missing,
unreadable, unparseable or result-summary-free document is reported as a
non-fatal warning that suppresses both the write and the discard.

The four plain-builder call sites in the shared argument-builder test file are
converted to splatting from two hashtables, which reclaims the lines the two
added arguments would otherwise have cost and keeps the file under the 500-line
ceiling. The main test file's exact-array assertion is widened to six elements.

Toolchain: poshqc format, analyze and test, all over scripts/vscode and
tests/scripts/vscode. The loop restarted once: the first analyze pass reported a
third PSAvoidUsingWriteHost on the entry point, introduced by this phase, which
was changed to Write-Output. Final pass: format ok, analyzer 16 diagnostics
matching the Phase 0 baseline set tuple for tuple, Pester 131 passed 0 failed 0
skipped over the whole test folder.

Acceptance criteria checked off: AC12, AC13.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…er leaks (#873 Phase 5)

The project file's publish destination now carries the repository-relative
value the sibling project file already uses, replacing an absolute user-profile
path. The Power Query additional-symbols setting now resolves through the editor
workspace-folder variable instead of an absolute path into another checkout.
Both edits are paired with a parse check, because an absence count alone would
report success for an edit that broke the markup.

CLAUDE.md gains a Committed Test Evidence Format section naming the three
permitted projections and prohibiting the two raw documents, and both
test-console toolchain steps now name the explicit results-directory switch and
the explicit log-file-name form. Both steps were amended, because amending one
would leave the sibling contradicting it. The rule lives here rather than in the
push-down-owned conventions documents, which are reverted on the next push-down.

Five agent-memory files had a leaked account or host token substituted for an
angle-bracket placeholder. The sixth was rewritten rather than substituted: its
sentence used the token twice on one line to contrast a case-sensitive search
against a case-insensitive one, and one placeholder in both positions would have
destroyed the contrast. The shared hygiene rule file gains two numbered
obligations that its prior text recorded only as incidents.

Every after-state observation is recorded as counts only, with no token value in
any artifact, which is the rule this delivery exists to enforce. All five memory
files, the project file and the settings file return zero for both run-time
derived tokens. No PowerShell file changed, so no toolchain gate applies.

Acceptance criteria checked off: AC16, AC17, AC18, AC19.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…n on a pre-existing defect (#873 Phase 6, partial)

P6-T1 passes. The coverage entry point run in discovery-only mode reports nine
built Debug test assemblies, and an independent re-derivation of the same
discovery predicate enumerates the same nine repository-relative. The bare
integer is greater than zero, so the Phase 0 rebuild contingency did not apply.

P6-T2 is BLOCKED and its checkbox stays unchecked. The run executed 7221 tests
in 42.89 seconds; 7218 passed and three failed. All three belong to
QuickFiler.Controllers.Tests.QfcInitEmailQueueZeroBatchTests in the
QuickFiler.Test assembly, and each throws a TypeInitializationException for
Deedle.Reflection whose innermost cause is a FileNotFoundException for
netstandard 2.1.0.0. The throw site is the test class helper calling
Deedle.Reflection.convertRecordSequence, and the sub-millisecond elapsed times
identify it as an assembly-load failure at static-initialiser time rather than
an assertion failure.

The entry point throws on a nonzero test-console exit code before it reaches the
post-processing, the projection write and the summary write, so the run left the
raw Cobertura document and the TRX in the ignored coverage tree but produced
neither the projection nor the test-result summary this task observes. The
reconciliation integers are therefore not derivable.

The console banner records class-level parallelisation with 24 workers for the
QuickFiler.Test assembly, which is supplied by the off-root CLI runsettings the
argument builder appends as the inner /Settings: switch. The continuous
integration workflow passes no settings file, so the defect is invisible there.
Neither the runsettings file nor the /Settings: switch was altered: both sit
outside this delivery Write Set, and the plan Phase 6 flakiness attribution rule
requires recording and reporting a pre-existing failure rather than suppressing
it by editing a test outside the Write Set.

No file outside this feature folder changed. No raw coverage or test-result
document was added to version control. P6-T3 through P6-T5 stay unchecked, AC23
stays unchecked, and Phase 7 was not begun.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…check-off (#873 Phase 7)

Runs the full toolchain in order over the delivery. PowerShell format rewrote nothing;
the analyzer reports the same 16 diagnostics as the Phase 0 baseline with none absent
from it; Pester reports 133 passed, 0 failed, 0 skipped over the test folder against a
baseline of 103 passed. The C# format check exits 0 over 1626 files, and the analyzer
and nullable rebuilds each exit 0 with 0 warnings and 0 errors, matching their Phase 0
baselines.

New-code coverage is 92.86 percent line for Invoke-MSTest.TrxSummary.ps1 and 92.50
percent for Invoke-MSTestWithCoverage.Projection.ps1, both above the 90 floor. The
first measurement failed at 82.50 percent for the projection part file; two tests were
added to its test file, which is inside the declared Write Set, covering the valid-total
reconciliation throw and the no-parent guard clause, and the toolchain loop was
restarted from the format step with every preceding gate re-run and passing. Both the
failing measurement and the remediation are recorded rather than replaced.

Every PowerShell file is at most 500 lines, the largest being 498, and the helpers file
grew by exactly one line against its post-format baseline. The changed-file inventory
records 100 paths, one project file, and an empty executor agent-memory carve-out.

Checks off AC15, AC20, AC21 and AC22, taking the total to 22 of 23. AC23 stays
unchecked and is recorded as OUTSTANDING in the acceptance status summary, because
Phase 6 tasks P6-T2 through P6-T5 have not run. A disclosure artifact records that this
Phase 7 pass was taken with those tasks outstanding, names the tracked paths Phase 6
would modify, and records that none of them is a file any Phase 7 gate measures.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
 Phase 7)

Appends the delivery commit identifier, the eighteen-file summary, the empty
POST_COMMIT_PORCELAIN record with EXIT_CODE 0, and the executor agent-memory
disposition, which is empty so neither of that step's two commands was run.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This task's own check-off is a modification of a tracked file, so it is committed
separately and the clean-tree observation is taken after it rather than before it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ion-and-identity-leak-tooling-873 for Phase 6

Brings in item 880 (issue 877) QuickFiler.Test AssemblyResolve self-sufficiency and item 839
QfcHomeController.Init ordering fix. The 877 change is the remediation for the pre-existing
netstandard 2.1 Deedle assembly-load failure that aborted the first P6-T2 attempt.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…e 6)

Runs P6-T2 through P6-T5 after merging origin/main a5622ab, which carried the
issue 877 AssemblyResolve fix that repaired the netstandard 2.1.0.0 bind failure
halting the first P6-T2 attempt. No file in this delivery's Write Set was changed
to make the run pass.

P6-T2 default-output run: 7222 tests, 7222 passed, 0 failed. Raw document retained,
projection written beside it, reconciliation exact at 56066 covered and 65416 valid
lines, test-result summary present.

P6-T3 external-output run: 7222 tests, 7222 passed, 0 failed. Raw document discarded,
projection and summary retained, which is invariant 4's discard branch.

P6-T4 default-name scan: zero matches over both results-directory listings and zero
over the 195-path changed-path union. The artifact carries the full post-merge
provenance and reconciles the union against the pre-merge P7-T9 inventory of 100:
plus 91 inherited from the merge, plus 4 own artifacts authored after P7-T9 ran.

Also re-runs the three C# gates against the merged tree, since the merge altered
compilation inputs that the pre-merge Phase 7 gates measured. Format, analyzers and
nullable all return results identical to their Phase 0 baselines.

AC23 is checked off, taking the item to 23 of 23.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Delegated to Agent(feature-review) after the coordinator identified the missing review as a gap in its definition of done rather than in this run's execution. The prior step7_status was the invalid literal not-run, recorded with a rationale rather than a passing value.

Verdict PASS on all three artifacts, 0 Blocking and 11 Non-blocking findings, 23 of 23 acceptance criteria independently evaluated and satisfied. Coverage rows: C# 85.71 line / 79.87 branch against the CLAUDE.md floors of 80/75; PowerShell new code 92.86 and 92.50 against the 90 floor.

No coverage.xml was created under any artifacts/ path, so the 85 percent hook floor was not activated. Three non-blocking residuals are recorded in the checkpoint for follow-up promotion.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@drmoisan
drmoisan merged commit e6d8604 into main Sep 13, 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.

1 participant