Repository navigation
Capability map cannot record array-body cardinality constraints (minItems/maxItems/uniqueItems) — needs schemaVersion 2 #83
Description
Activity
juemerson-at-purestorage commented
on Aug 2, 2026 CollaboratorAuthorMore actionsjuemerson-at-purestorage commented
on Aug 5, 2026 CollaboratorAuthorMore actionsFiled #95, which asks the same question this issue asks — proposing they be decided together
rather than separately.#95 is about
Assert-PfbApiCapability's-is [System.Collections.IDictionary]guard, which
skips the body-field loop entirely for array bodies. #84 PR 1 fillsbodyPropertiesfor the
four array-bodied endpoints (23 fields), which removes that guard's stated premise — but not
the guard. So after PR 1 the map records per-element fields and nothing reads them at request
time.Lined up against this issue's option 3:
#95 #83 option 3 Question gate per-element field versions? gate per-element cardinality? File Private/Assert-PfbApiCapability.ps1Private/Assert-PfbApiCapability.ps1Cmdlet affected today Set-PfbWorkloadTagSet-PfbWorkloadTagCallers benefiting today none until #44 none until #44 Risk refuses calls that succeed today refuses calls that succeed today Same function, same cmdlet, same class of behaviour change, same absence of a caller that
benefits yet. Deciding them apart risks an incoherent gate: element field versions enforced
but element cardinality not (or vice versa), with no principled reason for the asymmetry, and
whichever lands second has to explain the mismatch.The inclination already recorded here — "(1) in the schema PR and (3) as a follow-up once
there is a caller for more" — transfers to #95 unchanged, and is a defensible answer for
both. Record in the schema PR; enforce when #44 provides callers.One constraint that applies to both, noted in #95 in more detail: whichever PR enforces has to
argue againstAssert-PfbApiCapability's founding principle that "a capability check must
never be the reason a call that would otherwise succeed gets blocked" — which the Fusion
Phase 0 design has recently re-litigated and re-affirmed, shipping the connect-time staleness
warning as the honest half of staying permissive. The case that evidence-backed gating is
consistent with that principle (rather than a reversal of it) is worth making explicitly
rather than assuming.Suggested shape, which also shortens #44's dependency chain from three predecessors to two:
#84 PR 1 → #82 map + reporting → unblocks #44's design work PR 2 → #83 + #95: what the local → unblocks #44's runtime gate gate enforces for array bodies, and when #44 → batch cmdlets- added 3 commits that reference this issue
on Aug 5, 2026 juemerson-at-purestorage commented
on Aug 5, 2026 CollaboratorAuthorMore actionsContext for whoever picks this up — this is now the last outstanding item in #84's group.
#71, #82 andcontextScopeall landed via #96/#97 and reachmainin #98.The base you inherit changed
schemaVersionis already 2, not 1. Phase 0 took the bump forcontextScope, so this work
increments from 2 → 3. Cheap either way: nothing inPrivate/orPublic/reads the field, and
the five generators version independently.- Every endpoint now carries
contextScope({scope, provenance}) — 632 of 632. Its
position in the per-endpoint object matters only in that key order is not stable across
unrelated changes; see below. - The capability map on
mainafter Integration: Fusion Phase 0 prerequisites + capability-map schema-walk fixes (#96, #97) #98 will be SHA-2566B9F0865…F156, regenerated from the 29
cached specs and verified byte-identical to generator output.
Two traps that cost time on #71/#82
Expect order-only diffs, and don't assume they are noise. #97 produced two records whose keys
reordered with every value identical —PATCH /ssh-certificate-authority-policies(within
bodyProperties) andPATCH /file-systems(top level,contextScopeand
readOnlyBodyPropertiesswapping). Both were deterministic across three regenerations. Diagnose
them rather than waving them through, but two is the normal count here.No CI gate compares the committed artifact against generator output.
cross-platform-tests.ymlnever fetches the gitignoredtools/specs/, so roughly 27
real-artifact assertions skip silently and read exactly like passes (#63). Verify by hand:
regenerate and compare SHAs, and confirm the real-artifactDescribeblocks actually executed
— a scoped run reporting0 skippeddoes not prove they ran.The requirement this issue does not carry
From #84, and it applies here directly:
Tests/Build-PfbApiDriftReport.Tests.ps1asserts that
every query parameter and body property the committed map lists, for an endpoint an existing
cmdlet calls, lands in exactly one ofmissingQueryParameters/missingBodyProperties/
readOnlyFields, or the phantom-exclusion set.Adding
bodyConstraintsmust account for that in the same commit or the invariant goes red. #82
added 23 fields and hit precisely this.Useful precedent
If
bodyConstraintsneeds schema-walking changes, note that #82's fix deliberately did not
touchAdd-PfbSchemaPropertyNodes: that walker is shared withGet-PfbSpecResponseShapes, whose
contract requires an envelope's properties and itsitems[]element's properties stay separate
levels. Theitemshop went at theGet-PfbSpecCapabilitiescall site instead, and
Data/PfbResponseShapeMap.jsonstaying byte-identical (423DE668…FEF6) was the guard proving no
leak. Reuse that guard — if that SHA moves for abodyConstraintschange, something leaked.juemerson-at-purestorage commented
on Aug 20, 2026 CollaboratorAuthorMore actionsCarrying over what #84 held, ahead of closing it
#84 tracked four coordinated capability-map changes. Three have landed —
contextScope(#96),
#71 and #82 (both #97, tomainvia #98) — leaving this issue as the only one outstanding, so
#84 is being closed. Some of what it carried lives nowhere else. Transplanting it here rather
than leaving it in a closed tracker.The requirement no child issue carries
Tests/Build-PfbApiDriftReport.Tests.ps1:707asserts a "nothing vanishes" invariant: every
query parameter and body property the committed map lists, for an endpoint an existing cmdlet
calls, must land in exactly one of the drift report'smissingQueryParameters/
missingBodyProperties/readOnlyFields, or the phantom-exclusion set.Any change that adds keys to the map has to account for them there in the same commit or that
test goes red. It bit #82 exactly as #84 predicted — its 23 recovered fields had to be
accounted for in the drift report in the same change.bodyConstraintsis a body-level key rather than a field, so this may well not apply. Check it
deliberately; do not discover it from a red build.Where
bodyConstraintsactually slots in, as ofmaintodayThe four array-bodied endpoints, post-#82 and post-
contextScope:Endpoint minVersionbodyPropertiesCurrent key set PUT /workloads/tags/batch2.23 5 minVersion,parameters,bodyProperties,contextScopePOST /nodes/batch2.18 12 + readOnlyBodyPropertiesPOST /resource-accesses/batch2.19 2 as /workloads/tags/batchPOST /fleets/members/batch2.27 4 as /workloads/tags/batch23 fields across the four, matching #82's prediction. Only
PUT /workloads/tags/batchdeclares
constraints, so the expected diff is still the single endpoint described above.schemaVersionis already 2This issue's body says it "makes this the first of the pending capability-map changes to require
schemaVersion 2". That is no longer true —contextScopetook 2 when Fusion Phase 0 landed,
andData/PfbCapabilityMap.jsononmaincarries"schemaVersion": 2today. This change
increments to 3.The number remains a per-artifact maintainer label. Nothing in
Private/orPublic/reads it;
five generators each write their own independent value.Two regeneration gotchas, confirmed in practice on #97
- Expect two order-only records, not one. Alongside the predicted
PATCH /ssh-certificate-authority-policies,PATCH /file-systemsalso moves — top-level key
order only,contextScopeandreadOnlyBodyPropertiesswapping position, all values
identical. Deterministic across three regenerations. Diagnose an unexpected order-only diff
rather than assuming it is noise, but know that two are normal. - Do not grep for
{}to find array-bodied endpoints. Pre-Capability map records no body fields for array-bodied endpoints (schema walk never descends throughitems) #82 they each held one
bodyPropertiesentry keyed by the empty string, never an empty object. Post-Capability map records no body fields for array-bodied endpoints (schema walk never descends throughitems) #82 they hold
the real fields above.
Three things #84 recorded that have since been inverted
Stated because #84's body and comments read the other way, and the difference changes how this
change must be shipped.- A regeneration gate now exists. Tracking: coordinated changes to the capability-map generator and schema (#71, #82, #83, contextScope) #84's headline assumption-correction was that there is no
byte-identical regeneration gate onData/PfbCapabilityMap.json, and its sequencing plan
depended on that — it explicitly allowed a PR to "open carrying a not-yet-regenerated
artifact". PR Gate the derived Data/ and Reports/ artifacts on pull requests #130 addedscripts/Assert-PfbDerivedArtifacts.ps1and
verify-derived-artifacts.yml, which regenerates all eleven derived artifacts on
pull_requestand diffs them against the committed copies, pinned to the committed map's own
generatedFromspec list so spec-publication drift cannot red an unrelated PR. So this
change must regenerate in-PR. A stale committed artifact now fails the build rather than
being caught by eye. - The real-artifact assertions no longer skip silently in CI. Tracking: coordinated changes to the capability-map generator and schema (#71, #82, #83, contextScope) #84 warned that the invariant
test is guarded on the gitignoredtools/specs/and "only bites in CI". Theprepare-specs
job added for CI silently skips ~23% of the test suite, including the absolute-path regression guards #63 publishes the spec cache as artifactpfb-specsto both test jobs, so those
blocks execute.Tests/coverage-baseline.psd1names them underRequiredDescribes, so their
absence is now a red build too. A green local run without a spec cache is still not evidence
— that part of the warning stands. - Do not diff against
origin/automated/update-api-capability-map. Two Tracking: coordinated changes to the capability-map generator and schema (#71, #82, #83, contextScope) #84 comments instruct
exactly that, on the grounds that the auto-PR step was failing on a repo permission and the
branch held regenerationsmainnever received. The workflow has been succeeding since at
least 2026-08-14, and that branch is now 1 ahead / 186 behindmainwith a single
Reports/-only commit dated 2026-08-04. It is superseded; regenerate frommain.
The remaining decision is shared with #95
This issue's three consumption options — record only, warn, or throw — are the same question #95
asks about the same function, for the same cmdlet, with the same blast radius. #95's body already
carries the side-by-side table. Decide the two together; see the comment there.- Expect two order-only records, not one. Alongside the predicted
- addedstatus:needs-designUnderstood, but the approach is not settled. Needs a design doc.Understood, but the approach is not settled. Needs a design doc.priority:P1Wrong on the wire, or blocks other queued work.Wrong on the wire, or blocks other queued work.size:MA day. Several files, one coherent change.A day. Several files, one coherent change.area:capability-mapData/PfbCapabilityMap.json, its generator, and Assert-PfbApiCapability.Data/PfbCapabilityMap.json, its generator, and Assert-PfbApiCapability.source:driftOpened from a drift-report finding, by tooling.Opened from a drift-report finding, by tooling.
on Sep 21, 2026
Summary
The capability map has nowhere to record an array request body's cardinality constraints
(
minItems,maxItems,uniqueItems), so a cmdlet cannot validate batch size locally andonly discovers the limit from the array.
This is a schema gap rather than a walker bug, and it is the smaller, forward-looking half
of the array-body problem. The other half — element field names being dropped entirely —
is a walker fix needing no schema change and is filed separately.
Current state
Data/PfbCapabilityMap.jsonisschemaVersion 1. Per-endpoint keys areminVersion,parameters,bodyProperties,parameterComponentOverridesandreadOnlyBodyProperties. None of them can hold a constraint on the body as a wholerather than on a named field.
Of the four array-bodied operations in fb2.28, one declares constraints today:
minItemsmaxItemsuniqueItemsPUT /workloads/tags/batchPOST /nodes/batchPOST /resource-accesses/batchPOST /fleets/members/batchSo the concrete impact today is one endpoint:
Set-PfbWorkloadTagwill accept 50 tags,serialise them, and let the array reject the request. Filing this anyway because the
constraint is declared in the spec, is trivially machine-readable, and the batch-cmdlet
work in #44 will add callers for the other three — at which point any limit those endpoints
later acquire has the same problem.
Why it needs a schema change
bodyPropertiesis a field-name →introducedVersionmap. Cardinality is a property ofthe array, not of any field in it, so there is no honest place to put it without a new key.
That makes this the first of the pending capability-map changes to require
schemaVersion 2.Worth knowing before treating that as expensive: nothing in
Private/orPublic/readsschemaVersion. The runtime loader does not validate it. Four generators(
Build-PfbCapabilityMap,Build-PfbApiDriftReport,Build-PfbFieldCmdletMap,Build-PfbValueEnumMap) each write their own independent1, so the version isper-artifact and today purely informational. The bump costs a few fixtures in
Tests/Get-PfbCapabilityMap.Tests.ps1, not a migration.The Fusion
contextScopefield (see #25 and the rev 3 design in #72) is the other pendingchange that needs
schemaVersion 2. Landing both in one bump is the reason for thetracking issue.
Suggested shape
An additive, optional per-endpoint key, absent when the body is not an array:
Optional-and-absent keeps the diff to the single endpoint that declares constraints, and
avoids asserting
nulllimits on endpoints whose spec is simply silent — an absentmaxItemsmeans "not declared", not "unlimited", and the map should not blur those.Consumption is a separate decision from recording. Options, in increasing order of
commitment:
Assert-PfbApiCapabilitywarns on violation.Assert-PfbApiCapabilitythrows, matching how it already treats version gating.(3) is the most useful and the most likely to surprise: a hard local throw changes
Set-PfbWorkloadTag's behaviour for anyone currently batching more than 30 tags andrelying on the server error. Worth a deliberate call rather than folding in silently. My
inclination is (1) in the schema PR and (3) as a follow-up once there is a caller for more
than one endpoint, but this is the open question on this issue.
Constraint on any implementation
Tests/Build-PfbApiDriftReport.Tests.ps1asserts the "nothing vanishes" invariant over thecommitted map — every field it lists for a called endpoint must land in exactly one of the
drift report's buckets or the phantom-exclusion set. A new endpoint-level key is not a
field and should fall outside that accounting, but the invariant enumerates map contents,
so confirm it does not sweep the new key up rather than assuming it won't.
Out of scope
items, so all fourendpoints record
bodyProperties: {}. Separate issue; walker fix, no schema change.maxLength,pattern, numeric bounds) on ordinary objectbodies. Same category of "the spec declares a validatable constraint the map drops", and
a much larger surface. Deliberately not folded in — if it is worth doing, it deserves its
own design rather than riding along on four batch endpoints.
Reports/PfbValueEnumMap.jsonandBuild-PfbValueEnumMap.ps1; not a capability-map concern.