Skip to content

Capability map cannot record array-body cardinality constraints (minItems/maxItems/uniqueItems) — needs schemaVersion 2 #83

Description

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 and
only 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.json is schemaVersion 1. Per-endpoint keys are minVersion,
parameters, bodyProperties, parameterComponentOverrides and
readOnlyBodyProperties. None of them can hold a constraint on the body as a whole
rather than on a named field.

Of the four array-bodied operations in fb2.28, one declares constraints today:

Endpoint minItems maxItems uniqueItems
PUT /workloads/tags/batch 1 30 true
POST /nodes/batch — — —
POST /resource-accesses/batch — — —
POST /fleets/members/batch — — —

So the concrete impact today is one endpoint: Set-PfbWorkloadTag will 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

bodyProperties is a field-name → introducedVersion map. Cardinality is a property of
the 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/ or Public/ reads
schemaVersion.
The runtime loader does not validate it. Four generators
(Build-PfbCapabilityMap, Build-PfbApiDriftReport, Build-PfbFieldCmdletMap,
Build-PfbValueEnumMap) each write their own independent 1, so the version is
per-artifact and today purely informational. The bump costs a few fixtures in
Tests/Get-PfbCapabilityMap.Tests.ps1, not a migration.

The Fusion contextScope field (see #25 and the rev 3 design in #72) is the other pending
change that needs schemaVersion 2. Landing both in one bump is the reason for the
tracking issue.

Suggested shape

An additive, optional per-endpoint key, absent when the body is not an array:

"PUT /workloads/tags/batch": {
  "minVersion": "2.23",
  "bodyConstraints": { "minItems": 1, "maxItems": 30, "uniqueItems": true },
  ...
}

Optional-and-absent keeps the diff to the single endpoint that declares constraints, and
avoids asserting null limits on endpoints whose spec is simply silent — an absent
maxItems means "not declared", not "unlimited", and the map should not blur those.

Consumption is a separate decision from recording. Options, in increasing order of
commitment:

  1. Record only; no runtime consumer yet. Cheapest, unblocks the schema bump.
  2. Assert-PfbApiCapability warns on violation.
  3. Assert-PfbApiCapability throws, 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 and
relying 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.ps1 asserts the "nothing vanishes" invariant over the
committed 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

  • Element field names — the walker never descends through items, so all four
    endpoints record bodyProperties: {}. Separate issue; walker fix, no schema change.
  • Per-field constraints (maxLength, pattern, numeric bounds) on ordinary object
    bodies. 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.
  • Enum values. Already covered by Reports/PfbValueEnumMap.json and
    Build-PfbValueEnumMap.ps1; not a capability-map concern.

Activity

  1. juemerson-at-purestorage commented on Aug 2, 2026

    @juemerson-at-purestorage
    CollaboratorAuthor

    Tracked in #84, sequenced as PR 2 together with the Fusion contextScope field from #25 — the two changes that need schemaVersion 2, landing on one bump. The element-field-names half is #82.

  2. juemerson-at-purestorage commented on Aug 5, 2026

    @juemerson-at-purestorage
    CollaboratorAuthor

    Filed #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 fills bodyProperties for 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.ps1 Private/Assert-PfbApiCapability.ps1
    Cmdlet affected today Set-PfbWorkloadTag Set-PfbWorkloadTag
    Callers 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 against Assert-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
    
  3. juemerson-at-purestorage commented on Aug 5, 2026

    @juemerson-at-purestorage
    CollaboratorAuthor

    Context for whoever picks this up — this is now the last outstanding item in #84's group.
    #71, #82 and contextScope all landed via #96/#97 and reach main in #98.

    The base you inherit changed

    • schemaVersion is already 2, not 1. Phase 0 took the bump for contextScope, so this work
      increments from 2 → 3. Cheap either way: nothing in Private/ or Public/ 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 main after Integration: Fusion Phase 0 prerequisites + capability-map schema-walk fixes (#96, #97) #98 will be SHA-256 6B9F0865…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) and PATCH /file-systems (top level, contextScope and
    readOnlyBodyProperties swapping). 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.yml never fetches the gitignored tools/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-artifact Describe blocks actually executed
    — a scoped run reporting 0 skipped does not prove they ran.

    The requirement this issue does not carry

    From #84, and it applies here directly: Tests/Build-PfbApiDriftReport.Tests.ps1 asserts that
    every query parameter and body property the committed map lists, for an endpoint an existing
    cmdlet calls, lands in exactly one of missingQueryParameters / missingBodyProperties /
    readOnlyFields, or the phantom-exclusion set.

    Adding bodyConstraints must account for that in the same commit or the invariant goes red. #82
    added 23 fields and hit precisely this.

    Useful precedent

    If bodyConstraints needs schema-walking changes, note that #82's fix deliberately did not
    touch Add-PfbSchemaPropertyNodes: that walker is shared with Get-PfbSpecResponseShapes, whose
    contract requires an envelope's properties and its items[] element's properties stay separate
    levels. The items hop went at the Get-PfbSpecCapabilities call site instead, and
    Data/PfbResponseShapeMap.json staying byte-identical (423DE668…FEF6) was the guard proving no
    leak. Reuse that guard — if that SHA moves for a bodyConstraints change, something leaked.

  4. juemerson-at-purestorage commented on Aug 20, 2026

    @juemerson-at-purestorage
    CollaboratorAuthor

    Carrying 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, to main via #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:707 asserts 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's missingQueryParameters /
    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.

    bodyConstraints is 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 bodyConstraints actually slots in, as of main today

    The four array-bodied endpoints, post-#82 and post-contextScope:

    Endpoint minVersion bodyProperties Current key set
    PUT /workloads/tags/batch 2.23 5 minVersion, parameters, bodyProperties, contextScope
    POST /nodes/batch 2.18 12 + readOnlyBodyProperties
    POST /resource-accesses/batch 2.19 2 as /workloads/tags/batch
    POST /fleets/members/batch 2.27 4 as /workloads/tags/batch

    23 fields across the four, matching #82's prediction. Only PUT /workloads/tags/batch declares
    constraints, so the expected diff is still the single endpoint described above.

    schemaVersion is already 2

    This issue's body says it "makes this the first of the pending capability-map changes to require
    schemaVersion 2". That is no longer true — contextScope took 2 when Fusion Phase 0 landed,
    and Data/PfbCapabilityMap.json on main carries "schemaVersion": 2 today. This change
    increments to 3.

    The number remains a per-artifact maintainer label. Nothing in Private/ or Public/ reads it;
    five generators each write their own independent value.

    Two regeneration gotchas, confirmed in practice on #97

    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.

    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.

  5. added
    status:needs-designUnderstood, but the approach is not settled. Needs a design doc.
    priority:P1Wrong on the wire, or blocks other queued work.
    size:MA day. Several files, one coherent change.
    area:capability-mapData/PfbCapabilityMap.json, its generator, and Assert-PfbApiCapability.
    source:driftOpened from a drift-report finding, by tooling.
    on Sep 21, 2026
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

    area:capability-mapData/PfbCapabilityMap.json, its generator, and Assert-PfbApiCapability.priority:P1Wrong on the wire, or blocks other queued work.size:MA day. Several files, one coherent change.source:driftOpened from a drift-report finding, by tooling.status:needs-designUnderstood, but the approach is not settled. Needs a design doc.

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions