Skip to content

test: add ShouldProcess and comment-based-help coverage sweeps, and fix the gaps they find - #146

Merged
juemerson-at-purestorage merged 7 commits into
dmann000:mainfrom
juemerson-at-purestorage:tests/sweep-shouldprocess-help
Aug 25, 2026
Merged

test: add ShouldProcess and comment-based-help coverage sweeps, and fix the gaps they find#146
juemerson-at-purestorage merged 7 commits into
dmann000:mainfrom
juemerson-at-purestorage:tests/sweep-shouldprocess-help

Conversation

@juemerson-at-purestorage

@juemerson-at-purestorage juemerson-at-purestorage commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator

What this adds

Two CI tripwires over the whole Public/ population, in the style of Tests/PfbEmptyPipelineGuardCoverage.Tests.ps1 — AST assertions rather than behaviour tests, aimed at a 544-cmdlet generated-and-hand-edited population decaying silently as new cmdlets arrive without the convention. Plus the seven comment-based-help gaps the new sweep finds.

Tests/PfbShouldProcessCoverage.Tests.ps1 asserts that state-changing verbs declare SupportsShouldProcess, that a declaration is actually called, that the call is not in an inner scope, that read-only verbs never declare it, and — the load-bearing one — that every Remove-* declares ConfirmImpact = 'High'. ConfirmImpact prompts only when it meets or exceeds $ConfirmPreference, whose default is High, so a Remove-* left at Medium deletes without ever prompting.

Tests/PfbHelpCoverage.Tests.ps1 asserts a non-empty .SYNOPSIS per cmdlet, a .PARAMETER entry per declared parameter matched by name, no .PARAMETER naming a parameter that does not exist, and a non-empty body on each. Name matching rather than counting is what catches a rename, where the counts stay equal.

Both files assert a population floor first. Every other assertion is "the offender set is empty", so an empty Public/ glob or a regressed AST walk would pass all of them while checking nothing. Both also carry a mutation-proof It running the record builders against synthetic fixtures with known answers in each direction, so a parser that flags nothing cannot read as green.

The seven documentation fixes

Six cmdlets had undocumented parameters — Update-PfbSupport (no help block at all), Set-PfbContext, Invoke-PfbInContext, Clear-PfbContext, Get-PfbLog (StartTime, EndTime) and Get-PfbRemoteArray (CurrentFleetOnly).

The seventh is the opposite drift, and the reason the sweep matches by name rather than by count: Remove-PfbArrayErasure carried a .PARAMETER Attributes entry for a parameter its param() block does not declare. Removed rather than added — the cmdlet issues a DELETE with no body, so the entry is stale rather than a missing feature.

tools/Update-PfbContextHelp.ps1 was checked first for the three Context cmdlets, per the spec's note.

Where most of the work went: what Get-Help actually reads

The help sweep is only worth having if it agrees with Get-Help about which block a reader sees, and three successive attempts at that rule were wrong. Each was corrected by measurement rather than by reasoning, so the rule now in the file was re-derived from scratch:

  • Placement is not enough. A block inside the function extent and outside any nested function still renders nothing if it sits one line below param(), or is stranded mid-body after an early return. The sweep reported such a cmdlet as fully documented.
  • The unit is the comment RUN, not the block. Comment tokens on consecutive lines — # line comments and <# #> blocks alike — concatenate into one help block, with each delimiter stripped in place; a blank line ends the run. So a block at a perfectly honoured position renders nothing when the line above it is ordinary commentary, and content sharing a delimiter's line survives into the help.
  • A run stands or falls whole. One directive Get-Help cannot accept — .WIBBLE, a bare .PARAMETER, .DESCRIPTION with text on the same line — voids the entire block, .SYNOPSIS included.
  • A block above the function is honoured by Get-Help and is still reported here rather than credited, because it renders in place of a body block, and because crediting it would mean agreeing with Get-Help about adjacency to the byte — where the failure direction is a file header being scored as a cmdlet's help. The record carries HelpAboveFunction and HelpBlockMisplaced so the message can name the remedy (move the block) rather than telling a developer with perfectly good help to write some.

125 probe shapes were written to disk, dot-sourced and read back through Get-Help -Full under Windows PowerShell 5.1 (5.1.26100.8875) and PowerShell 7.6.5. Every shape gave the same placement answer on both editions, so there is one rule and not one per edition.

Verification

  • Scoped Pester: 56 passed / 0 failed / 0 skipped on both editionsPfbHelpCoverage, PfbShouldProcessCoverage, CiCoverageGate, Update-PfbContextHelp. The full suite is CI's job.
  • Population unchanged and clean under the stricter rule: 544 cmdlets, 542 with parameters, 2860 parameters declared and 2860 documented, zero findings of any kind.
  • Mutation-tested. Seventeen mutations of the detector were applied on both editions, each anchor confirmed to match exactly once first so a no-op is not read as a survivor. Two survived their first run and are the reason four fixtures exist.
  • All 41 fixtures were put through real Get-Help on both editions and compared against the detector: 37 agree exactly, and the 4 that diverge are all the deliberate above-the-function convention.
  • An independent scoped review re-measured the final fix diff without reusing the original harness — 232 compositional plus 34 targeted shapes against real Get-Help, zero unexplained divergences on either edition, and byte-identical SHA-256 digests across editions over 19,800 directive-line verdicts.

That review found two behaviours the fixture set asserted but did not discriminate; the last commit closes both.

  • The directive character class. dotword-prose-voids-block was documented as pinning \w against [A-Za-z] and does not — .NET is pure ASCII, so both patterns match it identically, and what that fixture actually pins is the argument group. digit-dotword-voids-block opens its .EXAMPLE body with .5 is a fraction …, which is directive-shaped to \w alone. Measured on both editions, Get-Help renders auto-generated syntax help and no parameter text for it, so narrowing the class would credit a cmdlet whose help does not render at all.
  • Which run above the function counts. Every other above-function fixture holds exactly one run up there, so reading the first instead of the last is indistinguishable in all of them. last-run-above-function-wins puts a file header first, real help last and a body block as well; Get-Help renders the last run, so reading the first would fall through and credit a body block no reader ever sees.

Both new fixtures were mutation-checked: narrowing the class at both sites, and taking $aboveRuns[0], each now fail the suite and passed before.

Live verification

Exempt. Every change under Public/ is inside a comment block — no executable line changed in the module source or the manifest, confirmed by comparing the parsed token stream on both sides of the diff.

Derived artifacts

Reports/PfbApiDriftReport.json is regenerated in the last commit. Adding the missing help block to Update-PfbSupport.ps1 pushed its param() block from line 5 to line 24, and the report records that line as each finding's target — six paramBlockLine values, no endpoint, parameter or enum verdict moved. Regenerated through scripts/Assert-PfbDerivedArtifacts.ps1 -UpdateCommitted; the full gate now reports all 11 artifacts up to date.

Not included

No version bump and no CHANGELOG.md entry — the maintainer's separate decision.

Two CI tripwires over the whole Public/ population, in the style of
Tests/PfbEmptyPipelineGuardCoverage.Tests.ps1: AST assertions, not behaviour
tests, that catch a 544-cmdlet generated-and-hand-edited population decaying
silently as new cmdlets arrive without the convention.

PfbShouldProcessCoverage asserts that state-changing verbs declare
SupportsShouldProcess, that a declaration is actually called, that the call is
not in an inner scope, that read-only verbs never declare it, and -- the
load-bearing one -- that every Remove-* declares ConfirmImpact = 'High'.
ConfirmImpact prompts only when it meets or exceeds $ConfirmPreference, whose
default is High, so a Remove-* at Medium deletes without ever prompting.

PfbHelpCoverage asserts a non-empty .SYNOPSIS per cmdlet, a .PARAMETER entry
per declared parameter matched BY NAME, no .PARAMETER naming a parameter that
does not exist, and a non-empty body on each. Name matching rather than
counting is what catches a rename, where the counts stay equal.

Both files assert a population floor first. Every other assertion is "the
offender set is empty", so an empty Public/ glob or a regressed AST walk would
pass all of them while checking nothing. Both also carry a mutation-proof It
that runs the record builders against synthetic fixtures with known answers in
each direction, so a parser that flags nothing cannot read as green.

Test-PfbNestedInInnerScope is duplicated from
PfbEmptyPipelineGuardCoverage.Tests.ps1 rather than shared. Extracting one copy
to tools/lib/ is the right end state and is a deliberate follow-up; refactoring
a CI-critical gate was out of scope here.

Remove-PfbWorkloadTag, the only Remove-* at ConfirmImpact = 'Medium', is parked
in a clearly-marked PENDING-DECISION list of its own. Whether it is a defect or
a deliberate choice is the maintainer's call and must be settled before merge.

Pure parsing throughout -- neither file imports the module, so no module state
is created or leaked. Both pass under pwsh 7 and Windows PowerShell 5.1.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Documentation only -- no executable line changes, so nothing here can alter a
request the module sends or a response it parses.

Six cmdlets had undocumented parameters:
  Update-PfbSupport       no help block at all; 0 of 2 parameters
  Set-PfbContext          0 of 5
  Invoke-PfbInContext     0 of 5
  Clear-PfbContext        0 of 1
  Get-PfbLog              4 of 6 (StartTime, EndTime)
  Get-PfbRemoteArray      6 of 7 (CurrentFleetOnly)

A seventh, not in the original list, is the opposite drift and is the reason
the sweep matches by name rather than by count: Remove-PfbArrayErasure carried
a .PARAMETER Attributes entry for a parameter its param() block does not
declare. Removed rather than added -- the cmdlet issues a DELETE with no body,
so the entry is stale rather than a missing feature.

tools/Update-PfbContextHelp.ps1 was checked first for the three Context
cmdlets, per the spec's note. It generates a .NOTES context-requirement block
only, keyed on endpoint contextScope, and emits no .PARAMETER entries at all --
so these are hand-written and are not at risk of being overwritten by a
regeneration.

Public/ now reads 544/544 cmdlets with a .SYNOPSIS and 2860/2860 declared
parameters documented, with no orphaned, duplicated, nameless or empty entries.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Eight code findings from the whole-branch review, plus the ConfirmImpact ruling
the previous commit left open.

Help-block lookup -- the one that could false-green:
PfbHelpCoverage located a cmdlet's help by taking the nearest .SYNOPSIS block
INSIDE the function, falling back to the nearest one ABOVE it. Two shapes then
report as fully documented while Get-Help shows nothing: a file-header block a
dozen lines up, and a block belonging to a nested helper when the cmdlet itself
has none. The second is why dropping the fallback alone would not have been
enough -- a nested function's extent is a subset of the cmdlet's, so those
blocks passed the "inside" test too. A block must now sit inside the function
body and outside any nested function within it. All 544 blocks in Public/
already satisfy that, so this is strictly stronger rather than a restriction
anyone has to work around. The fallback branch had no fixture at all; four new
fixtures now pin placement in both directions.

ShouldContinue no longer satisfies SupportsShouldProcess:
ShouldContinue does not participate in -WhatIf -- it is an extra confirmation
prompt that runs regardless -- so a cmdlet declaring SupportsShouldProcess whose
only guard is ShouldContinue still issues its request under -WhatIf, which is
verbatim the harm that assertion exists to catch. The record now counts the two
separately. ShouldContinue stays in the inner-scope scan, where the hazard is
identical either way. sweep-tests-spec.md specified the original wording, so the
implementation was faithful and the spec is what needed correcting.

Remove-PfbWorkloadTag -- MAINTAINER RULING, keep it at Medium:
The previous commit parked this as pending and said it must be settled before
merge. Settled: a workload tag is metadata, DELETE /workloads/tags carries an
empty body and destroys no array data, and the tag is cheaply recreated, so the
prompt its 111 siblings carry would be friction protecting nothing. It is now a
settled entry in $confirmImpactExempt carrying that reason. Accepted cost,
recorded rather than glossed: someone who has learned that Remove-Pfb* stops and
asks will not be asked here. Revisit if the endpoint ever gains the ability to
delete something other than labels.

The separate pending-decision list goes away with the ruling and its entry moves
into $confirmImpactExempt, which keeps the same per-entry staleness guard: each
name must resolve to exactly one real cmdlet, and that cmdlet must still be
non-High. Neither list ever had a bound on its LENGTH -- a growth cap was
requested in review and declined, so nothing mechanically stops N Remove-*
cmdlets being parked at Medium with the sweep still green, and nothing enforces
that an entry carries a written reason. That gap stays open on purpose; it is
held as its own decision rather than folded into this ruling.

Four fail-open paths closed:
  - A verb in none of the three lists was checked by nothing: not required to
    declare SupportsShouldProcess, not forbidden from declaring it. A census now
    reds once, names the offender, and turns classification into a one-line
    decision. Connect and Disconnect were sitting in that gap.
  - $shouldProcessExempt gained the staleness guard its neighbour already had.
  - The two copies of Test-PfbNestedInInnerScope were held in sync only by a
    comment, on a function both files call CI-critical and whose regression is
    caught by exactly one It in each. An It now asserts they are -ceq identical.
  - A comment claimed "first .SYNOPSIS wins" while the guard only prevented
    un-setting a flag that is never un-set, so a populated first .SYNOPSIS
    followed by an empty second flagged the cmdlet. Now actually enforced.

coverage-baseline.psd1 gains both new Describes in both edition blocks.
Assert-PfbTestCoverage.ps1 walks $Result.Containers, so a file that drops out of
DISCOVERY entirely appears nowhere and the run stays green with the tripwire
silently gone; RequiredDescribes is the only rail that sees that. The entries
match the declared Describe names exactly, verified by comparison -- but they
are first exercised end-to-end by CI, since this repo reserves the full-suite
run for CI rather than running it locally.

Also folds in a held docstring fix in scripts/Assert-PfbSpecCache.ps1, which
cited .gitignore by line number (:36 and :46) and justified its own location
with a blanket tools/ ignore that no longer exists. Comment text only.

Verification: 17 tests pass under pwsh 7 and Windows PowerShell 5.1, 0 skipped,
containers ok on both; 48/48 with the exemplar sweep and CiCoverageGate run
alongside. Each of the eight new or changed assertions was mutation-tested and
all eight were killed. The limit of that evidence is worth stating: neutering an
offender FILTER survives by construction for any "the offender set is empty"
assertion, since the set is empty either way on a clean tree. What protects
those is the fixture pair and the population floors, not the mutation run.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The previous commit claimed to close the help-lookup false-green, and it did not.
Its rule was "the block is inside the function extent and outside any nested
function", and a residual fail-open of exactly the class it set out to fix
survived: a block one line BELOW param(), or stranded mid-body after an early
return or a validation stanza, satisfies both conditions and Get-Help renders
nothing for it. The sweep reported such a cmdlet as fully documented. It also
took the FIRST surviving candidate on the stated grounds that tokens arrive in
source order, which decides nothing about which block Get-Help reads.

So this replaces the placement rule with a measured one. 34 probe shapes were
written to disk, dot-sourced and read back through Get-Help -Full under both
Windows PowerShell 5.1 and PowerShell 7.6.5 -- every shape agreed across the two
editions, so there is one rule and not one per edition:

  - honoured positions are immediately ABOVE the function keyword (at most one
    blank line, nothing else in between), at the START of the body before the
    param block, and at the END of the body after the last statement.
    [CmdletBinding()] belongs to the param block, so a block between the
    attribute and param( is not honoured either.
  - ABOVE beats everything inside the body; START of body beats END of body;
    blocks on consecutive lines form one run and are concatenated, so a later
    .SYNOPSIS overrides an earlier one; a blank line ends the run and the first
    run carrying help keywords is the help.
  - a run whose keywords do not include .SYNOPSIS still claims the help and
    suppresses every later block -- .DESCRIPTION alone renders an EMPTY synopsis
    rather than falling through. That is why the keyword pattern matches any
    .KEYWORD: matching only .SYNOPSIS would credit a block Get-Help never reads.

A block ABOVE the function is honoured by Get-Help -- the previous comment had
that backwards, and the file contradicted itself about it -- but it stays a
finding here, for two reasons now stated instead of asserted. It renders IN
PLACE OF a block inside the body, so a cmdlet carrying both shows the outer one
and the inner one is text no reader ever sees. And crediting it would mean this
sweep has to agree with Get-Help about adjacency to the byte, where the failure
direction is a file-header block being credited as a cmdlet's help. The record
now carries HelpAboveFunction and HelpBlockMisplaced so the failure message can
name the remedy -- move the block -- rather than telling a developer with
perfectly good help to write some.

One place where a first attempt at this was wrong in the other direction, kept
here because it is the reason the rule is shaped as it is: a run that mixes a #
line comment with the help block is not symmetric. A line comment ABOVE the
block makes Get-Help render nothing; one BELOW it is honoured, with the line's
text appended to the last section. Public/FileSystem/Get-PfbOpenFile.ps1 is the
second shape -- it carries a drift-report note between the block and
[CmdletBinding()] -- so rejecting both would have red-built a cmdlet whose help
renders correctly, which the real file confirmed as a control.

Also: the keyword regexes end in [ \t\r]*$ rather than [ \t]*$. In multiline
mode $ matches before the \n but not before the \r, so a tail without \r matches
nothing at all in a CRLF file, which every file here is. The symptom was the
entire 544-cmdlet population reading as undocumented while the same pattern
worked on an LF fixture.

Nine fixtures added, each pinning a measured shape rather than a reading of the
docs: help-below-param, help-mid-body, help-body-end, two-inside-start-and-end,
two-blocks-one-run, two-runs-blank-separated, linecomment-then-help,
help-then-linecomment, above-plus-inside and above-description-only. The nested
function walk stays, demoted to diagnosis: the position test already rejects a
nested helper's block, and what the walk now decides is whether a cmdlet is told
"no help" or "help in the wrong place", which are different fixes.

Two comment corrections in PfbShouldProcessCoverage and one message reword:

  - the ConfirmImpact exemption comment retired a rail that never existed. It
    said a one-entry cap on the pending-decision list had been removed because
    it had no remaining job. Verified against 564f7a0: that list carried only a
    per-entry staleness loop and no length assertion anywhere. A growth cap was
    requested in review and declined. The comment now says so, and says plainly
    that the gap is open -- N Remove-* cmdlets could be parked at Medium and
    this file would stay green, and nothing mechanically enforces the "written
    reason" the paragraph leans on.
  - assertion 1's -Because said only that a cmdlet needs comment-based help with
    a .SYNOPSIS. Under the new placement rule a cmdlet can fail it while holding
    a perfectly good .SYNOPSIS, so the message now names the placement
    requirement and each offender says which of the three cases it is.

Verification: 48 passed / 0 failed / 0 skipped under both pwsh 7.6.5 and Windows
PowerShell 5.1, across PfbHelpCoverage, PfbShouldProcessCoverage,
PfbEmptyPipelineGuardCoverage and CiCoverageGate. Five mutations applied, each
anchor confirmed to match exactly once before running so a no-op is not read as
a survivor, and all five killed by the fixture assertion intended: neutering the
position filter, taking the first rather than the last .SYNOPSIS in a run,
searching the trailing region before the leading one, dropping the
above-the-function flag, and ignoring line-comment order within a run.

No version bump and no CHANGELOG entry -- the maintainer's separate decision.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
be32412 measured where Get-Help reads help from and encoded it, and the review
of it found three fail-open shapes still standing. All three are the same root
cause: it modelled where a block SITS and never asked what else shared its
comment run. Adjacent comments concatenate into one help block and a blank line
ends the run, so a block at a perfectly honoured position renders nothing when
the line above it is ordinary commentary -- and the sweep scored it as
documented.

The rule was re-derived from scratch rather than patched, because three separate
written accounts of Get-Help placement during this work were each wrong and each
corrected only by measurement, including statements in be32412's own message and
comments. 125 probe shapes were written to disk, dot-sourced and read back
through Get-Help -Full under both Windows PowerShell 5.1 (5.1.26100.8875) and
PowerShell 7.6.5. Every shape gave the same placement answer on both editions,
so there is one rule and not one per edition.

  - The unit is the RUN: comment tokens on consecutive lines, `#` line comments
    and `<#...#>` blocks alike, concatenated with each delimiter stripped IN
    PLACE and each line comment's leading `#` removed. Content sharing a
    delimiter's line survives, measured, so the delimiter LINE must not be
    dropped.
  - A run is help when its first non-blank line is a recognised directive line
    AND every directive-SHAPED line in it is recognised. Prose above the first
    directive voids it; below one it is that section's body. One unrecognised
    directive anywhere voids the whole block, .SYNOPSIS included.
  - Regions are searched above the function (last run only, at most one blank
    line away), then the start of the body, then the end. A region holding no
    help run suppresses nothing and the search moves on -- so a #region marker
    or a file header above a function costs nothing. A run that IS help claims
    it absolutely, even with no .SYNOPSIS in it.

The three findings then fall out of one predicate rather than three patches:

  - above-function help followed by a `#` line comment. The comment JOINS the
    run and the outer block still renders, in place of any block inside the
    body. The old code rejected any non-newline token between block and keyword,
    so it ignored the outer block and credited the inner one -- rendered help
    and scored help came from different blocks.
  - an ordinary `<#...#>` block on the line above a help block. Non-directive
    text first voids the run, help block and all.
  - the same with an unknown-keyword block. Its blank-line-separated twin is now
    CREDITED, reversing the documented conservative case: an unrecognised
    keyword claims nothing, so a help block below it renders normally, and
    treating it as claiming red-built a shape that works.

Measurement contradicted the file in three more places, and the measurement
wins in each:

  - the LAST .SYNOPSIS in a run wins, not the first, within one block as much as
    across blocks. A trailing EMPTY .SYNOPSIS overrides a populated one and
    Get-Help renders a blank synopsis. Taking the first was a false green over
    exactly the defect the empty-synopsis assertion exists to catch. The
    second-synopsis fixture asserted the opposite and now asserts this.
  - a run is ONE help block, so every .PARAMETER in it renders no matter which
    comment carried it. Scoring the single token that held the winning .SYNOPSIS
    hid the other blocks' entries from the orphan and duplicate assertions;
    two-blocks-one-run's .PARAMETER Other is a genuine orphan and is now
    asserted as one.
  - a bare `.PARAMETER` does not merely fail to document its parameter, it voids
    the entire block. The NamelessParameterSections field and its assertion are
    therefore gone: once the run test is correct that counter can never be
    non-zero, and an assertion over a structurally-zero field cannot fail, which
    is the failure mode the anti-vacuous floor at the top of the file exists to
    prevent. The same source defect now fails the first assertion instead,
    earlier and harder, and HelpRunDefect names the offending line -- the one
    case a developer cannot diagnose by reading a block that looks correct.

The LOW from the review is fixed in the same message. It told developers that
help above the function is not rendered. It IS rendered; the finding is a
convention one, because an outer block renders in place of an inner one and a
cmdlet carrying both has inner help no reader ever sees. The message now says
that, and says why crediting it would force this sweep to match Get-Help on
adjacency to the byte.

Sixteen fixtures added or changed, each measured on both editions first:
above-linecomment-then-inside, ordinary-then-help-one-run,
ordinary-blank-then-help, unknown-keyword-then-help-one-run,
unknown-keyword-blank-then-help, prose-before-keyword,
synopsis-with-inline-argument, linecomment-help-claims-run,
linecomment-continues-run, invalid-above-falls-through,
ordinary-start-help-at-end, delimiter-line-content,
unknown-keyword-later-in-block, dotword-prose-voids-block,
above-blank-line-still-adjacent and distant-block-falls-through, plus reversed
expectations on second-synopsis, two-blocks-one-run and nameless. The last two
new ones exist because a mutation of the above-the-function adjacency bound
SURVIVED: distant-header looks like it covers that bound and does not, since its
last run before the keyword is a line comment and the block above never gets an
adjacency test at all.

dotword-prose-voids-block is worth naming on its own. `.NET Core is mentioned
here` in an .EXAMPLE body is prose to a reader and a malformed directive to
Get-Help, because the directive pattern is `\w` and not `[A-Za-z]` -- so is
`.5 is a fraction`. Its harmless twin is `.\tools\Update-PfbContextHelp.ps1
-WhatIf` in the dotted-prose fixture, where a backslash is not a word character
and the block renders. The pair is the point.

Verification: 48 passed / 0 failed / 0 skipped on both editions across
PfbHelpCoverage, PfbShouldProcessCoverage, PfbEmptyPipelineGuardCoverage and
CiCoverageGate, unchanged from be32412. The 544-cmdlet population is unchanged
too -- 544 cmdlets, 542 with parameters, 2860 declared, 2860 documented, and
zero findings of any kind under the stricter rule. Seventeen mutations of the
detector were applied on both editions, each anchor confirmed to match exactly
once first so a no-op is not read as a survivor, and every one was killed. Two
of the seventeen survived their first run and are the reason four of the
fixtures above exist. Separately, all 41 fixtures were put through real Get-Help
on both editions and compared against the detector: 37 agree exactly and 4
diverge, all four being the deliberate above-the-function convention.

No version bump and no CHANGELOG entry -- the maintainer's separate decision.

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

A scoped review of the help-detector fix found two behaviours the fixture set
asserts but does not discriminate: mutating either survives every assertion in
the file, so a regression in either would land green.

The character class. The existing 'dotword-prose-voids-block' fixture is
documented as pinning `\w` against `[A-Za-z]`, and does not -- `.NET` is pure
ASCII, so both patterns match it identically and what that fixture actually
pins is the argument group. Add 'digit-dotword-voids-block', whose `.EXAMPLE`
body opens with `.5 is a fraction ...`: directive-shaped to `\w` alone.
Measured on pwsh 7.6.5 and WinPS 5.1, Get-Help renders auto-generated syntax
help and no parameter text for it, so narrowing the class would credit a cmdlet
whose help does not render at all. Correct the attribution in the two comments
that made the wrong claim.

Which run above the function counts. Every above-function fixture holds exactly
one run up there, so reading the first instead of the last is indistinguishable
in all of them -- and in 'distant-header' the first falls through anyway, on
adjacency. Add 'last-run-above-function-wins': a file header first, real help
last, and a body block as well. Measured on both editions, Get-Help renders the
last run, so reading the first would fall through and credit a body block no
reader ever sees.

Both fixtures were mutation-checked: narrowing the class to `[A-Za-z]` at both
sites, and taking `$aboveRuns[0]`, each now fail the suite and passed before.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Adding the missing help block to Update-PfbSupport.ps1 pushed its param()
block from line 5 to line 24, and the drift report records that line as the
target of each finding. Six `paramBlockLine` values, one per finding against
that file; no endpoint, parameter or enum verdict moves.

Regenerated through scripts/Assert-PfbDerivedArtifacts.ps1 -UpdateCommitted
rather than by running the generator bare, so the output is the one the CI
gate compares against: the gate stages exactly the 29 pinned spec versions,
while Build-PfbApiDriftReport.ps1 run on its own records whatever the local
tools/specs/ cache happens to hold.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@juemerson-at-purestorage
juemerson-at-purestorage force-pushed the tests/sweep-shouldprocess-help branch from de4018a to 0a81619 Compare August 25, 2026 22:20
@juemerson-at-purestorage
juemerson-at-purestorage merged commit 51f9259 into dmann000:main Aug 25, 2026
6 checks passed
@juemerson-at-purestorage
juemerson-at-purestorage deleted the tests/sweep-shouldprocess-help branch August 25, 2026 22:59
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