Summary
Remove-PfbFileSystem sends the query key delete_link_on_eradication on two different
operations. The published OpenAPI declares it on only one of them, and the module's two safety
gates are each structurally incapable of noticing. Separately, the cmdlet's help text states the
opposite of what the spec says, and contradicts an inline comment in its own body.
Nothing here is a measured wire failure. What the impact actually is depends on how the array
treats an undeclared query parameter, which is not established — see Impact is open below.
This issue is filed for the parts that are provable statically.
Cross-reference, added 2026-08-28. The same soft-delete branch has a second, unrelated
defect -- the protocol-disable step at line 74 is wrapped in a bare catch { }, so its failure is
discarded and the destroy at line 77 runs anyway. That is already tracked in #117 (see its
"Plus one, related but a different failure mode" section); it is noted here only because a reviewer
touching this branch will see both, and both want the same live FlashBlade session. Do not fix it
under this issue -- #117 owns it.
What the cmdlet sends
Public/FileSystem/Remove-PfbFileSystem.ps1:
| line |
branch |
operation |
carries the key |
| 74 |
soft delete, protocol disable |
PATCH /file-systems |
no |
| 77 |
soft delete, destroy |
PATCH /file-systems |
yes, via $destroyQuery |
| 84 |
eradicate |
DELETE /file-systems |
yes, via $queryParams |
# line 64-65, soft-delete branch
$destroyQuery = @{} + $queryParams
if ($DeleteLinkOnEradication) { $destroyQuery['delete_link_on_eradication'] = 'true' }
# line 82, eradicate branch
if ($DeleteLinkOnEradication) { $queryParams['delete_link_on_eradication'] = 'true' }
What the spec declares
Checked across all 29 published specs, 2.0 through 2.28:
PATCH /file-systems declares delete_link_on_eradication (in: query) in every version.
DELETE /file-systems declares it in no version. Its full query surface is ids, names
(2.0-2.13), +X-Request-ID (2.14-2.16), +context_names (2.17-2.28). The operation has no
requestBody, so there is no body surface hiding it either.
A whole-corpus scan finds exactly one lowercase occurrence of the string per spec file, and it is
accounted for by that PATCH parameter, so no declaration of any other kind exists.
A methodology note worth recording, because it inverts the answer. From 2.17 onward every
PATCH parameter on this endpoint is a {"$ref": "#/components/parameters/..."} rather than an
inline object. A scan that reads only inline .name values reports the key as absent from 2.17
onward — a false absence covering the twelve most recent specs, and precisely backwards from the
truth, which is that it has been declared continuously since 2.0. Any re-check of this issue must
resolve $refs.
Why neither gate catches it
The dead-key report never evaluates the key. The parameter lands in two operations that
disagree, so the resolver correctly declines to name one, and Method/Endpoint come back
$null. Build-PfbDeadKeyReport.ps1 then routes it to the endpoint/method ambiguous skip
bucket and the spec is never consulted. The abstention is deliberate and pinned by
Tests/PfbCmdletParamTools.Tests.ps1:2342; it is correct, and the blindness is a side effect of
it rather than a defect in it.
That bucket holds 14 parameters. This is the only one where the blindness costs anything — all
26 other (parameter, operation) pairs carry keys that are declared on every operation they reach:
| parameter(s) |
key(s) |
operations reached |
declared |
Get-PfbNode -Filter/-Id/-Limit/-Name/-Sort |
filter, ids, limit, names, sort |
GET /nodes, GET /blades |
both |
Remove-PfbBucket -Id/-Name |
ids, names |
PATCH /buckets, DELETE /buckets |
both |
Remove-PfbFileSystem -Id/-Name |
ids, names |
PATCH/DELETE /file-systems |
both |
Remove-PfbFileSystemSnapshot -Id/-Name |
ids, names |
PATCH/DELETE /file-system-snapshots |
both |
Remove-PfbRealm -Id/-Name |
ids, names |
PATCH/DELETE /realms |
both |
Remove-PfbFileSystem -DeleteLinkOnEradication |
delete_link_on_eradication |
PATCH/DELETE /file-systems |
PATCH only |
The runtime capability check cannot catch it either, by design. Assert-PfbApiCapability is a
version floor, not a membership test: it looks the parameter up in the capability map and, on a
miss, silently does nothing — its own help says a capability check must never be the reason a call
that would otherwise succeed gets blocked. Data/PfbCapabilityMap.json lists only
ids, names, X-Request-ID, context_names for DELETE /file-systems, so this key is a miss, and a
miss is a no-op. ConvertTo-PfbQueryString filters nothing but nulls and empties. Two gates
exist; neither is the kind of gate that could see this.
A possible fix, with its discriminator
The bucket's name is a disjunction, and only half of it applies here: the endpoint is agreed
(file-systems on every landing), only the method is ambiguous. That fact is known and then
discarded twice — Get-PfbRequestRoleForVariable tests distinctness on the composite
Method|Endpoint and nulls both, and Resolve-PfbWireLandingArbitration re-imposes it.
If the resolver retained an agreed endpoint, the gate could evaluate an
endpoint-agreed/method-ambiguous key against the intersection of declared query keys across
the methods the variable reaches. Measured against today's Public/, that flags this key and
nothing else — one new finding, zero new false positives:
| endpoint |
intersection of declared query keys |
contains the key |
nodes ∩ blades |
the full 8-key set |
n/a |
buckets |
context_names, ids, names |
n/a |
file-system-snapshots |
context_names, ids, names |
n/a |
realms |
ids, names |
n/a |
file-systems |
context_names, ids, names |
no — flags |
Intersection versus union is the whole fix: union semantics keep the key hidden, because the
PATCH declaration would cover the DELETE send.
For reference, had the gate evaluated this key it would classify it WRONG-VERB rather than
UNDECLARED — the key is declared on the same endpoint's PATCH query surface — and mark it
DESTRUCTIVE on the DELETE. WRONG-VERB is the accurate label and the more actionable one.
The help text says the opposite of the spec
Remove-PfbFileSystem.ps1:15-18:
When eradicating a file system that participates in a replica link, ALSO delete the link as part
of the eradication. Without this, the FB refuses the eradicate with "Please specify
delete-link-on-eradication". Only meaningful with -Eradicate.
Per the spec the reverse is true: PATCH is the only operation that declares the key, so it is
meaningful only without -Eradicate. The documented example at line 26,
Remove-PfbFileSystem -Name "fs1" -Eradicate -DeleteLinkOnEradication, is the one invocation
where the flag lands on the operation that does not declare it.
The file also contradicts itself. The inline comment at lines 61-63 asserts the opposite of the
help:
# FB requires `delete_link_on_eradication=true` here too when the file system
# has a replica link - the destroy is the point at which FB warns the link
# will be removed at eradication time.
Whichever way the array actually behaves, the help block and the comment cannot both be right.
The user-facing trap that follows, if the spec is right: destroy without the flag, then follow the
documented example to eradicate. There is then no documented route to eradicate a linked file
system. The workaround — re-running the destroy form against an already-destroyed file system,
Remove-PfbFileSystem -Name fs1 -DeleteLinkOnEradication with no -Eradicate, to re-PATCH the
flag — is undocumented and counterintuitive.
Impact is open
Three readings are consistent with everything above, and they are not distinguishable without a
live array:
- The array ignores unknown query parameters. The DELETE send is a silent no-op, and the
defect is confined to the help text and the gate blindness.
- The array rejects unknown query parameters. Passing the flag turns a working eradicate into
a hard failure. Note this would fire only on the flagged path, so a plain -Eradicate smoke
test still passes and would not surface it.
- The array accepts it as undeclared-but-working internal surface. The code works and the
spec has the gap, which makes this a spec-gap report rather than a dead-key one.
The quoted error string in the help ("Please specify delete-link-on-eradication") reads like a
real observation, but nothing establishes it was seen on the DELETE rather than the PATCH: there
is no live-test record for this cmdlet in the repo, no design note, and the parameter arrived
inside a single bulk v2.0.3 commit (0a76ab3) with a one-line CHANGELOG entry
(CHANGELOG.md:308).
A live check against a FlashBlade is what settles which reading holds, and no fix should assert
an impact before that runs. Note also that even reading 3 would not license the module to depend
on the key: an undeclared-but-working parameter is internal surface, not GA, and the published
spec governs.
Verification
Every figure here was measured, not recalled:
- Skip-bucket counts reproduced from
Get-PfbCmdletParameterInventory and the generator's own
branch ladder, then reconciled against committed Reports/PfbDeadKeyReport.json — 31 / 28 / 6 /
309 / 14 / 0 with 1780 evaluated over 2168 inventoried, matching on every bucket and summing
correctly.
Method and Endpoint type-checked as genuinely $null, not empty strings.
- Spec paths matched against the exact literal
/api/<version>/file-systems, so the 25 sibling
*file-systems* paths are excluded, and spec files ordered numerically so 2.9 does not sort
after 2.28.
- Declared-key sets computed twice — once through the production
Get-PfbDeclaredQueryKey, once
through an independent $ref-resolving reader — agreeing on all 10 operations.
Summary
Remove-PfbFileSystemsends the query keydelete_link_on_eradicationon two differentoperations. The published OpenAPI declares it on only one of them, and the module's two safety
gates are each structurally incapable of noticing. Separately, the cmdlet's help text states the
opposite of what the spec says, and contradicts an inline comment in its own body.
Nothing here is a measured wire failure. What the impact actually is depends on how the array
treats an undeclared query parameter, which is not established — see Impact is open below.
This issue is filed for the parts that are provable statically.
Cross-reference, added 2026-08-28. The same soft-delete branch has a second, unrelated
defect -- the protocol-disable step at line 74 is wrapped in a bare
catch { }, so its failure isdiscarded and the destroy at line 77 runs anyway. That is already tracked in #117 (see its
"Plus one, related but a different failure mode" section); it is noted here only because a reviewer
touching this branch will see both, and both want the same live FlashBlade session. Do not fix it
under this issue -- #117 owns it.
What the cmdlet sends
Public/FileSystem/Remove-PfbFileSystem.ps1:PATCH /file-systemsPATCH /file-systems$destroyQueryDELETE /file-systems$queryParamsWhat the spec declares
Checked across all 29 published specs, 2.0 through 2.28:
PATCH /file-systemsdeclaresdelete_link_on_eradication(in: query) in every version.DELETE /file-systemsdeclares it in no version. Its full query surface isids,names(2.0-2.13),
+X-Request-ID(2.14-2.16),+context_names(2.17-2.28). The operation has norequestBody, so there is no body surface hiding it either.A whole-corpus scan finds exactly one lowercase occurrence of the string per spec file, and it is
accounted for by that PATCH parameter, so no declaration of any other kind exists.
A methodology note worth recording, because it inverts the answer. From 2.17 onward every
PATCH parameter on this endpoint is a
{"$ref": "#/components/parameters/..."}rather than aninline object. A scan that reads only inline
.namevalues reports the key as absent from 2.17onward — a false absence covering the twelve most recent specs, and precisely backwards from the
truth, which is that it has been declared continuously since 2.0. Any re-check of this issue must
resolve
$refs.Why neither gate catches it
The dead-key report never evaluates the key. The parameter lands in two operations that
disagree, so the resolver correctly declines to name one, and
Method/Endpointcome back$null.Build-PfbDeadKeyReport.ps1then routes it to theendpoint/method ambiguousskipbucket and the spec is never consulted. The abstention is deliberate and pinned by
Tests/PfbCmdletParamTools.Tests.ps1:2342; it is correct, and the blindness is a side effect ofit rather than a defect in it.
That bucket holds 14 parameters. This is the only one where the blindness costs anything — all
26 other (parameter, operation) pairs carry keys that are declared on every operation they reach:
Get-PfbNode -Filter/-Id/-Limit/-Name/-Sortfilter, ids, limit, names, sortGET /nodes,GET /bladesRemove-PfbBucket -Id/-Nameids, namesPATCH /buckets,DELETE /bucketsRemove-PfbFileSystem -Id/-Nameids, namesPATCH/DELETE /file-systemsRemove-PfbFileSystemSnapshot -Id/-Nameids, namesPATCH/DELETE /file-system-snapshotsRemove-PfbRealm -Id/-Nameids, namesPATCH/DELETE /realmsRemove-PfbFileSystem -DeleteLinkOnEradicationdelete_link_on_eradicationPATCH/DELETE /file-systemsThe runtime capability check cannot catch it either, by design.
Assert-PfbApiCapabilityis aversion floor, not a membership test: it looks the parameter up in the capability map and, on a
miss, silently does nothing — its own help says a capability check must never be the reason a call
that would otherwise succeed gets blocked.
Data/PfbCapabilityMap.jsonlists onlyids, names, X-Request-ID, context_namesforDELETE /file-systems, so this key is a miss, and amiss is a no-op.
ConvertTo-PfbQueryStringfilters nothing but nulls and empties. Two gatesexist; neither is the kind of gate that could see this.
A possible fix, with its discriminator
The bucket's name is a disjunction, and only half of it applies here: the endpoint is agreed
(
file-systemson every landing), only the method is ambiguous. That fact is known and thendiscarded twice —
Get-PfbRequestRoleForVariabletests distinctness on the compositeMethod|Endpointand nulls both, andResolve-PfbWireLandingArbitrationre-imposes it.If the resolver retained an agreed endpoint, the gate could evaluate an
endpoint-agreed/method-ambiguous key against the intersection of declared query keys across
the methods the variable reaches. Measured against today's
Public/, that flags this key andnothing else — one new finding, zero new false positives:
nodes∩bladesbucketscontext_names, ids, namesfile-system-snapshotscontext_names, ids, namesrealmsids, namesfile-systemscontext_names, ids, namesIntersection versus union is the whole fix: union semantics keep the key hidden, because the
PATCH declaration would cover the DELETE send.
For reference, had the gate evaluated this key it would classify it
WRONG-VERBrather thanUNDECLARED— the key is declared on the same endpoint's PATCH query surface — and mark itDESTRUCTIVEon the DELETE.WRONG-VERBis the accurate label and the more actionable one.The help text says the opposite of the spec
Remove-PfbFileSystem.ps1:15-18:Per the spec the reverse is true:
PATCHis the only operation that declares the key, so it ismeaningful only without
-Eradicate. The documented example at line 26,Remove-PfbFileSystem -Name "fs1" -Eradicate -DeleteLinkOnEradication, is the one invocationwhere the flag lands on the operation that does not declare it.
The file also contradicts itself. The inline comment at lines 61-63 asserts the opposite of the
help:
Whichever way the array actually behaves, the help block and the comment cannot both be right.
The user-facing trap that follows, if the spec is right: destroy without the flag, then follow the
documented example to eradicate. There is then no documented route to eradicate a linked file
system. The workaround — re-running the destroy form against an already-destroyed file system,
Remove-PfbFileSystem -Name fs1 -DeleteLinkOnEradicationwith no-Eradicate, to re-PATCH theflag — is undocumented and counterintuitive.
Impact is open
Three readings are consistent with everything above, and they are not distinguishable without a
live array:
defect is confined to the help text and the gate blindness.
a hard failure. Note this would fire only on the flagged path, so a plain
-Eradicatesmoketest still passes and would not surface it.
spec has the gap, which makes this a spec-gap report rather than a dead-key one.
The quoted error string in the help (
"Please specify delete-link-on-eradication") reads like areal observation, but nothing establishes it was seen on the DELETE rather than the PATCH: there
is no live-test record for this cmdlet in the repo, no design note, and the parameter arrived
inside a single bulk
v2.0.3commit (0a76ab3) with a one-line CHANGELOG entry(
CHANGELOG.md:308).A live check against a FlashBlade is what settles which reading holds, and no fix should assert
an impact before that runs. Note also that even reading 3 would not license the module to depend
on the key: an undeclared-but-working parameter is internal surface, not GA, and the published
spec governs.
Verification
Every figure here was measured, not recalled:
Get-PfbCmdletParameterInventoryand the generator's ownbranch ladder, then reconciled against committed
Reports/PfbDeadKeyReport.json— 31 / 28 / 6 /309 / 14 / 0 with 1780 evaluated over 2168 inventoried, matching on every bucket and summing
correctly.
MethodandEndpointtype-checked as genuinely$null, not empty strings./api/<version>/file-systems, so the 25 sibling*file-systems*paths are excluded, and spec files ordered numerically so2.9does not sortafter
2.28.Get-PfbDeclaredQueryKey, oncethrough an independent
$ref-resolving reader — agreeing on all 10 operations.