Skip to content

Remove-PfbFileSystem sends delete_link_on_eradication on DELETE /file-systems, where no spec version declares it - and both gates are blind to it #154

Description

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
nodesblades 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:

  1. 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.
  2. 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.
  3. 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions