Repository navigation
Capability map records no body fields for array-bodied endpoints (schema walk never descends through items) #82
Description
Activity
juemerson-at-purestorage commented
on Aug 2, 2026 CollaboratorAuthorMore actions- added 6 commits that reference this issue
on Aug 5, 2026 juemerson-at-purestorage commented
on Aug 5, 2026 CollaboratorAuthorMore actionsFixed at the map-and-reporting scope in #97, reaching
mainvia #98 (which carries the
closing keyword — #97 targeted an integration branch, and closing keywords only fire on merges
into the default branch).Recovered, exactly as this issue predicted: 23 body properties —
POST /nodes/batch+12,
POST /resource-accesses/batch+2,PUT /workloads/tags/batch+5,
POST /fleets/members/batch+4. AdditionallyreadOnlyBodyPropertiesonPOST /nodes/batch
resolves from empty to 8 fields (capacity,chassis_serial_number,data_addresses,
details,id,raw_capacity,status,unique).One correction to this issue's framing
The four endpoints did not record
"bodyProperties": {}. Each recorded exactly one entry
keyed by the empty string — the nameless artifact of a walk that reached the array node and
could not descend it. Harmless to the diagnosis, but worth correcting because an empty map and
a map with a blank-named key fail differently, and anyone grepping for{}would have found
nothing.Why the fix is not in the shared walker
Worth recording, because the obvious fix is wrong. Teaching
Add-PfbSchemaPropertyNodesto
descenditemswould have silently corruptedData/PfbResponseShapeMap.json: that walker is
shared withGet-PfbSpecResponseShapes, whose entire contract is that an envelope's properties
and itsitems[]element's properties stay two deliberately-separate levels. Collapsing them
would have made the response-shape map's cross-version removal detection compare incomparable
sets, with nothing to warn.So the
itemshop happens at theGet-PfbSpecCapabilitiescall site instead, matching the
precedentGet-PfbSpecResponseShapesalready sets. The guard that proves no leak:
Data/PfbResponseShapeMap.jsonis byte-identical throughout —423DE668…FEF6before, after,
and after the integration merge.This also needed #71's
MaxDepthfix to work at all: descending throughitemsconsumes a
depth level, so at the old default of 8 the recovered fields would have been truncated again.
Neither fix does anything alone, which is why they landed together.The runtime half is #95, and that split is deliberate
This issue's stated scope included the runtime capability gate, and that half is not fixed
here.Assert-PfbApiCapability's body-field loop is guarded on
-is [System.Collections.IDictionary], and an array body arrives as[hashtable[]], so the
loop is skipped beforebodyPropertiesis ever consulted. The 23 fields are present in the map
and inert at runtime today.Confirmed live rather than reasoned about —
Set-PfbWorkloadTagagainst FB-A (REST 2.26)
returned:FlashBlade API error (HTTP 400): Workload does not exist.A resource rejection, not a body-field one. The request reached the wire and was never gated
client-side, which is direct evidence the type guard short-circuits ahead ofbodyProperties.Closing this at the map/reporting scope and letting #95 own the runtime half: recovering
the fields is a generator fix, whereas making the gate act on them means changing a type guard
in a live path that every write cmdlet traverses — different change, different risk, different
review. #95 now has real data to test against, which it did not before.- added a commit that references this issue
on Aug 13, 2026
Summary
The capability map's schema walk never descends through an array schema's
items, soevery array-bodied endpoint records
"bodyProperties": {}. Four endpoints are affectedand 23 body fields are invisible to both the runtime capability gate and the drift report.
Unlike #71 (which records fields with a wrong
introducedVersion), these fields arerecorded not at all, and the defect reproduces on the current spec rather than
self-healing at 2.17.
Affected endpoints
All four array-bodied operations in fb2.28, all with
bodyProperties: {}:minVersionPOST /nodes/batchNodePostPOST /resource-accesses/batchResourceAccessPostPUT /workloads/tags/batchTagBatchPOST /fleets/members/batchFleetMemberBatchPostThe fields exist in the spec, one hop below where the walk stops:
Root cause
Add-PfbSchemaPropertyNodesintools/lib/PfbSpecTools.ps1— the recursive helper behindGet-PfbSchemaPropertyNames/Get-PfbSchemaPropertyDetails— has branches for$ref(via
Resolve-PfbRef),allOf,propertiesandrequired. It has no branch foritems, and none foroneOf/anyOf.The request body for
PUT /workloads/tags/batchin fb2.28:{ "maxItems": 30, "minItems": 1, "uniqueItems": true, "type": "array", "items": { "$ref": "#/components/schemas/TagBatch" } }Resolve-PfbRefreturns that array node unchanged. It has nopropertieskey and noallOf, so the accumulator adds nothing and the walk terminates.NodePostisadditionally
allOf [Node, {node_key}], so it needs the existingallOfhandling to runafter the
itemshop — the two compose.Why it is easy to miss
"bodyProperties": {}is indistinguishable from "this endpoint legitimately has no bodyfields." Nothing warns, nothing logs, and no test fails. There is no error state to
notice — only an absence that reads as a valid answer.
Runtime impact
Private/Assert-PfbApiCapability.ps1readsbodyPropertiesto version-gate body fields.For these four endpoints the map is empty, so no field-level gate can fire — a caller
supplying a field that requires a newer REST version gets an opaque wire error instead of
the local, actionable message every other endpoint produces.
This is why PR #81, which taught
Assert-PfbApiCapabilityto accept array bodies at all,deliberately guards the field loop on
-is [System.Collections.IDictionary]rather thanvalidating the union of element field names: against an empty map such a check could never
fire, and a gate that cannot fire is worse than none, because it looks like coverage.
Endpoint
minVersionand query-parameter gating do run for these endpoints today; onlyfield-level gating is missing.
Reporting impact
The drift report's addable-body-parameter axis reads
bodyProperties, so these fourendpoints contribute zero addable fields and cannot appear as gaps.
That lands directly on #44 (batch-operation cmdlets for nodes, resource-accesses,
workload-tags and fleet-members) — the drift report cannot inform the design of precisely
the cmdlets #44 proposes.
Set-PfbWorkloadTagalready ships with a free-form[hashtable[]]$Tags, and nothing in the tooling can tell a user thatnamespace,copyableorresourceare the valid keys.readOnlyBodyPropertiesisnullfor all four.NodePostcarries obviously read-onlymembers (
id,status,capacity,raw_capacity), so that field should populate tooonce the walk descends — worth asserting explicitly rather than assuming.
Suggested fix
Add an
itemsbranch toAdd-PfbSchemaPropertyNodesthat resolves the element schema andrecurses into it with the existing accumulator, so
allOfcomposition inside the elementcontinues to work. Then rebuild
Data/PfbCapabilityMap.jsonand diff.Expected diff: four endpoints gain populated
bodyProperties(23 fields total, each withan
introducedVersionderived per the normal first-sight rule), plus whateverreadOnlyBodyPropertiesresolves to. Nothing else should move.Two constraints on the fix:
Depth budget. Descending through
itemsconsumes a level, and Capability map records 5 body fields with introducedVersion one release too late (MaxDepth=8 truncation in fb2.12-2.16) #71 is already aboutthe walk running out of depth on the fb2.12–2.16
allOfchains. Capability map records 5 body fields with introducedVersion one release too late (MaxDepth=8 truncation in fb2.12-2.16) #71's suggested fix(an explicit
-MaxDepth 32at theGet-PfbSpecCapabilitiescall sites) supplies theheadroom this needs. See the sequencing note in the tracking issue.
The "nothing vanishes" invariant.
Tests/Build-PfbApiDriftReport.Tests.ps1assertsthat every field the map lists for an endpoint an existing cmdlet calls lands in
exactly one of the report's addable / read-only buckets or the phantom-exclusion set.
Newly-populated fields must be accounted for there in the same change, or that test
goes red. No child issue on the map carries this requirement, so it is easy to trip.
A regression test wants a synthetic fixture whose request body is an array of an
allOf-composed element schema, asserting the element's fields are found — a fixture thatfails today.
Relationship to #71
Same function, different keyword, and neither fix subsumes the other:
allOf— handled, walk stops earlyitems— no branch existsMaxDepthdefaults to 8introducedVersionone release late on 5 fieldsRaising
MaxDepthdoes nothing for this; addingitemsdoes nothing for #71. They share acall site, a data file and a verification burden, so they are worth landing together — see
the tracking issue.
Out of scope
The array-level constraints on the same schemas (
minItems,maxItems,uniqueItems)are a separate gap requiring a new map field rather than a walker fix. Filed separately.