Repository navigation
Audit every pipeline-bound selector against the response schema, and guard the ones that cannot bind (#90) - #122
Merged
juemerson-at-purestorage merged 30 commits intoAug 18, 2026
Conversation
Replace the nonexistent member selector with explicit policy/filter and bucket selectors across bucket policy operations. Co-Authored-By: Claude <noreply@anthropic.com>
…mann000#90) Round-1 review fixes on the bucket policy/filter selector work: - New-PfbBucketAuditFilter now sends the spec-required `names` on every POST, defaulting it from -BucketName, so naming a filter and identifying its bucket are combinable rather than mutually exclusive. - Remove-PfbBucketCorsPolicy drops -PolicyName: `policy_names` is undeclared on the DELETE endpoint and is silently ignored, so combining it with a valid `bucket_names` deleted every CORS association on the bucket. The ShouldProcess target now states that blast radius. - -MemberId/`member_ids` renamed to [string[]]-BucketId/`bucket_ids` on the five cmdlets that carried it; the endpoints declare `bucket_ids` only. - -PolicyName/-PolicyId removed from Get-PfbBucketAccessPolicy and Get-PfbBucketCorsPolicy; those keys exist only on the /rules variants. - ValueFromPipeline moved to -BucketName on the Remove-* pair so a piped bucket name means the same thing there as on the Get-* siblings. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Expose certificate, realm, and local-port selectors under names matching their published wire contracts. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Lift parent policy and role names on emitted objects so property-name pipeline binding sends scalar selectors instead of stringified references. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Lift scalar names from nested response references without rebuilding or projecting API response objects. Converges Get-PfbObjectStoreAccessPolicyRole onto the Task 3 enrichment contract for both parent references, policy and member: the lift is now conditional on the nested object and its name being non-null, and it no longer -Force-overwrites an existing top-level property. The previous unconditional -Force lift created PolicyName/MemberName holding $null when the parent was absent, which still binds downstream by property name and sends an empty selector, making the consumer return everything. Get-PfbLocalGroupMember and Get-PfbArrayConnectionKey are unchanged; see task-4-report.md. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Lift group.name to a top-level GroupName property, and rename the selector
parameter from Group to GroupName so the lift can actually bind. Group and
group_name are retained as aliases, so -Group keeps working.
The rename is what makes the lift effective rather than cosmetic. During
by-property-name binding PowerShell resolves one property per parameter and
prefers the property matching the parameter NAME over any property matching
an alias, so a parameter named Group always resolved to the response's own
'group' reference object and never reached the lifted GroupName. Because
binding an object to [string[]] needs a conversion, the by-property-name pass
then failed outright and the next pass bound the whole piped item, putting a
stringified membership record on group_names. The report's own evidence for
the primary producer shows exactly that:
group_names=@{context=; group=; group_gid=...; member=; member_id=...}
Naming the parameter after the lifted property is the idiom the eight
already-converged cmdlets use (PolicyName <- policy.name), so this makes the
lift surface consistent rather than introducing a special case.
Restates the alias assertion as what now holds -- GroupName is the parameter,
Group and group_name are its aliases -- and adds a case proving -Group is
still bindable on the command line, which is the compatibility half the
original assertion existed to protect.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Drop public parameters that emitted query keys their endpoints never declared, so a filtered call no longer silently returns the unfiltered collection.
) Removing the dead -Name selectors took the only pipeline entry point on the open-file and resource-access cmdlets with them, leaving Get-PfbOpenFile | Remove-PfbOpenFile a hard binding error. The piped path never actually worked -- names was silently ignored by these endpoints -- but ending an issue about pipeline selector binding with a net loss of pipeline capability reads as a regression on upgrade. Add ValueFromPipelineByPropertyName to -Id on Get-PfbOpenFile, Remove-PfbOpenFile, Get-PfbResourceAccess and Remove-PfbResourceAccess. The response items carry id in every version the endpoints exist in (open-files 2.17-2.28, resource-accesses 2.19-2.28), and ids is the only declared DELETE selector on both, so this binds a real selector rather than resurrecting a dead key. Bare ValueFromPipeline is deliberately NOT added: piping a bare string has no correct meaning now that the name selector is gone, and that coercion path is what this work exists to remove.
Replace the nonexistent member selector on the remaining bucket operations and drop the undeclared generic id selector, so a filtered call no longer returns the unfiltered collection. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…rtions (dmann000#90) Follow-ups from task review: examples referenced fields their items do not carry, and the no-bare-pipeline guarantee was asserted narrowly. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…n000#90) POST buckets/cross-origin-resource-sharing-policies/rules declares names as a required query parameter, but the cmdlet sent no query parameters at all and so was unable to satisfy it. Add the bucket, policy and rule name selectors the operation declares, with the required key reachable in every parameter set. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
fix: correct example body shapes and guard empty required selectors (dmann000#90) Examples on the two bucket policy rule cmdlets advertised request-body fields and shapes the endpoints do not accept. New-PfbBucketAccessPolicyRule passed principals as a string array, but BucketAccessPolicyRulePost declares principals as an object shaped { all: boolean }, and constrains actions to s3:GetObject alone; effect is readOnly, so no caller can send it. The New-PfbBucketCorsPolicyRule examples used method and origin subsets the schema documents as unsupported. All examples now show only content a caller could really send, verified against every spec version the endpoints exist in (REST 2.12 through 2.28), and the access-policy-rule test fixture no longer implies a caller can send the readOnly effect. Separately, a required selector could be silently dropped by an empty array. Update-PfbBucketAuditFilter -Name is not Mandatory, so -Name @() and -Name @() with an empty string bound successfully, ContainsKey reported true, and the endpoint-required names query key went on the wire empty -- reconstructing the dead-selector defect this work exists to remove. ValidateNotNullOrEmpty now rejects both forms, and is applied to the required key parameters of the two rule cmdlets as well. Access policy examples now use the module predominant fully-qualified form mybucket/myaccount:policy. The per-parameter-set names assertion now asserts per set rather than by total invocation count. @
…dmann000#90) New-PfbBucketAccessPolicy and New-PfbBucketCorsPolicy gained ValueFromPipeline on their Mandatory -BucketName during this work, which let a piped bucket object bind stringified into a selector on a write path -- an invocation that was previously impossible. Both now match their sibling rule cmdlets and accept pipeline input by property name only. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…nn000#90) Seventeen read cmdlets declare ValueFromPipeline on a name selector. A piped object matching none of their parameters by name falls through to by-value coercion and the whole object goes on the wire stringified -- HTTP 200, and the array returns the unfiltered collection, which is the defect this work exists to remove. The earlier lifts fixed the self-chain; they cannot help when the piped item comes from a different endpoint in the same family. A shared guard now rejects that value with an actionable module-level error naming the property to pipe instead. Bare-string piping and by-property-name binding are unaffected. Removing ValueFromPipeline was rejected: it would break bare-string piping, and it does not eliminate coercion where an alias matches an object-valued property. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…mann000#90) A fourth site still asserted that a ValueFromPipelineByPropertyName-only parameter cannot coerce, contradicting the correction made alongside the new guard. The Guarded legend and its help text credited every guarded row to dmann000#64, which will be wrong for the majority of rows once the reports are regenerated. Also corrects the new helper's inaccurate backtick rationale, a test title that named a distinction it does not test, and a hint that missed the chain the guard actually fires on. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…guards The seventeen guarded read cmdlets now classify as Guarded and the two bucket POST cmdlets as Unbindable, dropping Coerced from 371 to 268 and Primary coerced pairs from 21 to 2. Waivers for pairs the guard now covers are removed. The unresolved wire-name ceiling moves to 126 for New-PfbBucketAuditFilter|Name, a parameter added during this work that was never evaluable rather than one that stopped being so. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…000#90) Several conclusions in this work are counter-intuitive enough to invite a well-meaning future change that would reintroduce the defect: a cross-endpoint chain cannot filter correctly and can only be made to fail loudly, the runtime lifts work but are invisible to the selector rail by construction, and removing ValueFromPipeline is not the general remedy because an alias matching an object-valued property still coerces. Each affected cmdlet now carries a short in-line note pointing at the full reasoning. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ets (dmann000#90) Eight guarded read cmdlets carried the reasoning only in their runtime Hint text, which addresses a caller who mis-piped rather than a maintainer deciding whether the guard is worth keeping. Each now states in-line that a cross-endpoint chain has no correct generic binding, that a loud failure is therefore the best available outcome, and that adding an alias would turn the rail green without fixing anything. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…dmann000#90) The example-chain scanner reads every line of every Public cmdlet, comments included, so the maintainer notes added for dmann000#90 registered "A | B" prose as documented example chains and flipped FromExample on eight rows, reddening the regeneration rail. The notes now say the same thing without a pipe between two cmdlet names, and the scanner's docstring records the hazard. Also refreshes stale prose in the waiver register: a retained waiver's mechanism sentence, an emptied cluster heading, and two claims the four-pass finding falsified. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ionKey (dmann000#90) Piping an array connection object into Get-PfbArrayConnectionKey silently returned every key. GET /array-connections items carry no `name`, so the piped object matched nothing by property name, fell through to PowerShell's ByValue-with-coercion pass, and went onto the wire stringified as `names=@{...}`. The array answers that with HTTP 200 and the UNFILTERED collection, so the caller believes they are looking at one connection's key. Two changes, both in Public/Replication/Get-PfbArrayConnectionKey.ps1: - Add -Id, bound ValueFromPipelineByPropertyName. The endpoint declares the generic `ids` query key from REST 2.0 and an array connection carries `id`, so this is a real fix rather than a workaround: the object now binds at binding pass 2 and never reaches coercion. -Name and -Id occupy exclusive parameter sets because the spec states `ids` "cannot be provided together with the `name` or `names` query parameters" -- an illegal key combination is precisely the request that can come back 200 and unfiltered, so it is refused at bind time instead. Both keys are generic on this endpoint, so Add-PfbCommonQueryParams carries them unchanged. - Add the Assert-PfbSelectorNotCoerced guard as the first statement of process, matching the seventeen read cmdlets guarded in 8efb810. The residual it closes is the self-chain: this endpoint's own items carry neither name nor id, so they still coerce. -Name stays waived and un-aliased for the original reason -- the endpoint's items are connection_key/created/expires, so no name can ever bind. The param-block comment now states both halves: -Name remains impossible, identity-based piping is supported via -Id. The Get-PfbArrayConnectionKey|Name waiver in Tests/Fixtures/PfbSelectorWaivers.psd1 is deleted, not downgraded. Its four Coerced rows become one Guarded (the connection-key producer itself) and three NoSelector, so the pair is no longer a finding, and Rail A fails on a waiver whose pair no longer coerces. The new Get-PfbArrayConnectionKey|Id rows measure Bound on the three producers that carry `id` and Guarded on the one that does not. Report totals move from 268 rows / 102 pairs to 264 / 101; the Cluster 2 heading and the register preamble are updated to match. Reports/PfbPipelineSelectorMap.{json,md} regenerated in the same commit. The only other delta is FromExample flipping true on the Get-PfbArrayConnection producer row, which is the new .EXAMPLE chain being correctly detected. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
b7ddff9 added -Id so that array-connection output piped into this cmdlet would filter. That cannot work, and the published spec settles it: components.schemas.ArrayConnectionKey -- the item type GET array-connections/connection-key returns -- declares exactly connection_key, created and expires at every version 2.0 through 2.28, and is a flat object with no allOf, so it never inherits the base schema that supplies id and name to ordinary resources. The generic ids and names query keys select resources of the endpoint they are given to, by the identifier those resources carry. An item carrying neither id nor name gives ids nothing to match, so ids cannot select on this endpoint, and the same reasoning applies to names. The authoring error was reasoning from the PRODUCER endpoint's response shape (GET /array-connections items do carry id) to the CONSUMER endpoint's query capability. Those are different endpoints. -Name stays -- the spec declares the key, so the cmdlet sends it -- but its help no longer promises filtering the endpoint cannot do. The process-block coercion guard is therefore the entire remedy: it turns a silently unfiltered result into a loud error. Also: reword the guard hint away from the -Id chain it now mispoints at, drop the parameter sets and the pipeline .EXAMPLE that documented the chain, correct the waiver fixture's prose to credit the guard alone, and regenerate Reports/PfbPipelineSelectorMap.{json,md}. The four Get-PfbArrayConnectionKey|Name rows go Coerced/NoSelector -> Guarded, the four |Id rows disappear, and the FromExample flip reverts to false. Reports/PfbDeadKeyReport.json regenerates unchanged. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Compare bound selector text with the original process input so object types whose ToString output lacks @{ cannot bypass the guard, while legitimate type-like resource names remain valid.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Restore legacy audit-filter aliases, remove undeclared open-file keys, document lift trade-offs, and regenerate the affected reports with tightened regression ceilings. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Keep the monotone gate's explanatory counts aligned with its newly tightened issue-90 ceilings. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Remove the last executable help example while retaining a one-line pointer to the existing drift record for the spec-required protocols gap. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Correct the baseline comment to reflect that the monotone destructive allowlist intentionally retains seven obsolete ceiling entries beyond the 13 current findings. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Tighten the destructive and no-surviving-selector allowlists to the regenerated issue-90 artifact now that their stale entries are proven absent. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The review fix removed every example from this cmdlet, leaving it as one of only three public cmdlets with none. The pipeline example in particular documented why -Id declares ValueFromPipelineByPropertyName, which is the behaviour this branch exists to establish. Both examples are reworded to avoid asserting how the array responds to a request that omits the spec-required protocols key. The gap is named and pointed at the drift report instead. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Three documentation corrections from the scoped re-review of the fix round.
The BindingPropertyName docstring claimed the '@{' signal still catches an
object-valued property that pass 4 stringifies. That is false for exactly the
types the widened guard was written for: a Hashtable and a generic Dictionary
render as bare type names, so piping an object with a Hashtable-valued property
binds at pass 4 via an alias and passes both signals. The whole-input comparison
cannot cover it either, because the item's own ToString() is not the bound value.
The gap is now stated, along with why closing it is a maintainer's decision and
why a response object piped from a real cmdlet stays covered.
The restored Get-PfbOpenFile pipeline example attributed the -Id binding to this
cmdlet when it belongs to Remove-PfbOpenFile. Reworded, and the -Id example is
restored so the cmdlet's only selector is demonstrated.
The Update-PfbBucketAuditFilter aliases now record why the Remove-Pfb* cmdlets in
the same family deliberately received none, so the asymmetry does not read as an
oversight to the next reader.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
CI failed on all four OS/edition jobs with 3162 passed and exactly 2 failed.
Both failures were baseline figures pinned during the Stage 1 audit that the
Stage 2 fixes deliberately invalidated:
Build-PfbPipelineSelectorMap.Tests.ps1 probePairs 1179 -> 1247,
findings 389 -> 264,
coerced pairs 127 -> 101
PfbPipelineSelectorTools.Tests.ps1 bound rows 303 -> 311,
ValueFromPipeline 214 -> 209
Neither number was simply overwritten. The module-wide population change was
verified by diffing Get-PfbPipelineBoundParameter between origin/main and this
branch: 26 rows added, 18 removed, net +8. Every added row is either the new
name of a renamed selector (MemberName -> BucketName across the bucket-policy
cmdlets, Name -> CertificateName / GroupName / LocalPortName / RealmName), a
selector replacing a removed dead one (-Id on the open-file and resource-access
cmdlets), or a parent-name selector added so a policy-family pipeline binds by
property name rather than coercing. Every removed row is the old name of one of
those renames or a dead selector this branch deleted. No row changed its
ValueFromPipeline value, which matches the branch's finding that removing the
attribute grants no structural immunity under four-pass binding.
The coerced-pair figure is cross-checked rather than free-standing: 101 is also
the entry count of Tests/Fixtures/PfbSelectorWaivers.psd1, and Rail A fails on a
waiver for a pair that no longer coerces, so the two cannot drift apart quietly.
The reasoning behind each figure is recorded in the tests themselves, so a future
reader can tell a justified re-baseline from a silenced tripwire.
Also renames the first test from 'reproduces the Stage 1 headline numbers' to
'reproduces the measured headline numbers', since it no longer pins Stage 1.
The rest of the run was clean: the real coverage gate reported "Coverage gate
passed" for both pwsh7 and winps51 with a tree-wide skip count of 2 against a
ceiling of 5, so no baseline ceiling needed raising.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
juemerson-at-purestorage
deleted the
fix/issue-90-pipeline-selector-fixes
branch
August 18, 2026 05:14
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #90.
What this does
#90 asked for three things: audit every pipeline-bound selector against the field its resource
actually returns, decide per cmdlet whether to guard / alias / drop, and add a rail so a future
cmdlet cannot reintroduce the defect quietly. All three are here.
The audit is observational, not inferred.
tools/Build-PfbPipelineSelectorMap.ps1imports theshipped module, shadows the request layer inside module scope with a capture shim, pipes a
schema-derived probe object into the real cmdlet, and records what actually reached the wire.
Nothing in the report is a conclusion drawn from reading source.
Result over 1,247 probe pairs (1,213 evaluated, 629 genuine candidates, control leakage 0):
BoundCoercedUnbindableGuardedNoSelectorCmdletErrorBindErrorA correction to the issue's premise
#90 (and the tooling written for #64) states that binding resolves in three passes. It resolves
in four — ByValue, ByPropertyName, ByValue with coercion, then ByPropertyName with
coercion. Measured on pwsh 7.6.4.
This is not pedantry, it changes a remedy. The three-pass model implies that removing
ValueFromPipelinemakes coercion structurally impossible, soUnbindablewas documented asimmunity. It is not: a
ValueFromPipelineByPropertyName-only parameter whose alias matches anobject-valued property still binds that object stringified at pass 4. Such a parameter needs a
guard, not an attribute removal. The tooling's comments and the
Unbindableverdict text arecorrected accordingly.
What changed, by kind
A coercion guard — the one new runtime component.
Private/Assert-PfbSelectorNotCoerced.ps1,called as the first statement of
processin 18 read cmdlets. It turns a silently unfilteredresult into an actionable error naming the property to pipe instead.
Two constraints in it are load-bearing and commented as such: it must stay imperative in
process, because aValidateScriptfailure on a pipeline-bound argument is non-terminatingand
endwould still issue the unfiltered request; and its message must keep the substringstringified object, which is how the map generator classifies theGuardedoutcome.Its predicate is structural rather than textual. It compares the bound selector against the
original pipeline item's own
ToString()and suppresses the check when the item exposes a propertythat could legitimately have bound. That catches a
Hashtableand a genericDictionary, whichrender as bare type names and were invisible to the previous
@{test, without the type-namepattern matching that would falsely reject a resource legitimately named e.g.
System.backup.Selector keys corrected where a cmdlet sent a generic
names/idsto an endpoint that declaresa resource-specific key instead, and parent names preserved so policy-family pipelines bind at
pass 2 rather than falling through to coercion.
Dead selectors removed — parameters the endpoint declares no key for, which were therefore
either dropped on the wire or sent as a key the array ignores.
Reports regenerated: selector map, dead-key report, field/cmdlet map, API drift report.
Breaking changes
Please read this section rather than skimming it. Every removal below is a parameter that could
not work — the endpoint declares no corresponding query key at any cached spec version, so the
parameter either silently did nothing or put a key on the wire that the array ignores while
returning the unfiltered collection. The authoritative pinned list is
Tests/PfbDeadSelectorRemoval.Tests.ps1.Renamed, old name still callable — not breaking
Update-PfbBucketAuditFilter:-MemberName→-BucketName,-MemberId→-BucketId,-FilterNames→-Name. All three old names retained as aliases.Get-PfbLocalGroupMember:-Group→-GroupName, withGroupretained as an alias.Parameters removed — breaking
Get-PfbBucketAccessPolicy-Id,-MemberId,-MemberName,-PolicyId,-PolicyNameGet-PfbBucketAccessPolicyRule-Id,-MemberNameGet-PfbBucketAuditFilter-MemberId,-MemberNameGet-PfbBucketCorsPolicy-Id,-MemberId,-MemberName,-PolicyNameGet-PfbBucketCorsPolicyRule-Id,-MemberNameNew-PfbBucketAccessPolicy-MemberName,-PolicyNameNew-PfbBucketAccessPolicyRule-MemberNameNew-PfbBucketAuditFilter-MemberNameNew-PfbBucketCorsPolicy-MemberName,-PolicyNameRemove-PfbBucketAccessPolicy-MemberName,-PolicyNameRemove-PfbBucketAuditFilter-MemberId,-MemberNameRemove-PfbBucketCorsPolicy-MemberId,-MemberName,-PolicyNameGet-PfbCertificateGroupCertificate-NameGet-PfbObjectStoreTrustPolicyRule-PolicyIdGet-PfbOpenFile-Filter,-Name,-SortRemove-PfbOpenFile-NameOn
Get-PfbOpenFile,filterandsortare declared at no version 2.17–2.28 (the endpointdoes not exist before 2.17), so they were inert.
Note one deliberate asymmetry: the
Remove-Pfb*bucket cmdlets underwent the sameMemberName/MemberIdrename but were given no compatibility aliases, whileUpdate-*was.Their legacy parameters wrote undeclared keys and so over-deleted; aliasing those onto a key that
does work would quietly change what a
DELETEremoves, which is worse than the break. Therationale is recorded in the code so it does not read as an oversight.
Regression protection
Tests/PfbPipelineSelectorRail.Tests.ps1— Rail A asserts no unwaived selector coercion,and surrounds it with waiver hygiene: every waiver must name a cmdlet, parameter, issue and
reason, be used exactly once, still cover exactly the producer count it was granted for, and —
the sharp one — carry no waiver for a pair that no longer coerces, so a stale waiver fails
rather than lingering. Rail B regenerates the map and requires byte-identical JSON and
Markdown.
Tests/Fixtures/PfbSelectorWaivers.psd1— 101 entries, one per remaining coercion pair, eachwith
Cmdlet / Parameter / Issue / Producers / Scope / Why. 100 are Family-scoped. These arepairs that cannot be fixed within one cmdlet: the coercion originates at a cross-endpoint
producer, so the ceiling is documented per pair rather than waved at.
Rail B self-skips when the gitignored
tools/specscache is absent, which is worth knowing whenreviewing from a fresh clone.
Verification
Unit — scoped, both editions. Per-task and final runs were scoped to the touched files and run
under pwsh 7 and Windows PowerShell 5.1, judged by the container column rather than the counts.
Final re-review run: pwsh 7
292 passed / 0 failed / container ok; WinPS 5.1281 passed / 0 failed / 11 skipped / container ok.The aggregate full suite was not run locally — it exceeds the local tool timeout and CI owns it.
Aggregate, cross-OS — CI, and it caught something the scoped runs could not. The first CI run
on this branch failed on all four OS/edition jobs: 3,162 passed, exactly 2 failed. Both failures
were in tests this PR itself pins, and neither was a defect in the shipped code — but they were
real failures and are worth reading rather than skipping to the green result below.
Both were baseline figures pinned during the Stage 1 audit that the Stage 2 fixes deliberately
invalidated —
probePairs1179→1247,findings389→264, coerced pairs 127→101 inTests/Build-PfbPipelineSelectorMap.Tests.ps1, and the module-wide pipeline-bound population303→311 with
ValueFromPipeline214→209 inTests/PfbPipelineSelectorTools.Tests.ps1.These escaped every scoped per-task run because they assert on the module and the committed
artifact as a whole rather than on the files any one task touched — the exact blind spot scoping
trades away, and the reason CI runs the aggregate.
Both are now re-baselined, and neither figure was simply overwritten. The population change was
verified by diffing
Get-PfbPipelineBoundParameterbetweenorigin/mainand this branch: 26 rowsadded, 18 removed, net +8. Every added row is the new name of a renamed selector, a selector
replacing a removed dead one, or a parent-name selector added so a policy-family pipeline binds by
property name instead of coercing; every removed row is one of those old names or a dead selector
this branch deleted. No row changed its
ValueFromPipelinevalue, which independentlycorroborates the four-pass correction above — the attribute removal was rejected as a remedy, so
nothing used it. The coerced-pair figure is cross-checked rather than free-standing: 101 is also the
waiver fixture's entry count, and Rail A fails on a waiver for a pair that no longer coerces, so the
two cannot drift apart quietly. The reasoning is recorded in the tests, so a later reader can tell a
justified re-baseline from a silenced tripwire.
The re-run is green on all four jobs:
3164 passed / 0 failed / 2 skippedon pwsh 7 acrossubuntu, macos and windows, and
2908 passed / 0 failed / 258 skippedon Windows PowerShell 5.1.That also settles the one thing only a full-suite result can measure. The coverage gate reported
Coverage gate passedfor both the pwsh7 and winps51 profiles on every job — so theTests/coverage-baseline.psd1ceilings hold as-is, and no ceiling was raised against an unmeasuredfigure.
Live — a lab FlashBlade at REST 2.26, reads and scoped writes. Mocked tests pass while a wire
contract is wrong, so the guard and the corrected keys were exercised against a real array.
Gaps, stated rather than omitted:
groups/members, NFS export rules, or bucket access policies, so those paths are verified only
for request construction and guard behaviour, not for correct rows.
Skipped-Policyfrom the live harness's safety rails — the railsworking as designed, not evidence of success.
Get-PfbOpenFile's removed-Filter/-Sortwere inert is spec-derived, notmeasured on the wire.
Found along the way, filed separately
Invoke-PfbApiRequestreturns a one-elementtotal_item_countwrapper on anyempty list result, which is truthy, reports
Measure-Objectcount 1 when the answer is zero, andcoerces on the pipeline into
names=@{total_item_count=0}. On at least one endpoint the arrayignores that key and returns the full collection. Pre-existing on
main, untouched here, andin the shared response layer, so it needs its own change and its own decision about
-TotalOnly.The guard in this PR happens to catch it on its 18 cmdlets — useful evidence the approach
generalises, but not a fix.
Assert-PfbAdminNameNotCoerced,Assert-PfbRemoteNameNotCoerced,Assert-PfbFileSystemReplicaLinkTransferNameNotCoerced) still document the falsified three-passmodel. Pre-existing on
mainand out of scope here; worth a follow-up sweep.property binding at pass 4 via an alias passes both signals. Closing it means comparing against
each binding property's
ToString(), which widens the false-positive surface — a maintainer'scall, not a drive-by. Reachability is low because
ConvertFrom-JsonyieldsPSCustomObject,whose
ToString()does contain@{.Two upstream specification defects were also established during this work and are being routed
to the API team rather than filed here, since neither is a module bug:
GET /file-systems/open-filesadvertisesprotocols=nfs, which the array rejects withUnsupported protocols specified in the parameterswhile acceptingsmb.idsand eight declarenamesover item schemas carrying no such field,so the selector silently does nothing and the caller gets an unfiltered 200.
Not included, deliberately
No version bump and no CHANGELOG entry — those are maintainer decisions in this repo.
Diff size
58 files, +10,231 / −4,933. The bulk is regenerated report artifacts and new tests; the runtime
change is one new private helper plus selector corrections across 35 files under
Public/.