Skip to content

Teach the wire-name resolver three assignment shapes; measure 32 more keys and classify dead keys - #151

Merged
juemerson-at-purestorage merged 29 commits into
dmann000:mainfrom
juemerson-at-purestorage:fix/issue-141-wire-name-shapes
Aug 28, 2026
Merged

Teach the wire-name resolver three assignment shapes; measure 32 more keys and classify dead keys#151
juemerson-at-purestorage merged 29 commits into
dmann000:mainfrom
juemerson-at-purestorage:fix/issue-141-wire-name-shapes

Conversation

@juemerson-at-purestorage

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

Copy link
Copy Markdown
Collaborator

Closes #141.

What this changes

The wire-name resolver decides, for each cmdlet parameter, which key that parameter becomes on
the wire. Three assignment shapes in the module were written in forms the resolver did not
recognise, so it did not resolve them wrongly -- it declined to resolve them at all and recorded
them as skipped. A skipped parameter is invisible to every downstream safety gate: the dead-key
detector, the drift report and the field map all reason only over parameters that were measured.

This branch teaches the resolver those three shapes, traces which payload role each resolved
value lands in, and then updates the committed artifacts and the tripwires so they describe the
larger measured population instead of the smaller one.

No cmdlet behaviour changes. This is entirely tooling, tests, and regenerated reports.

Measured effect

Both columns are read from Reports/PfbDeadKeyReport.json at the respective commit.

main (76f0c95) this branch
parameters inventoried 2168 2168
keys evaluated 1747 1780
resolved ok 1664 1695
dead keys 83 85
skip: wire name unresolved 127 31
skip: outside standard request -- 28
skip: not wire parameter -- 6
skip: body property 280 309
skip: endpoint/method ambiguous 14 14

The parameter population is unchanged at 2168, which is the control: nothing was added to the
inventory. What moved is how many of those parameters the resolver could actually measure.

wire name unresolved falling 127 -> 31 is the headline. That bucket was doing two jobs: holding
parameters the resolver genuinely could not read, and holding parameters that were never wire
parameters in the first place. Splitting out outside standard request (28) and not wire parameter (6) means the residual 31 is now a real backlog rather than a mixed bag, and it is
small enough to enumerate.

Dead-key classification

Dead-key records now carry a classification and a declaredElsewhere provenance list, which
did not exist on main (every record there classified as empty):

  • UNDECLARED -- 71
  • WRONG-VERB -- 13
  • WRONG-SURFACE -- 1

WRONG-VERB = 13 is not anticipated anywhere in the plan and is not a regression. Those keys are
declared in the spec on the same endpoint under a different HTTP method; previously they were
indistinguishable from keys the spec does not know at all. Separating them is the point of the
classification -- a key that exists under the wrong verb is a different kind of problem from one
that does not exist.

The dead-key count rising 83 -> 85 is the direct consequence of evaluating 33 more keys. Both new
identities are named in the justification beside the re-baselined gate in
Tests/CommittedDeadKeyReport.Tests.ps1.

Abstention, and the defaulting-alias rule that removed its last real-tree case

The resolver now withdraws a row when two role-agreeing landings disagree on the name, rather
than publishing a guess. An abstention is not a failure to resolve, and it does not fall through
to a weaker idiom.

No row in the module is withdrawn. A whole-module sweep of all 2861 parameters reports zero
abstentions; the same sweep against the previous commit reports exactly one. The machinery is
retained as a guard and is unit-tested against synthetic ambiguity, not left unexercised.

That one case was a genuine defect in the rule, caught by CI rather than locally.
Update-PfbBucketAuditFilter sets names from -Name, and an elseif sets names from
-BucketName so a -BucketName-only caller need not restate the value. The resolver counted
that arm as a landing of -BucketName, so -BucketName appeared to land both bucket_names
and names, the arbitration abstained, PATCH /buckets/audit-filters lost parser traceability
and dropped to partial confidence, and Tests/Issue31.DriftConfidence.Tests.ps1 failed. It
failed only on Linux/macOS pwsh: that Describe is -Skipped below PowerShell 7, so WinPS 5.1
was green while the same assertion was red beside it.

Test-PfbIsDefaultingAliasAssignment flags an index assignment whose key and target
variable are already written by an earlier sibling clause of the same if/elseif chain
from an expression that does not mention this parameter. Two independent ifs are not a chain
and are left abstaining, because that is real ambiguity.

The proviso on top of it is load-bearing, and the first version of this rule did not have it:
a flagged landing is dropped only when the parameter still has an unflagged landing of its
own.
New-PfbFleetMember writes members from a FleetKey arm and again from a -Members
elseif; neither arm defaults the other, and -Members has no other landing. An unconditional
drop deleted its only evidence and relocated the identical regression onto POST /fleets/members.
Three synthetic controls all missed that, because each happened to give the parameter a second
landing; only regeneration against the real tree caught it. It is now pinned by a test.

Pipeline selector map moved, and why that is expected

Reports/PfbPipelineSelectorMap.json moved: findings 264 -> 266, finding pairs 101 -> 102.

The discriminator is that probePairs is unchanged at 1247 on both sides while
candidatePairs moves 629 -> 655. The generator's code and its probe population are identical;
what changed is how many parameters now resolve well enough to be probed at all. The gate
breakdown accounts for every one of those pairs on both sides, with 28 rows leaving
SelectorUnresolved (34 -> 6) into Candidate (+26) and Matched (+2), and 655 + 586 + 6 =
1247 on this side against 629 + 584 + 34 = 1247 on main. controlLeakage remains 0.

probePairs is deliberately left un-rebaselined in Tests/Build-PfbPipelineSelectorMap.Tests.ps1
so it keeps working as that discriminator: if a future change moves the probe population itself,
that assertion should fail.

New selector waiver -- a live wire defect this branch reveals

Get-PfbUserGroupQuotaPolicy | Name is waived in Rail A against this issue. It is a real defect
in the module that this branch reveals without introducing: the primary producer binds -Name
correctly, but two family endpoints (GET /user-group-quota-policies/file-systems and
/members) return join items whose members are objects (context, member, policy) carrying
no name, so a name-shaped selector cannot bind by property name and the whole object is
stringified onto the wire -- BoundValue @{context=; member=; policy=}, carried under the
names key (Evidence records the names= prefix; BoundValue itself is the bare object).

Get-PfbTlsPolicy and Get-PfbWormPolicy share the same root cause, though not an identical
BoundValue -- Get-PfbTlsPolicy stringifies @{member=; policy=}, with no context member.
The class is wider than these three: measured against the regenerated map, 150 rows across 65
cmdlet/parameter pairs
coerce a join item this way, in three shape variants
(@{context=; member=; policy=} x124, @{member=; policy=} x21,
@{context=; link=; member=; policy=} x5), and the broader object-stringification class is all
266 findings across 102 pairs. Every one of them is Coerced rather than silently bound, and no
primary producer is affected.

Two issues now track this and the waivers have been re-pointed off closed #90 in this branch:
#152 takes the 65-pair join-item class, #153 takes a further 37 pairs whose items lack a
name for unrelated reasons (alert/hardware records keyed on component_name, @{group=; member=} membership items, realm/object-store associations). They are tracked apart on purpose:
#152 is one coherent cause a single fix plausibly resolves, while #153 has no shared structure to
key a fix on. 65 + 37 = 102, every Coerced pair, with no silent remainder.

In the waiver file the split lands as 64 / 37 / 1: Get-PfbUserGroupQuotaPolicyRule | PolicyName
belongs to the #152 class by root cause but points at #123, which already tracks that pair
against a different, upstream blocker (the array honours a policy_names key the published
OpenAPI omits). Both numbers are correct; they answer different questions, and the file's header
says so.

Fixing the cmdlets is deliberately not done here -- see the exemption note below.

Live-test exemption

Verified mechanically with wire-exemption\Test-PfbWireExemption.ps1 -RepoPath <worktree>, base
origin/main @ 76f0c952, head c566c78: VERDICT: EXEMPT (exit 0), "No in-scope file
changed."

The diff leaves the module source and the manifest entirely untouched, so nothing here can
alter a request the module sends or a response it parses.

Every changed file is under Reports/ (7), Tests/ (9) or tools/ (7) -- 23 in total. Public/, Private/,
PureStorageFlashBladePowerShell.psd1 and .psm1 are untouched -- not merely free of executable
changes, but absent from the diff entirely. This is why the three selector-coercion cmdlet fixes
are a follow-up rather than part of this PR: one executable line in any of those four locations
disqualifies the whole PR from exemption, and there is no partial exemption.

Verification

  • Scoped Pester runs on both editions (pwsh 7 and WinPS 5.1) for every test file touched, with
    container status read per edition rather than inferred from a summary line: 484 passed / 0
    failed under pwsh 7, 418 passed / 0 failed / 66 skipped under WinPS 5.1.
  • The defaulting-alias rule's own tests were run against the pre-fix resolver as a
    non-vacuity control: 2 of the 7 go red there. The other 5 are regression pins that must hold on
    both sides, including the New-PfbFleetMember shape, which pins against the over-broad first
    version of the rule rather than against main.
  • ./scripts/Assert-PfbDerivedArtifacts.ps1 -- all eleven checks up to date. As a control, the
    same script was run on main (76f0c95) and reported 11/11 clean there too, which establishes
    that the artifacts regenerated here were stale because of this branch and not pre-existing
    debt.
  • CI's first run on this PR failed one assertion, on Linux and macOS only; that failure is the
    PATCH /buckets/audit-filters regression described under Abstention above, and it is fixed in
    c566c78. The Tests/coverage-baseline.psd1 skip ceilings remain measurable only by a full
    suite (Build-PfbDeadKeyReport.Tests.ps1 is pinned at 16, with entries summing to 322), so
    they are settled by CI rather than locally.

Deliberately not included

  • No version bump and no CHANGELOG entry -- those are the maintainer's call, made separately.
  • No cmdlet fixes, for the exemption reason above.
  • One pre-existing inaccuracy is left in place and flagged rather than fixed:
    Tests/CommittedDeadKeyReport.Tests.ps1 says a collection "has only 18 entries" where
    noSurvivingSelector has 7. The identical line is at origin/main:289, so it predates this
    work and is out of scope here.

juemerson-at-purestorage and others added 29 commits August 25, 2026 22:05
Recognize only AST-exact Boolean casts and zero-argument ToString/ToLower chains while preserving the resolver's never-guess boundary.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Exercise method arity and Boolean type aliases while keeping wire-key fixtures independent of parameter names.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Extract Get-PfbHelperArgumentSourceVariable so the Add-PfbCommonQueryParams
-Names/-Ids readers accept a bare variable or an exact zero-argument
$var.ToArray() call, and nothing else. Real shape: Get-PfbUserGroupQuotaPolicy
hands its [List[string]] accumulators to the helper's [string[]]-typed
parameters as $allNames.ToArray().

Guards, one per early-return line: not static, member literally ToArray, zero
arguments (via Test-PfbInvokeHasNoArguments), bare VariableExpressionAst target.
The static guard is separate because $var::ToArray() satisfies all three others
-- it parses as an InvokeMemberExpressionAst with member ToArray, no arguments,
and a bare variable target -- so only .Static rejects it.

Extracted rather than inlined for testability: the guards are now reachable from
a unit test against a parsed AST, independent of the fixture-file path, giving
each guard a second kill route. A guard with one route is one outer-check bug
away from being silently uncovered.

The fixture builder now throws on parse errors. `-Names [SomeType]::ToArray()`
does not parse in command-argument mode -- PowerShell emits ExpectedExpression
and splits it into a bareword plus a ParenExpressionAst -- so a fixture written
that way never reaches any guard while the suite stays green.

Inventory: 2071 -> 2073 resolved of 2168, the two rows being
Get-PfbUserGroupQuotaPolicy -Name -> names and -Id -> ids, Query/GET on
user-group-quota-policies. Nothing else moves.

Tests 119 -> 136, both editions, 0 failed 0 skipped.
Two reviewers (opus5-medium, kimi-k3-max) both APPROVED 4da55f0. Three live
Minor findings, all addressed here; no behaviour change.

- Document the dynamic-member guard as StrictMode-defensive rather than
  behavioural. It has no mutation kill route because deleting it leaves the
  next line refusing the same shape while this file runs without
  Set-StrictMode -- an equivalent mutant, not an uncovered guard. Measured by
  re-running the shape corpus against the mutated build.
- Assert the shared-accumulator fixture parses. It was the one new fixture
  parsing inline source without the assertion the other two builders make.
- Relabel two direct negatives that are refused by the member-name guard, not
  the STATIC guard. Measured under mutation: `$allNames::ToArray()` is the
  only shape in that block that reaches Static.

Scoped Pester: 136 passed / 0 failed / 0 skipped, both editions, unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The wire-name resolver decided a variable's request role by its NAME: a
`switch ($TargetVariable) { 'body' { 'Body' } 'queryParams' { 'Query' } }`
trust gate that no request argument ever had to confirm. That was wrong in
both directions. A variable called $q, $payload or $destroyQuery got no role
at all even when it was handed straight to Invoke-PfbApiRequest, and a
variable called $body would have been reported as a Body landing even if the
only place it went was -QueryParams.

Get-PfbRequestRoleForVariable replaces the gate. It scans the function's
Invoke-PfbApiRequest calls, derives the surface from the command PARAMETER
the variable is passed to, and derives the operation from literal -Method and
-Endpoint arguments. Both argument forms are read (`-Body $x` and `-Body:$x`),
a nonliteral operation argument leaves the operation unread rather than
guessed, and an unread landing still counts as a landing -- a readable call
never speaks for one it could not read.

Arbitration is now over the complete tuple. Every landing an idiom finds is
collected, identical tuples collapse to one, and a complete answer is returned
only when exactly one distinct tuple survives; otherwise only the components
every candidate agrees on are kept and the rest are nulled. This is what keeps
Remove-PfbFileSystem -DeleteLinkOnEradication honest: it writes the same key
into $destroyQuery (reaching a PATCH) and into $queryParams (reaching a
DELETE), and retiring the name gate makes $destroyQuery the earlier AST match,
so a first-match resolver would report PATCH and hide the DELETE outright. It
keeps its key and its Query surface and names no operation.

Get-PfbEndpointForVariable stays as a compatibility wrapper and delegates; it
keeps no copy of the name switch.

Over the real Public/ tree this resolves 30 (cmdlet, parameter) pairs that
were previously unresolved, all of them payload variables the old gate could
not see by name. It also withdraws one previously-resolved row --
Update-PfbBucketAuditFilter -BucketName, which is written to two different
query keys ('bucket_names' and, in the -Name default branch, 'names') and so
has no single wire name to report. The old answer was true but partial, and
picking it depended on source order.

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

Review round 1 on the issue dmann000#141 Task 3 resolver.

Finding 1 (major). Get-PfbWireNameForParameter documented that a tier which
finds candidate landings and then abstains ends the resolution, but only the
index tier implemented it. The literal, nested-reference and helper tiers gated
on the truthiness of the arbitrated answer, which cannot distinguish "found
nothing" from "found landings that disagreed", so an abstaining tier fell
through and let a weaker idiom publish the arbitrary pick the abstention exists
to refuse. Split the two landing producers out of their arbitrating wrappers
(Get-PfbHashtableLiteralWireLanding, Get-PfbNestedReferenceWireLanding) and gate
every tier on landings.Count instead. The same invariant now holds for the two
sub-forms inside the nested-reference tier.

Finding 2 (major). Resolve-PfbWireLandingArbitration merged candidate components
with -ne, which is case-insensitive for strings: two landings differing only in
the case of a wire key or an endpoint were judged to agree and the first one the
parser reached was published, with no clash reported. Compare with
[string]::Equals(..., Ordinal), matching the ordinal List[string].Contains
distinctness tests in Get-PfbRequestRoleForVariable. Dropped the matching
ToUpperInvariant on the method so the tracer reports what the source says rather
than a literal that appears nowhere in it.

Minor. Corrected the comment on the -isnot [CommandParameterAst] argument-binding
guard, which claimed to prevent a misread that every downstream branch already
re-validates away; it is kept for correctness at the point of binding, not
because a test covers it. Added the parse assertion test-bed rule 1 asks for to
the two on-disk fixtures in the issue dmann000#99 Describe.

Tests. One tier-boundary test per boundary, each carrying a control assertion
that the later tier really would have answered; four case-sensitivity tests; and
a real-tree enumeration of the whole one-variable-many-keys population, masked
instances included, so a future unmasking is a named event rather than a
regression from nowhere.

No behaviour change on the real tree: all 2168 inventory rows are byte-identical
to the previous commit.
The Task 3 re-review found one surviving mutant: returning
$literalLandings[0] instead of arbitrating over the whole array leaves the
suite green at 192/0/0. Not an equivalent mutant -- on two disagreeing outer
keys the resolver correctly abstains and the mutant publishes the first one,
which is first-match arbitration alive in the code path fix round 1
restructured.

The existing sibling test cannot reach it: the nested tier consults its INDEX
sub-form first, so with any index assignment present the literal sub-form
never answers. The new fixture has no index assignment at all, making the
literal sub-form the only producer.

Carries a control assertion -- one outer key alone resolves to 'gamma' -- so
the refusal is the disagreement talking rather than an inert fixture.

Scoped Pester: 193 passed / 0 failed / 0 skipped, both editions.
Mutant re-applied and confirmed KILLED in both editions, by this test alone
(192/1).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The re-review noted the .DESCRIPTION asserted the sticky-abstention invariant
as a general property. It holds within Get-PfbWireNameForParameter and stops
at its return: an abstention is signalled as $null, which is also how silence
is signalled, and Get-PfbCmdletParameterInventory retries through
Find-PfbAccumulatorVariable on any falsy result.

Measured end-to-end: a parameter written to both $q[alpha] and $q[beta] AND
fed to an accumulator keyed at $q[names] abstains in the resolver and still
emits a Typed row naming names. Latent -- no cmdlet in Public/ has that shape,
as neither known multi-key parameter has an accumulator -- and it predates the
tier work rather than being introduced by it.

Comment only. An over-broad doc claim about this invariant is the same defect
the tier fix addressed, one level up.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Integrate issue dmann000#141 Task 3's role tracing into the cmdlet parameter
inventory and retire the remaining name gates.

Resolve-PfbParameterWireLanding now returns { Landings; Resolution } so
the inventory can tell an ABSTENTION from SILENCE. Both used to surface
as $null, and the Find-PfbAccumulatorVariable retry fired on either --
so a parameter whose own landings disagreed had a fifth source consulted
on its behalf and was republished with a confident name. The retry now
fires only on Landings.Count -eq 0.

Add two non-applicable Surface values, neither of which is a failure to
resolve: OutsideStandardRequest (the declaring function issues no
Invoke-PfbApiRequest at all) and NotWireParameter (an audited request
control that steers the call rather than appearing in it). The latter is
an enumerated Cmdlet|Parameter allowlist, never a name pattern -- the
suite re-validates every entry against the real AST so a stale one fails
loudly, and a fixture pair proves two identically shaped -Eradicate
switches classify differently purely by identity.

Add tools/Compare-PfbInventoryTuple.ps1 and Compare-PfbInventoryTupleSet:
a row-level regression gate over Surface|WireName|WireSurface|Method|
Endpoint. Task 3 withdrew Update-PfbBucketAuditFilter -BucketName while
the Typed total went UP, and only a hand diff inside a code review caught
it. A change passes only when a declaration names its exact before and
after; a declaration that matched nothing fails too, so the gate cannot
rot into a rubber stamp.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Get-PfbParameterCoverageGaps decided doubt with `Surface -ne 'Typed'`.
That is a denylist: it reads "anything I cannot resolve is doubt about
this endpoint", so the two non-applicable surfaces would have been swept
into confidence.unresolvedParameters and demoted 34 real endpoints from
high to partial on the strength of parameters that are not wire fields at
all and could never have covered a gap.

Replace it with an exhaustive `switch ($row.Surface)` whose `default`
throws. A new Surface value must now be assigned a meaning here, by hand;
until it is, the report fails rather than silently taking whichever side
the negation happened to fall on.

The second `-ne 'Typed'`, in Get-PfbWireNameCmdletCounts, is left as it
is with a comment saying why: its `-or -not $row.WireName` companion
already excludes every row a non-applicable surface can produce, and that
guard is load-bearing for Typed rows too.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Build-PfbFieldCmdletMap.ps1 had three buckets and no reconciliation, so a
Surface it had not been taught was filtered out by all three Where-Objects
and vanished from the report without a word -- the quietest possible
failure, and the one issue dmann000#141 exists to stop.

Emit a fourth bucket, notApplicable, carrying `surface` per row. It is
deliberately NOT folded into typedUnresolved: that list is read as "the
tool could not find this field wire name", and these rows are not fields.
The Markdown gets its own heading for the same reason -- a reader
triaging work should not be handed six -Eradicate switches as work.

Assert the partition instead of assuming it: every inventory row must land
in exactly one of five buckets, the fifth being typed-with-ValidateSet,
which is emitted nowhere because this report recommends ADDING one. A row
that lands in none throws and names the counts.

coverage-baseline.psd1: Build-PfbFieldCmdletMap.Tests.ps1 15 -> 25. The
two new Describes exercise the script itself, which carries
`#Requires -Version 7.0`, so they take the file existing PS7 gate.
Measured on Windows PowerShell 5.1, not inferred.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The Markdown heading for the non-applicable bucket read "Not a wire field
(nothing to inspect)". That is a confident false statement about six real
cmdlets. Connect-PfbArray -Username/-Password are OutsideStandardRequest and
are POSTed to /api/login by hand (Public/Connection/Connect-PfbArray.ps1:331
builds the body, :351 sends it via Invoke-WebRequest); -ClientId/-Issuer/-KeyId
reach the OAuth2 token request the same way.

Plan Correction 1 requires these rows be classified as outside the standard
request-payload resolver, NOT as having no wire effect, and Completion
Condition 7 spells out the same limit. Publishing the stronger claim in prose
is the same class of error as writing a wrong row into JSON -- the never-guess
contract does not stop at the artifact boundary.

The heading now names the resolver's reach rather than the parameter's
behaviour, and a disclaimer under it says what OutsideStandardRequest does and
does not mean, with the Connect-PfbArray case as the worked example.

The new test asserts BOTH halves: the retired wording must not come back, and
the disclaimer must be present. Asserting only the first could be satisfied by
deleting the section entirely, which would lose the rows instead of describing
them correctly.

Coverage pin: Build-PfbFieldCmdletMap.Tests.ps1 25 -> 26 on Windows PowerShell
5.1 for that test, measured not inferred. The header note's arithmetic was also
wrong independently of this change -- it said one entry had moved since the 297
seed and summed to 307, but PfbApiDriftTools.Tests.ps1 had already moved 8 -> 12
for issue dmann000#113. Recomputed from the map: 297 + 4 + 11 = 312.
Compare-PfbInventoryTuple.ps1 defaulted -BaselineRef to origin/main while the
declaration file recorded nothing about the ref its tuples were measured at, so
the documented invocation only worked by coincidence. On a stacked branch,
origin/main is not an ancestor of the measured ref, and every improvement made
by the branches in between is then reported as an undeclared change. The next
maintainer either concludes the tool is broken or pastes those rows in to
silence it -- pre-authorising movement nobody reviewed, which is the
anti-rubber-stamp rail running backwards.

The file is now an object with a required baselineRef, which wins over the
parameter default when -BaselineRef is not explicitly passed. Explicit
-BaselineRef still overrides, so comparing declarations against some other ref
stays possible. The resolved ref and its provenance are printed on every run.

A bare array is refused with its own diagnostic rather than quietly accepted.
That refusal needs ConvertFrom-Json -NoEnumerate: without it the pipeline
unrolls a ONE-element JSON array into a bare PSCustomObject, the -is [array]
test never fires, and a single-declaration array falls through to the less
specific "no baselineRef" error. Both refusal paths were probed and each now
emits its own message.

Retirement is documented rather than made a softer rail. Once a declared change
lands, every entry matches nothing and the run goes red with one STALE-DECL per
entry; the script now prints a NOTE naming that case and pointing at
tools/inventory-tuple-baselines/landed/, which it never reads. Teaching the
unused-declaration rail to tolerate the expected case would also teach it to
tolerate the typo'd key it was built to find.

Also resolves tar by absolute path on Windows: a bare `tar` under a pwsh
launched from Git Bash gets GNU tar, which reads the leading C: of the archive
path as a remote host spec and aborts with "Cannot connect to C: resolve
failed".
…EADME

Compare-PfbInventoryTuple.ps1 was the only script in tools/ with no entry in
tools/README.md, so the one place a contributor looks to find out what is in
this directory did not mention the gate that a resolver change is supposed to
pass. An undiscoverable gate is not a gate.

Adds a "Resolver regression gate" section covering why totals cannot catch a
withdrawn resolution (the real Task 3 case: Typed +61 while
Update-PfbBucketAuditFilter -BucketName silently went from bucket_names to
unresolved), the working invocation, the declaration-file shape, and the
retirement step. Also adds it to the numbered list, flagged as the one entry
there that generates nothing and runs on demand rather than as part of a
normal pipeline run.

The issue-dmann000#141 Task 4 paragraph in the field-to-cmdlet section is corrected the
same way the report heading was: OutsideStandardRequest is a statement about
this resolver's reach, not about whether the parameter reaches the wire.
Get-PfbCommonQueryParamHelperWireName collapses two different answers into one
$null: "no Add-PfbCommonQueryParams call names this parameter" (silence, keep
looking) and "two calls disagree about it" (abstention, stop looking).
Resolve-PfbParameterWireLanding is safe because it retries on an empty landing
set rather than on a $null from any single resolver, but that safety lives at
the caller and was documented only there.

A future caller that treated this $null as "not my parameter" and fell through
to a looser resolver would turn a deliberate abstention into a confident wrong
wire name. Recording the hazard where the ambiguity is actually created.

No behaviour change. Nothing in the tree reaches the disagreement branch today,
and that is measured: only Get-PfbQuotaUser has two helper call sites, both
target $queryParams, and the ByParameterName rule has no Name entry, so the
distinct (WireName, TargetVariable) count is 1 tree-wide.
…mp leak

Three defects from the round-1 fix diff, all in what the fix itself added.

The RESIDUAL ABSTENTION HAZARD block on Get-PfbCommonQueryParamHelperWireName
described the hazard as mitigated, which is backwards in three ways and
contradicted the correct account 250 lines away on
Resolve-PfbParameterWireLanding ("One abstention remains invisible here").
Resolution is strict tier precedence, not collection from every resolver; the
accumulator retry lives in Get-PfbCmdletParameterInventory, not in
Resolve-PfbParameterWireLanding; and retrying on an empty landing set is the
mechanism by which this abstention ESCAPES, not a guard against it -- an
abstaining helper leaves the tier empty, which is precisely the retry trigger.
Two comments giving opposite accounts is bad in any file; in the one whose
whole thesis is never-guess, the wrong one reads as permission. Rewritten as
the recorded limit it is. The measurement that nothing reaches it today, and
the warning to future callers, are unchanged.

The recompute command added to coverage-baseline.psd1 did not work: ExpectedSkips
is nested under winps51 and pwsh7, not at the root. Verbatim it throws on pwsh 7
and silently yields nothing on 5.1 without StrictMode -- the worse direction,
since a mitigation for "a running total drifts silently" that returns nothing
would confirm any total put to it. Now names the edition, and says why that is
not optional.

Compare-PfbInventoryTuple.ps1 leaked an empty temp directory on either
declaration-validation refusal: moving the declaration read ahead of the archive
(correct in itself) left the two new throws outside the try whose finally is the
only cleanup. The scratch directory is now created immediately before that try,
so nothing thrown during validation has a directory to leak. Verified against a
control that the counter can see such a directory: 0 before, 0 after both
refusal probes.
Add a `classification` and a `declaredElsewhere` field to every dead-key
record in Reports/PfbDeadKeyReport.json (issue dmann000#141 Task 5). A dead key is
now labelled WRONG-SURFACE when the endpoint declares the key as a request
body property under some verb, WRONG-VERB when another verb declares it as a
query key, and UNDECLARED when no operation on the endpoint declares it on
either surface. `declaredElsewhere` carries the provenance -- the
(method, surface) pairs the classification is derived from -- deduplicated
and ordered by method then surface ordinally.

The declaration index reuses Get-PfbSpecCapabilities for body properties, so
the $ref/allOf walk that reaches Alert.flagged is the repo's existing walker
rather than a second implementation, and reuses Get-PfbDeclaredQueryKey for
query keys, so the index can never disagree with the gate that decided the
key was dead. Capability records' Parameters field is deliberately not used:
it is not filtered by `in:`, and 630 header-parameter occurrences across 629
of 632 operations would otherwise be read as query declarations.

Deadness itself is unchanged: the pre-existing deadKeys and
noSurvivingSelector populations are byte-identical on their pre-existing
fields, and the 66 parameters previously bucketed as "wire name unresolved"
now split 32/28/6 across unresolved, outside-standard-request and
not-wire-parameter with no leakage.

Reports/PfbDeadKeyReport.json is deliberately NOT regenerated here; the
regeneration gate's staleness assertion stays red until Task 6 refreshes the
artifact and re-baselines Tests/coverage-baseline.psd1.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review round 1. The endpoint key of the declaration index was an uncovered
guard, not an equivalent mutant as previously reported: the deadness gate
reaches the spec through PSObject property access on a ConvertFrom-Json
object, which is case-insensitive, so a cmdlet whose -Endpoint literal
differs in case from the spec path key is gated normally and reaches
classification. An ordinal-exact index would then miss it and publish
UNDECLARED -- a positive assertion of absence about a key the same generator
can see.

Add one It driving a mixed-case endpoint fixture (spec /api/9.9/widgets,
cmdlet -Endpoint 'Widgets') against a lower-case control (/api/9.9/gadgets,
'gadgets') that is identical in every other respect. Measured: shipped code
classifies both WRONG-SURFACE with [PATCH/Body]; with the index dictionary
patched to StringComparer::Ordinal the mixed-case record becomes UNDECLARED
with an empty provenance while the control is unchanged, so the
discrimination is provably the case divergence and not the fixture. Not a
live defect -- all 85 real dead-key endpoint literals match a normalized spec
path exactly -- so this guards a future ordinal hardening.

Also, comments only, no behaviour change:
- soften the dedup comment from "cannot be produced" to "is not produced by
  any spec we pin", and say why the dedup is load-bearing anyway (the
  unstable introsort makes a duplicate pair reorder the committed artifact
  with no input change);
- record that MaxDepth 32 is inherited from Get-PfbSpecCapabilities' own
  default rather than pinned by this report;
- record the residual of the array-items hop: a Body site inside an array
  element reads identically to a top-level one;
- note that the explicit -MaxDepth 32 in the real-spec control does not
  exercise the issue dmann000#71 truncation, since depth 8 also returns all 18
  properties of PATCH /alerts.

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

Review round 2, comments and tests only -- no classifier behaviour changes.

The dedup comment imported the introsort-stability argument from the
top-level sorts, where it holds, to a place where it does not: a
declaredElsewhere site carries only Method and Surface and the projection
emits only those two, so a tie on both sort keys is a tie on the entire
serialised record and an unstable sort cannot reorder byte-identical
elements. Measured from a planted duplicate with the dedup removed: the two
entries serialise to one distinct string. The real consequence is a WRONG
ROW -- declaredElsewhere: [DELETE/Query, DELETE/Query] published in a
committed artifact -- so the comment now says that, points at the assertion
that catches it, and warns off the "add a tie-break property" remedy, which
makes the order total and still publishes both rows.

Should -Be is case-insensitive for strings, so the endpoint-literal
assertion could not fail for the reason it gave: lowercasing the emitted
record left the whole file green. It is now Should -BeExactly, and that
mutant is KILLED.

The anti-leak exclusion list was a hand-maintained literal, whose mechanical
response to a red is to append the offending endpoint -- a one-token edit
that disables the gate and looks like every legitimate edit around it. It is
now derived from the fixture spec (normalized paths whose operations declare
a requestBody), so an unjustified widening cannot be written and a fixture
path that loses its body cannot leave a stale exclusion behind. Re-planting a
document-keyed Body leak reds the assertion with the offender named, and the
derivation also proved the old hand-list was wrong in the other direction: it
omitted synthetic/undeclared.

Finally, the header claimed this file contributes six 5.1 skips. That has
been false since the first Task 5 commit; it is sixteen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Round-3 comment-only corrections. No executable line changes: all 42 changed
lines are comments, both files parse clean, and the scoped run is unchanged at
pwsh 7 15/1/0 (inherited staleness only) and WinPS 5.1 0/0/16, container ok.

- tools/Build-PfbDeadKeyReport.ps1: fix the cross-reference to the assertion
  that catches a duplicated declaredElsewhere entry -- the quoted string is at
  Tests/Build-PfbDeadKeyReport.Tests.ps1:251 and the assertion block is
  :249-252, not :247-249, which held only comments and an assignment.

- tools/Build-PfbDeadKeyReport.ps1: put the anti-tie-break caution at the head
  of Sort-PfbDeadKeyRecords, where the remedy it contradicts actually lives. A
  site object carries only Method and Surface, so there is no third property to
  break a tie with, and a total order would still publish both rows.

- Tests/Build-PfbDeadKeyReport.Tests.ps1: retract the falsified introsort claim
  in the two further places it had been copied to -- the deduplication
  assertion and the ordered-provenance assertion. Neither ordering depends on
  sort stability: the provenance sites have distinct sort keys so the
  comparison never returns 0, and a tie on (method, surface) is a tie on the
  entire serialised record. Both assertions are correct; only their stated
  reasons were wrong. The ordered form is justified by the comparer contract it
  pins instead.

- Tests/Build-PfbDeadKeyReport.Tests.ps1: record that the derived exclusion set
  is strictly larger than the literal it replaced, newly excluding
  synthetic/undeclared, so a Body leak there is caught only per-record.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Add two synthetic-fixture declarations of `policy_names` -- a body
property on SyntheticEndpointPatch and a query key on DELETE
synthetic/allow -- neither of which any fixture record needs declared.
They exist so that widening Get-PfbDeadKeyDeclarationSite's Body lookup
from the record's own endpoint to a union over the index stops being an
equivalent mutant: either widening now hands Remove-PfbSyntheticDeadKey's
dead PolicyName a Body provenance on synthetic/dead, which the anti-leak
control rejects.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Comment-only. An earlier draft of this paragraph, written before the
measurement existed, read as though the anti-leak assertion were broadly blind
to a scope error. It is not.

Measured: with the 'policy_names' body property now in the fixture, widening
the Body lookup to a document-wide union over the whole declaration index DOES
red the anti-leak assertion, on
'Remove-PfbSyntheticDeadKey|PolicyName on synthetic/dead' -- that endpoint
declares no request body, so it is not excluded and the leak surfaces there.
Before that fixture property existed the same widening produced zero offenders
and the assertion passed while the index was document-keyed.

So the residual is exactly one exempt endpoint ('synthetic/undeclared', newly
excluded because the derived set is strictly larger than the literal it
replaced), not a general weakness. The paragraph now says so.

Scoped run unchanged: pwsh 7 15/1/0 with the inherited staleness failure as the
only red, WinPS 5.1 0/0/16, container ok both. 12 changed lines, all comments,
zero executable; file parses clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Fixes a blocking review finding against my own prior commit, plus two
over-broad claims in comments I wrote. Comment-only: 31 changed lines, zero
executable, both files parse clean, scoped run unchanged at pwsh 7 15/1/0
(inherited staleness only) and WinPS 5.1 0/0/16, container ok.

BLOCKING. b4dbeba set out to fix a stale cross-reference and committed one that
was stale on arrival: it cited Tests/Build-PfbDeadKeyReport.Tests.ps1:249-252,
derived from the file BEFORE that same commit inserted seven lines of retraction
prose above the target. At HEAD those four lines are pure comment; the $keys
assignment is :256, the assertion :257-259, the quoted string :258. That is
worse than the range it replaced, which at least included the assignment.

Rather than re-point the range -- the second stale citation of this same
assertion in two rounds -- the pointer is now the quoted string
"has a duplicated declaredElsewhere entry", verified unique in that file. A
range is correct only if re-derived after the edit that writes it, and a pointer
that silently rots is worse than none when the comment bills itself as the thing
to follow on a red.

Two minors, both mine, both universals generalised from one measurement:

- The residual-scope paragraph said the exemption "is the ONLY blind spot". True
  of what the derivation added; false of the assertion in general. A union
  narrowed to 'flagged' leaks Body provenance onto 'synthetic/surface', excluded
  for reasons predating the derivation and caught by the ordered provenance
  assertion instead. Scoped to "the only blind spot the derivation added", with
  the counterexample recorded.

- The ordered-provenance justification claimed the assertion pins "method first,
  then surface, both ordinal". The ordering halves are measured -- dropping
  Surface and swapping the keys each red it -- but ordinality is not pinned:
  DELETE before PATCH and Body before Query sort identically under ordinal and
  culture-aware comparison, so a -Culture '' mutant survives. Claim narrowed and
  the real location of the ordinality argument named.

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

Round-5 fix, both findings against my own commit 30138d0 and both the same
class the commit before it was written to remove: a claim asserted without
being executed. Comment-only -- 55 changed lines, zero executable, executable
token skeleton unchanged at 2981, file parses clean, scoped run unchanged at
pwsh 7 15/1/0 (inherited staleness only) and WinPS 5.1 0/0/16, containers ok.

MAJOR 1. The residual-scope paragraph justified its narrowing with a second
counterexample -- "a union narrowed to 'flagged' leaks Body provenance onto
synthetic/surface" -- that is FALSE. 'Flagged' is a body property of
SyntheticSurfaceBase only, reached only by synthetic/surface PATCH, so a union
narrowed to it returns the record's OWN endpoint's sites: declaredElsewhere is
unchanged, nothing leaks, every assertion stays green. It is an equivalent
mutant, which is exactly the hazard the 'policy_names' comment defuses for the
*_names case and the reason that property had to be planted.

Replaced with the structural statement the measurement supports: the assertion
is blind to a Body leak landing on any of the four endpoints the exclusion
covers (synthetic/surface, synthetic/undeclared, widgets, gadgets -- verified
against the fixture), and the derivation added exactly one of them. The other
three were exempt under the hand-written literal too. The only measured
positive control is the 'policy_names' one, and the paragraph now says so and
says not to restore an unexecuted counterexample.

MAJOR 2. The ordinality retraction pointed the reader at
Tests/CommittedDeadKeyReport.Tests.ps1 for coverage that does not exist there
for this data: that file does not mention declaredElsewhere at all. What it
asserts (:507-508, :514-515) is ordinal order for the two TOP-LEVEL sorts only
-- deadKeys on (cmdlet, parameter) and noSurvivingSelector on (cmdlet, method,
endpoint). The comment now separates ARGUED from ASSERTED, names the top-level
scope, and states plainly that no fixture pins ordinality for the
declaredElsewhere (method, surface) sort -- nor can one while every real method
and both surfaces are same-case ASCII. A gap closed by argument, not assertion.

The other half was accurate and is kept: the ordinal-vs -Culture '' argument is
at the head of tools/Build-PfbDeadKeyReport.ps1 (:58-59, above
Sort-PfbDeadKeyRecords at :84) and governs the shared comparer.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Last Minor from the round-5 verification, and it is the same class one more
time: a direction word carried over from deleted text and re-asserted without
being checked, inside the very paragraph whose subject is not restating
unchecked things. The 'policy_names' comment is ABOVE, at :337-344 on the
SyntheticEndpointPatch schema, not below. Names the schema so the pointer does
not depend on relative position at all.

Comment-only: 3 changed lines, zero executable, executable token skeleton
unchanged at 2981, parses clean. Scoped run unchanged -- pwsh 7 15/1/0
(inherited staleness only), WinPS 5.1 0/0/16, containers ok.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Task 6 of issue dmann000#141. Regenerates the seven artifacts the resolver work moved,
re-baselines three tripwires and adds two skip-reason keys, retires Task 4's
tuple declaration, and pins the measured 5.1 skip count.

REGENERATION. Through scripts/Assert-PfbDerivedArtifacts.ps1, never a bare
generator. Seven of eleven checked artifacts were stale; PfbValueEnumMap.json
and PfbValueEnumReconciliation.md held, as expected. All eleven now report up
to date. PfbDeadKeyReport.json goes 31,597 -> 40,242 normalised characters,
which clears the inherited staleness failure Tasks 4 and 5 were forbidden from
fixing: Build-PfbDeadKeyReport.Tests.ps1 is now 16/0/0 on pwsh 7.

CONTROL FOR THE STALENESS. main (76f0c95) regenerates 11/11 up to date, so all
seven movements are attributable to this branch and none is pre-existing debt.

SEMANTIC MOVEMENT, verified by regenerating rather than by checking that the
arithmetic closes -- the plan previously carried a paired off-by-one that
reconciled just as correctly:

  keysEvaluated 1779, parametersInventoried 2168, dead keys 85,
  noSurvivingSelector 7, skip reasons 32 / 28 / 6 / 309 / 14 / 0.
  Classification census UNDECLARED 71, WRONG-VERB 13, WRONG-SURFACE 1.

Diff against main's committed report is +2 and -0. No previously-reported
record was lost.

THREE GATE MOVEMENTS, each justified beside its pin:

- baselineDeadKeyCount 83 -> 85. Raising a monotone gate is against the file's
  own stated discipline, so the justification is written in: dmann000#141 changes no
  cmdlet, it teaches the resolver three assignment shapes it had been skipping,
  and two of the newly-evaluated parameters were dead all along --
  Get-PfbAlert|Flagged|flagged|GET|alerts and
  New-PfbCertificateSigningRequest|Name|names|POST|certificates/certificate-signing-requests.
  Pre-existing module defects made visible, not introduced.
- baselineNoSurvivingSelectorCount 6 -> 7, with the CSR identity added to the
  allowlist. CSR has one selector-shaped query key and the operation declares
  zero.
- 'wire name unresolved' 127 -> 32. Lowering, and the failure mode here is
  leaving it high: 34 of the 66 null-WireName rows are now separately accounted
  for, so 127 would carry 95 rows of slack.

TWO NEW VOCABULARY KEYS, 'outside standard request' = 28 and
'not wire parameter' = 6. Required rather than optional: the scan treats an
unknown reason as an offender and separately asserts it visited every reason.
Both are ceilinged rather than unceilinged because, unlike 'body property',
each names a population the resolver positively classified rather than failed
to read.

PIPELINE SELECTOR -- the brief said this artifact must not move and it does.
Diagnosed rather than copied: probe pairs are 1247 on both sides while
candidates move 629 -> 647. The generator's code and probe population are
unchanged; its candidate set depends on how many parameters resolve to a wire
name, which is what this issue improved. Two consequences, both real:

- Build-PfbPipelineSelectorMap headline pin 264 -> 266 findings, 101 -> 102
  pairs, with probePairs left pinned at 1247 as the discriminator.
- Rail A gains one waiver: Get-PfbUserGroupQuotaPolicy|Name, Family scope, 2
  producers. This is a LIVE WIRE-CORRECTNESS DEFECT that dmann000#141 reveals -- the
  parameter stringifies a nested join item to
  names=@{context=; member=; policy=} on GET /user-group-quota-policies/
  file-systems and /members. Same root cause as the existing Get-PfbTlsPolicy
  and Get-PfbWormPolicy entries. Waived against dmann000#141 following the register's
  own convention of naming the revealing issue; a fix issue is owed.

STEP 6b. tools/inventory-tuple-baselines/issue-141-task4.json git mv'd to
landed/ (R100, byte-identical). The brief's rationale for this was wrong in
both directions and is corrected in the task report: the file is NOT currently
stale (it validates CLEAN, exit 0, 34/34 declared), and a run with no
-DeclarationPath is NOT clean (97 undeclared changes against origin/main, which
is the gate working as designed on a branch that moves 97 tuples). The move is
still right, on the authority of the file's own retirement note and the
script's documented trigger: staleness fires once these commits ARE the
baseline, i.e. at merge, and this is the PR that merges them. Retirement is
safe because the script has zero auto-discovery -- -DeclarationPath is
explicit-only -- so landed/ can never be read implicitly.

COVERAGE BASELINE. Build-PfbDeadKeyReport.Tests.ps1 6 -> 16, measured on
Windows PowerShell 5.1 for that file alone and read out of the runner's child
winps51.json rather than its Write-Host summary: 0/0/16, container ok. No
headroom added; these entries are exact. The tree-wide total is only measurable
by a full-suite run, which is CI's.

TESTS. Plan Step 7 list, both editions: pwsh 7 581/0/0, WinPS 5.1 418/0/163,
containers ok on both.

CONSTRAINTS. No change under Public/, Private/,
PureStorageFlashBladePowerShell.psd1 or .psm1 -- checked by explicit filename,
because a *.psd1 pathspec also matches Tests/coverage-baseline.psd1 and reads
as a false breach. No version bump, no CHANGELOG edit.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review fixes for 8472011. Three Major, two Minor, all of them bookkeeping that
the previous commit either edited or invalidated and did not carry through.
Comment- and documentation-only: executable token skeletons byte-identical for
all three .ps1 files (1304/1015/609), every file parses clean, and no module
file is touched.

MAJOR 1. Tests/coverage-baseline.psd1 reconciliation note said "TWO entries have
moved ... 297 + 4 + 11 = 312". 8472011 moved a third (Build-PfbDeadKeyReport
6 -> 16) and left the note alone. Recomputed from the map with the check the
note itself prescribes rather than incremented by hand: 19 entries, sum 322.
The paragraph immediately below exists because an earlier revision made this
exact error; it has now been made twice by the same mechanism, and the note
says so.

MAJOR 2. The Step 6b git mv left four documented invocations pointing at a path
that no longer exists -- Compare-PfbInventoryTuple.ps1 .DESCRIPTION and
.EXAMPLE, and two runnable blocks in tools/README.md. The .EXAMPLE is the
documented way to run the gate, so anyone copying it got a file-not-found and
would reasonably conclude the gate was broken. The three runnable sites now use
<your-declaration>.json; the prose site points at landed/ and says the file is
a shape to read, not a file to pass.

MAJOR 3. The rationale given for retiring the declaration file pre-merge is
withdrawn. It claimed the authority of the file's own retirement note, and that
note -- with Compare-PfbInventoryTuple.ps1:56 -- says retire it WHEN THE CHANGE
MERGES, which is the opposite. The move is kept on narrower and honest grounds:
the plan mandates it in this task, this is the PR that merges those commits,
and the script has zero auto-discovery so landed/ can never be read implicitly.
What is given up is stated rather than glossed -- the file was a live, passing
gate at the moment of retirement (CLEAN, 34/34 declared, exit 0) and a reviewer
who wants it must now run it explicitly against landed/. That sentence belongs
in the PR body.

MINOR 4. CommittedDeadKeyReport.Tests.ps1 attributed the whole keysEvaluated
delta to dmann000#141. Measured against main's committed artifact: churn took
2174/1757 -> 2168/1747, and dmann000#141 then raised keysEvaluated 1747 -> 1779. The
old text credited dmann000#141 with +22 where it earned +32 against a base that had
fallen.

MINOR 5. PfbPipelineSelectorRail.Tests.ps1 still carried 264/101 in three
places while the identical prose in the waiver fixture had been updated to
266/102, so the two disagreed. Now 266 / 102 / "101 of the 102".

NOT FIXED, deliberately: CommittedDeadKeyReport.Tests.ps1's "this collection
has only 18 entries" is wrong (noSurvivingSelector has 7) but is pre-existing
-- the identical line is at origin/main:289 -- so it is flagged for the
whole-branch review rather than pulled into this task's scope.

Scoped run over the six affected files, both editions: pwsh 7 83/0/0,
WinPS 5.1 23/0/60, containers ok.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Whole-branch final review of 21d7068: APPROVED, no Blocking, no defect in code,
artifacts, gates or the exemption. Every load-bearing numeric claim held on
independent measurement. What it did find was prose arguing from figures a
later task changed -- the failure mode its cross-task item exists to catch.
Comment- and doc-only; executable skeletons byte-identical for both .ps1 files.

MAJOR. tools/Compare-PfbInventoryTuple.ps1 .DESCRIPTION credited Task 3 with
raising the Typed count by 61. Task 3's own delta is +29 (2073 -> 2102); 61 is
the cumulative branch figure, which the tuple gate confirms independently --
62 rows entered Typed, 1 left, 2102 - 62 + 1 = 2041 on main. The narrative was
right and only the number was misattributed, but this docstring is the
authoritative explanation of why the gate exists.

The plan's Completion Condition 8 carried the same class of error and is fixed
in the plan file (not in this repo): three of its nine values were the
superseded 65 / 31 / 1,780 triple that Task 6 Step 4 had already retracted 175
lines earlier, so the branch failed its own definition of done while being
correct. Acceptance criteria are what a reader checks the artifact against, so
that one could have driven someone to "fix" the artifact toward the wrong
numbers.

MINOR. Tests/CommittedDeadKeyReport.Tests.ps1: "this collection has only 18
entries" describes noSurvivingSelector, which holds 7. Pre-existing --
byte-identical at origin/main:289 -- and I deferred it once as out of scope.
Fixing it now, because the reviewer's argument is better than mine was: this
commit raises baselineNoSurvivingSelectorCount 6 -> 7 four lines above it in
the same BeforeAll, and Task 6 already swept this exact file for prose arguing
from contradicted figures. Not unsafe either way; the assertion beneath it is
an equality against input size, not against 18.

MINOR. The Compare-PfbInventoryTuple docstring and the plan gave opposite
instructions for the retired declaration file -- "no longer a file to pass to
-DeclarationPath" against a plan sentence telling a reviewer to pass exactly
that path. Softened to "not a file any ROUTINE run should pass; pass it
explicitly only to reproduce Task 4's evidence", and it now records the
sharper reason retiring it pre-merge was right, which the review supplied:
its baselineRef is an intermediate feature-branch commit, and this repo
squash-merges, so after merge the ref may be unreachable and a future run
would fail on ref RESOLUTION rather than the 34 STALE-DECL Step 6b predicted.

Verification, re-run after these edits -- scoped 10-file list, both editions:
pwsh 7 581/0/0, WinPS 5.1 418/0/163, containers ok, TotalCount 581 on both
legs, so the 5.1 leg discovered the identical set rather than being a smaller
run wearing a green label.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A cmdlet may write the same wire key from a later arm of one if/elseif
chain as a convenience default derived from a different parameter.
Update-PfbBucketAuditFilter does this: -Name sets names, and an elseif
sets names from -BucketName so a -BucketName-only caller need not restate
the value. The resolver counted that arm as a landing of -BucketName, so
-BucketName appeared to land both bucket_names and names, the arbitration
abstained, and PATCH /buckets/audit-filters lost parser traceability and
dropped to partial confidence -- tripping the issue dmann000#31 guard in CI.

Test-PfbIsDefaultingAliasAssignment flags an index assignment whose key
and target variable are already written by an earlier sibling clause of
the same if/elseif chain from an expression that does not mention this
parameter. Flagged landings are dropped only when the parameter still has
an unflagged landing of its own. That proviso is load-bearing:
New-PfbFleetMember writes members from a FleetKey arm and again from a
-Members elseif, but neither defaults the other and -Members has no other
landing, so an unconditional drop deletes its only evidence and relocates
the same regression onto POST /fleets/members.

Measured against the previous commit: keysEvaluated 1779 -> 1780, resolved
ok 1694 -> 1695, wire-name-unresolved 32 -> 31, dead keys unchanged at 85,
field map 2067 -> 2068 entries (the single added entry being
Update-PfbBucketAuditFilter|BucketName|bucket_names). Selector findings are
unchanged at 266 rows over 102 pairs with probePairs still 1247 and control
leakage still 0; SelectorUnresolved falls 14 -> 6. A whole-module sweep now
reports zero wire-landing abstentions, where the same sweep on the previous
commit reports exactly one.

Also re-points the 102 selector waivers off closed dmann000#90: 64 to dmann000#152 (the
join-item class), 37 to dmann000#153 (items with no name for unrelated reasons),
and 1 to dmann000#123, which already tracks that pair against a different,
upstream blocker.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@juemerson-at-purestorage
juemerson-at-purestorage merged commit 0a168da into dmann000:main Aug 28, 2026
6 checks passed
@juemerson-at-purestorage
juemerson-at-purestorage deleted the fix/issue-141-wire-name-shapes branch August 29, 2026 01:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant