Skip to content

Integration: Fusion Phase 0 prerequisites + capability-map schema-walk fixes (#96, #97) - #98

Merged
juemerson-at-purestorage merged 23 commits into
mainfrom
integration/capability-map-2026-08
Aug 13, 2026
Merged

Integration: Fusion Phase 0 prerequisites + capability-map schema-walk fixes (#96, #97)#98
juemerson-at-purestorage merged 23 commits into
mainfrom
integration/capability-map-2026-08

Conversation

@juemerson-at-purestorage

@juemerson-at-purestorage juemerson-at-purestorage commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator

Brings integration/capability-map-2026-08 to main. This is two already-reviewed PRs
stacked and tested together
, not new work:

PR What it does
1 #96 Fusion Phase 0 prerequisites — adds contextScope to the capability map, schemaVersion 1 → 2
2 #97 Corrects #71 and #82 — schema-walk depth and array items

Fixes #71. Closes #74. Closes #82 — see the scope note under "Deliberately not in scope".

(Those keywords live here rather than on #96/#97 because closing keywords only fire for merges
into the default branch, and both of those targeted the integration branch. #83 and #95 stay
open deliberately.)

Both were reviewed and CI-green on their own. They are stacked rather than merged
independently because both regenerate Data/PfbCapabilityMap.json, and merging them in
parallel would have made each one's artifact diff unreadable — you could not tell which
change caused which line to move. Sequencing preserves that evidence; the integration branch
then proves the combined state works before anything reaches main.

Sequencing was agreed in #84.

Once this merges, Phase 1 of the Fusion context work is unblocked

This branch is the complete prerequisite set — merging it is what allows Phase 1 to start.
The design doc originally folded all four prerequisites into Phase 1; they were split out
because each is independently justifiable and two are load-bearing in a way that is easy to
get wrong if built alongside the feature that consumes them. All four are here:

# Prerequisite Why it had to precede Phase 1
1 context_names component resolution moved from tools/lib into Private/ Phase 1 needs resolved component names to call the cardinality predicate at all. Built in the obvious order it silently reimplements the rule, producing two copies (#74)
2 contextScope per-endpoint field, schemaVersion 2 Kind-vs-scope validation and every scope-aware error message are map lookups. They cannot be written before the map carries the field
3 Connect-time capability-map staleness warning The honesty half of the decision to stay permissive beyond the map's scanned range. Not context-specific
4 Get-PfbFleetMember top-level MemberName / FleetName Phase 1's Set-PfbContext pipeline binding depends on it

Verified present on the merge commit: a single canonical resolver in Private/ which the
tools/lib rule checker dot-sources rather than duplicating; schemaVersion: 2 with
632 of 632 endpoints carrying contextScope; the staleness check wired into
Connect-PfbArray; and the two new top-level properties on Get-PfbFleetMember.

Nothing here introduces -Context, Set-PfbContext, Invoke-PfbInContext, -AllArrays,
or any injection behaviour
— that is all Phase 1. contextScope ships populated and
nothing reads it at runtime yet. A user upgrading to this cannot tell the context work is
coming, except that Get-PfbFleetMember output is more readable and a warning may appear at
connect time.

To be precise about what is not here: #83 and #95 remain open, and neither is a Phase 1
prerequisite.
#83 (bodyConstraints) is a separate capability-map enhancement, and #95 is
the runtime half of #82. Neither appears in the design's Phase 1 scope, so both can land on
their own schedule without holding Phase 1 up.

What lands

Phase 0 (#96) — every one of the 632 capability-map endpoints gains a contextScope
field, {scope, provenance}, where scopearray/fleet/unknown and provenance
default/declared/live-tested/unknown. Derived from three x-pure-* spec extensions
plus a curated table. unknown is a designed degradation, not a gap — it means the spec
carries no signal, and the cmdlet is left to the array to accept or reject rather than being
guessed at client-side. schemaVersion moves to 2; nothing in Private/ or Public/ reads
that field, so the bump is an inert label.

#71MaxDepth defaulted to 8, truncating fb2.12–2.16 allOf chains. Five body fields
on PATCH /password-policies carried introducedVersion = 2.17 when the specs say 2.16.
Runtime-visible rather than cosmetic: Assert-PfbApiCapability reads bodyProperties, so the
module would have refused those five parameters against an array on REST 2.16. The 2.17 spec
restructuring flattened these chains, which is why the defect self-heals from 2.17 on and is
invisible against current data.

#82 — the walk never descended an array body's items. Four batch endpoints recorded no
usable bodyProperties, hiding 23 fields from both the runtime gate and the drift report.

The two compose: descending items consumes a depth level, so #82 needs #71's headroom.
Neither fix does anything alone.

The design decision worth your attention

#82's obvious fix — teach Add-PfbSchemaPropertyNodes to descend items — would have
silently corrupted Data/PfbResponseShapeMap.json. That walker is shared with
Get-PfbSpecResponseShapes, whose entire contract is that an envelope's properties and its
items[] element's properties stay two deliberately-separate levels. Collapsing them
would make the response-shape map's cross-version removal detection compare incomparable
sets, with nothing to warn.

So the items hop happens at the Get-PfbSpecCapabilities call site, matching the precedent
Get-PfbSpecResponseShapes already sets. The shared walker is untouched, and
Data/PfbResponseShapeMap.json is byte-identical throughout — 423DE668…FEF6 before Phase 0,
after #97, and after the merge.

Verification of the merged state

Both PRs were green individually, but each PR's CI only ever saw its own merge preview — the
combined state had never been built. Run directly on the merge commit:

Full dual-edition suite:

pwsh 7    Pester 6.0.1  1830 passed / 0 failed /   2 skipped   Container ok
WinPS 5.1 Pester 6.0.1  1642 passed / 0 failed / 190 skipped   Container ok

Totals reconcile at 1832 per edition, so the 5.1 skips are the deliberate edition guards, not
containers failing to load.

Artifacts regenerated from the 29 cached specs and compared byte-for-byte:

Artifact Committed Regenerated
Data/PfbCapabilityMap.json 6B9F0865…F156 identical
Data/PfbResponseShapeMap.json 423DE668…FEF6 identical

Worth knowing: no CI job performs that comparison. cross-platform-tests.yml never
fetches the gitignored tools/specs/, so the ~27 real-artifact assertions skip silently
in CI and read exactly like passes. Those checks were run locally with the spec cache present,
and the real-artifact Describe blocks were confirmed to have actually executed rather than
skipped. A green CI summary on this repo is not evidence that the committed artifacts match
generator output — that has to be done by hand, and was.

Also verified: reverting only tools/lib/PfbSpecTools.ps1 to the pre-#97 base reproduces
Phase 0's capability map byte-identically, so 100% of #97's map diff is attributable to its own
code rather than drift or staleness.

Live-tested on FB-A, REST 2.26 (7 ledger rows against #97's tip): create / read-back /
update / destroy / eradicate all pass, PATCH /file-systems showed a real 1 → 2 GiB state
change, no residue left behind.

Two coverage gaps stated plainly rather than papered over:

Known-pre-existing, not caused by this work

Reports/PfbApiDriftReport.json on main was one gap stale, which is why the "nothing
vanishes" invariant in Tests/Build-PfbApiDriftReport.Tests.ps1 has been red locally. Root
cause: New-PfbFileSystemReplicaLink's [Nullable[bool]]$RemoteDefaultExports goes through a
conditional assignment the drift tracer cannot follow → confidence highpartial
enrichment disabled → systemicGaps/conventionStrength 252 → 246. Not spec staleness;
analysedVersions is identical at 2.28.

This was confirmed pre-existing three independent ways, and #97 separates it into its own
commit so it cannot be misread as damage from the schema-walk change. That test is now green.

The stale report is numerically identical to what
origin/automated/update-api-capability-map already holds — that workflow has failed at its
Open pull request step on every run since 2026-07-24, so main never received it. Worth
deciding separately whether that workflow should be repaired or deleted, since it regenerates
reports this work already regenerates.

Deliberately not in scope

juemerson-at-purestorage and others added 22 commits August 4, 2026 15:34
Splits the four prerequisites out of the design doc's Phase 1 into their
own phase, each independently justifiable and shipping no user-visible
context surface:

  1. Move context_names component resolution into Private/ (#74)
  2. contextScope per-endpoint map field, schemaVersion 1 -> 2
  3. Connect-time capability-map staleness warning
  4. Get-PfbFleetMember top-level MemberName/FleetName

Derived from docs/design/fusion-context-injection.md rev 4. Extension
counts (5 override / 28 incomplete-gre / 266 block) and the four-entry
curated contextScope table verified against tools/specs/fb2.28.json.

Refs #25, #74

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The spec treated contextScope + schemaVersion 2 as standalone. It is not:
issue #83 also needs schemaVersion 2, and tracking issue #84 plans both as
a single bump in a single PR (PR 2, behind PR 1 = #71 + #82). Adds the
coordination section with three options and a recommendation, and flags
the decision at the top of the document.

Also corrects two claims about test enforcement:

- schemaVersion is per-artifact, not global. Five generators each write
  their own independent value, all currently 1, and nothing in Private/
  or Public/ reads any of them -- it is a label, not a migration gate.
  The earlier justification (Get-PfbCapabilityMap as the single gate)
  overstated it.
- There is no byte-identical regeneration gate on the committed map. The
  drift test byte-identical assertions check determinism across two runs
  on synthetic fixtures; the real-artifact checks skip on gitignored
  tools/specs/. A green result is not proof they ran.

Notes that #85 (phantom-diff blocker) is closed, and that
origin/automated/update-api-capability-map holds regenerations main never
received because the auto-PR step has failed since 2026-07-24.

Refs #25, #83, #84

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Decision: Phase 0 ships contextScope and takes schemaVersion 2. #84s
generator work (#71, #82, #83) moves to its own PR, with #83 incrementing
from whatever it finds rather than being pre-assigned 3.

The test was whether Phase 0 could absorb all of #84, which would have
preserved the one-bump intent. It cannot, structurally: #71, #82 and #83
all land in Add-PfbSchemaPropertyNodes (tools/lib/PfbSpecTools.ps1:191), a
recursive walker whose MaxDepth threads through five call sites and which
feeds both Data/PfbCapabilityMap.json and Data/PfbResponseShapeMap.json.
It also has a non-Fusion downstream consumer -- #44 is blocked on PR 1,
whose four endpoints are the ones #82 makes visible.

contextScope reads operation-level x-pure-* extensions and never touches
the body-schema walk, so the two changes are independent in code. The cost
is two sequential regenerations of the map plus the lost bookkeeping
tidiness, both acceptable because nothing reads schemaVersion.

Follow-up owed: update #84 so its PR 2 no longer claims contextScope.

Refs #25, #83, #84

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…omponent resolution

The three-step resolution contract had no runtime home: it was inlined in
tools/lib/PfbContextRuleTools.ps1, which the module never loads. Extracting it
pure and parameterised lets both the module and tools/ execute one copy.

Key-present-but-null vs key-absent are distinguished, with an explicit
regression test -- both return $null, so the distinction is invisible in the
return value and trivially reimplemented wrong.

Refs #74

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ustomObject

Review of 74ada89 found the key-presence test only worked for the shape
ConvertFrom-Json produces. For a Hashtable or OrderedDictionary,
.PSObject.Properties.Name returns the adapted CLR surface (Keys, Values, Count,
IsReadOnly, ...) rather than the keys, so the test was ALWAYS FALSE and every
lookup fell through to the defaults -- reproducing the exact wrong-component
defect this function exists to prevent. It was also a false positive for an
intrinsic name: -ParameterName 'Count' returned the item count, an [int].

That shape is not hypothetical. tools/Build-PfbCapabilityMap.ps1 builds
parameterComponentDefaults and parameterComponentOverrides as [ordered]@{}, so
any tools/ caller resolving against the map it just built, rather than the JSON
round-trip, was affected.

Also closes four mutation gaps the original fixtures could not see. All four
wrong implementations below passed the original 7 tests; each is now killed:

  - regex -match instead of -contains: 'ids' matches the key 'ids_or_names'
  - reversed -contains operands: works only because every original fixture had
    a single-key overrides object. The real map has 40 multi-key overrides
    objects, 18 containing context_names -- the original bug class on real
    endpoints
  - guarding on the overrides PROPERTY existing rather than being non-null
  - PSObject-only resolution (the defect above)

-like was also examined and is NOT a defect: with no wildcard characters it is
exact match, so it is equivalent to -contains for real parameter names.

Behaviour on the shipped map is unchanged: the Get-PfbContextParameterFact
fact-set hash over Data/PfbCapabilityMap.json is byte-identical before and
after, per edition (376 facts, 376 with a component, 135 rule-multi-value).

Refs #74

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…nlining

Get-PfbContextParameterFact now calls Resolve-PfbParameterComponent rather than
reimplementing the three-step contract, and the context/allow_errors parameter
names move to Private/ so the module can reach them in Phase 1.

tools/ now depends on Private/ for the resolver, the constants and the
cardinality rule. That direction is the fix -- relocating without inverting the
dependency would not resolve the issue.

Behaviour is unchanged, verified three ways:
  - Tests/PfbContextRuleTools.Tests.ps1 is 54/0/0 on both editions, matching the
    pre-change baseline exactly
  - the full fact-set hash over Data/PfbCapabilityMap.json is byte-identical
    before and after the refactor, per edition
  - tools/ loads standalone in a fresh process with NO module imported, under
    both pwsh 7 and Windows PowerShell 5.1, yielding 376 facts / 376 with a
    component. A session with the module already imported would have masked a
    broken path computation, so this is checked without one.

Closes #74

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Get-PfbSpecContextScope reads x-pure-remote-execution-context-domains-override,
x-pure-incomplete-gre and x-pure-block-remote-execution per operation. It is a
sibling of Get-PfbSpecResponseShapes rather than an extension of
Get-PfbSpecCapabilities: the latter's record feeds drift gap accounting, and
issue #84's PR 1 edits its body walk, so extending it would collide for no
benefit. It never reaches Add-PfbSchemaPropertyNodes, so neither generated
artifact can move.

DomainsOverride is an empty array when the extension is absent, never $null, so
callers can test .Count without a null guard.

Includes a canary on the 5/28/266 extension counts at fb2.28, so the curated
table gets revisited when upstream's annotation pass advances. Verified against
the real spec: the 5 overrides are all /presets/workload (GET ARRAY+FLEET, every
write verb FLEET only), and all four endpoints the generator curates in the next
commit are in fact flagged incomplete.

Refs #25

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…sion 2

Every endpoint now carries { scope, provenance }. Declared domains overrides are
trusted (5 preset operations); four live-tested endpoints are curated; the
remaining 19 flagged x-pure-incomplete-gre record 'unknown' rather than being
defaulted, since a flagged endpoint's absent override proves nothing; everything
else defaults to 'array', the fail-safe direction.

Measured distribution over 632 endpoints, matching the spec's acceptance
criteria exactly:

  provenance  declared 5, live-tested 4, unknown 19, default 604
  scope       array 606, fleet 7, unknown 19

The regeneration was verified structurally rather than by reading the diff,
since there is no CI gate on this artifact (cross-platform-tests.yml never
fetches tools/specs/, so every real-artifact assertion skips). Comparing the
committed map against the regenerated one, key by key: schemaVersion 1 -> 2,
one contextScope object added per endpoint, and ZERO endpoints differing in any
other key. Endpoint key set and order identical; parameterComponentDefaults
identical.

contextScope does not reach drift accounting: the drift report generated from
the old map and from the new one is byte-identical (SHA-256), and the string
'contextScope' appears nowhere in either output. Reports/ therefore needs no
regeneration here and is deliberately left untouched -- its one stale entry
predates this branch and belongs to issue #84's PR 1.

DomainsOverride is now coerced to [string[]] so the declared .OUTPUTS type is
literally true; a bare @() yielded Object[].

Nothing reads contextScope at runtime in this phase -- Phase 1's kind-vs-scope
validation and scope-aware error messages consume it.

schemaVersion is per-artifact and read by nothing in Private/ or Public/, so the
bump is a maintainer label. Issue #83 increments from whatever it finds.

Refs #25, #84

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Fails when a curated entry gains an upstream domains override or stops being
flagged incomplete, so the table shrinks on its own instead of silently
shadowing better data. Also pins the 5/4/19 provenance distribution and the
topology-group write verbs' array default.

Verified the guard bites: adding a bogus fifth curated entry pointing at
GET /presets/workload -- which does carry an override -- fails exactly the
"gained an upstream override" and "exactly the expected curated endpoints"
assertions, then passes again once reverted.

The topology-group family is enumerated by regex rather than by a hardcoded verb
list, since issue #38 is still settling which verbs exist. The curated table is
mirrored here deliberately rather than imported: a test that reads the table it
is checking cannot detect the table going stale.

Refs #25

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…lity map

Assert-PfbApiCapability stays permissive beyond the map's scanned range, because
a cmdlet caller never chose the negotiated version and should not be blocked by a
packaging lag they cannot see. This warning is the other half of that trade, not
decoration.

Fires once per connection, caches ExceedsCapabilityMapCoverage on the connection
object, and makes no Gallery lookup -- this module runs against air-gapped
arrays. Major/minor compared as integers, since a string compare places 2.9
above 2.28.

The comparison is DELEGATED, not reimplemented: Test-PfbVersionAtLeast is the
same comparator Assert-PfbApiCapability uses, so the warning and the capability
check cannot disagree about what is in range, and ConvertTo-PfbVersionObject
finds the highest scanned version. A third copy of major/minor parsing in the
phase whose purpose is removing a duplicated rule would reintroduce #74's failure
mode. Mutation-verified: swapping the delegation for a string compare fails the
'2.9 does not read as newer than 2.28' test and nothing else.

Two failure modes are deliberately silent rather than loud, because the map is
diagnostic and the connection is the user's actual goal: an unparseable
generatedFrom entry (both version helpers cast with [int] and do not guard), and
a capability map that THROWS. The latter is a real regression risk this guards
against -- Get-PfbCapabilityMap returns $null for a missing file but does not
guard ConvertFrom-Json, so a corrupt shipped map would otherwise have become a
connect-time failure, leaving no way to connect and diagnose it.

ExceedsCapabilityMapCoverage is kept out of $defaultProps: it is diagnostic
state, and adding it would change the default Format-List view for every
connection.

Refs #25

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…Member

ValueFromPipelineByPropertyName matches only top-level names, and fleets/members
nests the array name at .member.name -- so piping into a context-scoping cmdlet
would bind nothing and silently no-op. Additive: the raw nested objects survive.

Decoration runs per emitted item rather than over the first page, so
-AutoPaginate's later pages are decorated too.

No IsLocal: is_local is relative to the call's context rather than the
connection, so the obvious Where-Object { -not $_.IsLocal } idiom would silently
select a different array once a context is active. Determining the local array
belongs to the connection, not a per-call response field.

Adds the cmdlet's first test file.

Refs #25

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Review found that two of the four scope-ladder decisions survived mutation with
every test green. Since there is no CI gate on Data/PfbCapabilityMap.json, the
fixture is the only rail, and it modelled a spec that does not exist.

1. contextScope had NO last-seen-wins coverage. The fixture used one spec
   version, and the comment claimed that was "enough" because the field is not
   version-gated -- backwards, since one version is exactly what cannot
   demonstrate non-version-gating. Wrapping the assignment in the same
   first-sight guard its neighbours use (minVersion, parameters,
   bodyProperties, three lines away) passed 6/6 while erasing all 5 declared and
   all 19 unknown records from the real artifact, because the annotations exist
   only in fb2.28 while every endpoint is first sighted in 2.0-2.27. The fixture
   is now two versions: 2.27 declares the endpoints unannotated, 2.28 annotates
   them, and the newer value must win. minVersion is asserted as still 2.27, so
   the two policies are shown to coexist rather than one having replaced the
   other. That mutation now fails 4 tests.

2. The fixture inverted reality on the only endpoints where ladder steps 1 and 3
   compete. All 5 override-bearing operations in the real fb2.28 are ALSO flagged
   x-pure-incomplete-gre; the fixture's carried the override without the flag, so
   making the flag beat the declaration passed 6/6 (declared 5 -> 0,
   unknown 19 -> 24). The flag is now on both fixture operations, with a
   dedicated test for the precedence. That mutation now fails 3 tests.

3. An override declaring an unrecognised domain became 'fleet' with
   provenance 'declared' -- asserting a scope on no evidence while telling
   Phase 1 upstream had declared it. It now falls to unknown/unknown, the same
   honest failure mode the flagged-but-uncurated case uses. Unreachable today:
   only ARRAY and FLEET occur across all 29 cached specs, and the regenerated
   artifact is byte-identical (SHA-256 2E59D07D...), so this is inert and covered
   by its own synthetic fixture.

4. Get-PfbFleetMember decorated Invoke-PfbApiRequest's { total_item_count = N }
   sentinel -- returned when the API reports a count but no items, which is what
   a filter matching nothing produces. That emitted an object looking like a
   fleet member whose name is $null, so piping it onward would bind $null: the
   exact silent-wrong-binding failure the decoration exists to prevent. The
   sentinel now passes through undecorated so its absence fails loudly.

Also: the drift guard never read the generator's curated table, only the
committed map, so editing $curatedContextScope without regenerating was
invisible. It now scrapes the generator's literal too. And the multi-page test
was renamed to what it actually proves -- it mocks Invoke-PfbApiRequest, so it
exercises one multi-item return, not pagination; what makes paging safe is that
-AutoPaginate accumulates every page into one array, a property of that function
and tested there.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ion gaps

Review swept 20 hostile generatedFrom shapes and could NOT break connecting: the
warning branch's parse was a byte-for-byte replay of the helper's own expression,
so a $true return was itself proof the expression had already succeeded. Safe --
but only by an invariant living in a different file, with nothing at the call site
saying so. Proven fragile: changing the helper's catch to return $true made the
call site throw outside any try and return no connection at all.

So the helper now hands back the maximum it already computed, via a [ref]
out-param (the idiom Assert-PfbConnection already uses here). The warning branch
performs no parse, no index and no cast -- it is one Write-Warning -- so it cannot
break the connect path structurally rather than by cross-file contract. The same
mutation now degrades to a spurious warning instead of a failed connection, and
is still caught by a test.

Three surviving mutations closed, all test-only:

  - Swapping the two versions in the warning text passed all 11 tests. The
    assertions checked only that '2.29' and '2.28' each appeared SOMEWHERE, so the
    backwards and actively misleading "running REST 2.28 ... covers through REST
    2.29" was green. Each version is now pinned to its role.

  - Adding ExceedsCapabilityMapCoverage to $defaultProps passed all 11 tests,
    despite the exclusion being presented as a deliberate decision. Now asserted
    against DefaultDisplayPropertySet, together with the property still being
    reachable programmatically.

  - The no-Gallery-lookup check was a raw text grep, which fails on a harmless
    COMMENT mentioning Find-Module or a doc link to the Gallery -- this test's own
    comment would have tripped it. It now walks the AST's invoked command names,
    so comments and strings cannot produce a false positive, and it covers all
    five files in the warning's call graph rather than two. The earlier claim that
    grepping "proves absence" was overstated on both counts.

Also corrected an inaccurate docstring: the .Count guard only short-circuits a
literal empty array. An absent or $null generatedFrom becomes @($null), whose
Count is 1, and is caught by the try instead. Both routes return $false, so the
guards are defence in depth and neither is individually load-bearing.

Refs #25

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… values

The synthetic Describe carried -Skip:($PSVersionTable.PSVersion.Major -lt 7),
withholding 9 assertions from Windows PowerShell 5.1 for no reason of its own:
Get-PfbSpecContextScope is a pure PSObject walk with no pwsh-7 dependency. The
only 5.1 blocker was the fixtures' own ConvertFrom-Json -Depth 32, and these
fixtures are four levels deep -- 5.1's ConvertFrom-Json parses them unaided.
Dropping -Depth lets the block run on both editions, matching the convention in
PfbSpecTools.Tests.ps1, which skips only the Describe that genuinely needs it.

The second Describe keeps its guard and now says why: the real fb2.28 spec does
need -Depth 64, which does not exist before PowerShell 6.2.

Also closes a surviving mutation. IsIncompleteGre/BlocksRemoteExec test both key
presence AND the key's value, but every fixture here -- and all 304 occurrences
across the 29 cached specs -- emits these extensions only as literal true, so a
presence-only implementation passed every assertion. Verified: reducing both to
[bool]($opKeys -contains $key) now fails on both editions. An endpoint upstream
deliberately un-flagged would otherwise have been forced to unknown/unknown with
nothing to catch it.

pwsh 7 14/0/0, WinPS 5.1 10/0/4, Container ok on both.
…EADME

tools/README.md is the tracked contract doc for the capability map's shape. It
enumerates what each endpoint record carries, and contextScope -- now emitted on
all 632 endpoints -- appeared nowhere in it, nor did the schemaVersion 1 -> 2
bump. Note contextScope is unconditional, unlike the "where non-empty" keys
above it, and record that it is last-seen-wins for the same non-monotonicity
reason as readOnlyBodyProperties.

Also point the parameter-component resolution prose at
Private/Resolve-PfbParameterComponent.ps1. That file now declares itself the
single runtime home of the contract and Build-PfbCapabilityMap.ps1 points here,
which left this README as the one copy of the contract not pointing at its
implementation -- the prose form of exactly the drift #74 exists to close.
…y-map-2026-08

feat(fusion): Phase 0 prerequisites - contextScope in the capability map, centralised component resolution, staleness warning
…tems

Two defects in Get-PfbSpecCapabilities' request-body walk, both changing
Data/PfbCapabilityMap.json and both runtime-visible through
Private/Assert-PfbApiCapability.ps1, which reads bodyProperties.

#71 -- the two body-schema helper calls passed no -MaxDepth, so both took the
helpers' own default of 8. The fb2.12-2.16 schemas compose through allOf chains
deeper than that, so five PATCH /password-policies fields recorded
introducedVersion 2.17 instead of 2.16 and would have been refused against a
2.16 array. Get-PfbSpecCapabilities now takes -MaxDepth, defaulting to 32 --
the value Get-PfbSpecResponseShapes already uses, chosen there by measuring
this same truncation (184 false removals at 8 versus 7 true ones at 32).

#82 -- a request body that is itself `type: array` carries its element schema
on the `items` sibling keyword, not as a property, so the walk terminated
immediately and four batch endpoints recorded an empty bodyProperties, hiding
23 fields from both the runtime gate and the drift report.

The items hop is done at the CALL SITE, not by teaching
Add-PfbSchemaPropertyNodes to descend `items`. That walker is shared with
Get-PfbSpecResponseShapes, whose contract is that an envelope's properties and
its items element's properties stay two separate levels; collapsing them would
silently change Data/PfbResponseShapeMap.json and make its cross-version
removal detection compare incomparable sets. This mirrors the hop
Get-PfbSpecResponseShapes already performs at its own call site. Verified:
Data/PfbResponseShapeMap.json regenerates byte-identical.

Both regressions are covered by fixtures confirmed to fail before the fix.

Artifact regeneration is deliberately NOT in this commit -- it is sequenced
behind Fusion Phase 0 per issue #84.

Refs #71, #82. Unblocks #44.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The rule has never had any effect. It was added in 69fe478 -- the same commit
that first committed tools/ -- as a hedge while it was undecided whether the
toolchain should be tracked ("Not yet decided whether this should be tracked").
Ignore rules do not apply to already-tracked files, so all 14 files under
tools/ have been tracked from birth and the rule has been inert ever since.

Removing it is a no-op for the working tree and a small improvement going
forward: a NEW file added under tools/ now shows up in git status instead of
being silently invisible, which is how tooling work here has previously gone
missing.

tools/specs/ keeps its own separate rule (.gitignore:36) and stays ignored --
verified that all 29 cached spec files remain ignored after this change, and
that git surfaces no newly-untracked files.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The comment explaining why array bodies go unchecked is invalidated by the
issue #82 map change in this branch, in both of its halves.

It stated that every array-bodied endpoint carries "bodyProperties": {}, so a
per-element check "could never fire". That is no longer true -- the four batch
endpoints now carry real per-element fields.

It then predicted that "this loop picks it up for free if a future map
representation ever lands." That was wrong even when written: the loop is
guarded on $Body being an IDictionary, and an array body arrives as
[hashtable[]] (Set-PfbWorkloadTag passes -Tags straight through), so the loop
is skipped before the map is consulted. A richer map alone changes nothing.

Replaced with what is actually true now: the map records the fields, the type
guard is the remaining blocker, and relaxing it is a real behaviour change that
can refuse calls which succeed today -- deliberately not smuggled in with a
generator fix. Also records that the blast radius is nil today (only
Set-PfbWorkloadTag reaches such an endpoint, all its fields are 2.23) and that
this stops holding once #44 adds cmdlets for the other three.

Comment-only. No behaviour change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Pre-existing correction, isolated deliberately. This commit reverts nothing
and fixes nothing in the generator -- it regenerates Reports/ with
tools/lib/PfbSpecTools.ps1 held at the Phase-0 base, so the entire diff here
is staleness that already existed on main before this branch.

Reports/PfbApiDriftReport.json on main was one gap behind the committed
capability map, which is why the "nothing vanishes" invariant in
Tests/Build-PfbApiDriftReport.Tests.ps1 has been red locally. Root cause is
New-PfbFileSystemReplicaLink's [Nullable[bool]]$RemoteDefaultExports being
sent through a conditional assignment the drift tracer cannot follow, so
confidence degrades high -> partial, enrichment is disabled, and
systemicGaps/conventionStrength move 252 -> 246.

Not spec staleness: analysedVersions is identical at 2.28.

The diff here (271/19/14/5 lines across four files) is numerically identical
to what origin/automated/update-api-capability-map already holds -- that
workflow has failed at its "Open pull request" step on every run since
2026-07-24, so main never received this regeneration.

Splitting it out means the next commit's diff contains only what this PR's
code change actually causes.
#82

Everything in this diff is caused by the walker fix in the previous commits.
Verified by construction: reverting tools/lib/PfbSpecTools.ps1 to the base and
regenerating reproduces Data/PfbCapabilityMap.json byte-identical to the
committed Phase-0 artifact, so nothing here is drift or nondeterminism.

Capability map -- exactly 7 endpoint records change, in four groups:

  #71  5 x introducedVersion 2.17 -> 2.16 on PATCH /password-policies
       (name, id, enabled, is_local, location)
  #82  23 bodyProperties recovered across the 4 array-bodied endpoints:
       POST /nodes/batch +12, POST /resource-accesses/batch +2,
       PUT /workloads/tags/batch +5, POST /fleets/members/batch +4
  #82  readOnlyBodyProperties on POST /nodes/batch resolves from empty to 8:
       capacity, chassis_serial_number, data_addresses, details, id,
       raw_capacity, status, unique
  ---  2 order-only records, no value changes:
       PATCH /ssh-certificate-authority-policies (bodyProperties key order)
       PATCH /file-systems (top-level key order; contextScope and
       readOnlyBodyProperties swap position)

Note the four batch endpoints did not have an EMPTY bodyProperties before --
each had exactly one entry keyed by the empty string, the nameless artifact of
a walk that reached the array node and could not descend it. That is what the
23 real fields replace.

The map is deterministic: three consecutive regenerations produced identical
SHA-256.

Data/PfbResponseShapeMap.json is byte-identical
(423de668a78356bad13b6d345bf9e834eca3438e7badf96577e78a5bbf93fef6). This is
the guard that the fix stayed at the Get-PfbSpecCapabilities call site and did
not leak into the shared Add-PfbSchemaPropertyNodes walker, whose contract is
that an envelope and its items element are two separate levels.

Phase 0 is undisturbed: schemaVersion still 2, 632 endpoints, and the full
contextScope scope-x-provenance cross-tab is unchanged at
array/default 604, array/declared 1, array/live-tested 1, fleet/declared 4,
fleet/live-tested 3, unknown/unknown 19.

Drift report: PUT /workloads/tags/batch gains 5 missingBodyProperties
(copyable, key, namespace, resource, value); addable body properties 422 -> 427.
Only this one of the four batch endpoints appears, because Set-PfbWorkloadTag is
the only existing cmdlet calling any of them -- the other three are #44's scope.

Reports/PfbFieldCmdletMap.json, Reports/PfbValueEnumMap.json and their .md
siblings do not move under this change.
…tegration/capability-map-2026-08

fix(tools): walk array item schemas and raise MaxDepth (#71, #82)
Resolve the four generated report artifacts by regeneration rather than by
hand. Both sides had regenerated them from different inputs: main after the
P0 wire-fix batch (2978f19), this branch after the Phase 0 and #84 PR 1
capability-map work. Three conflicted textually; Reports/PfbFieldCmdletMap.json
auto-merged into a splice that matched no generator run on either side, which
is the more dangerous case because nothing flags it.

Regenerated in dependency order: Build-PfbCapabilityMap, Build-PfbFieldCmdletMap,
Build-PfbResponseShapeMap, Build-PfbApiDriftReport.

Data/PfbCapabilityMap.json and Data/PfbResponseShapeMap.json both reproduce
byte-identically across the merge (6B9F0865...F156 and 423DE668...FEF6), so
main's cmdlet changes did not leak into either map.

Against main the only substantive report change is Set-PfbWorkloadTag's
missingBodyProperties moving from [] to the five fields copyable, key,
namespace, resource, value -- #82's array items[] walk, working as intended.
@juemerson-at-purestorage
juemerson-at-purestorage merged commit b9670ff into main Aug 13, 2026
8 checks passed
juemerson-at-purestorage added a commit to juemerson-at-purestorage/fb-powershell that referenced this pull request Aug 13, 2026
…landed on main

The 5.1 leg went red on the skip ceiling with 0 failed tests: 1818 passed,
190 skipped, ceiling 185. Nothing in this branch caused it.

PR dmann000#98 (Fusion context Phase 0) merged to main between this branch's previous
CI run and its latest one. A pull_request run tests the MERGE commit, so the
ceiling -- measured against a main that predated dmann000#98 -- went stale without any
change here. dmann000#98 added 21 PS7-gated It blocks (8 in
Build-PfbCapabilityMap.ContextScopeDrift.Tests.ps1, 9 in
Build-PfbCapabilityMap.Tests.ps1, 4 in PfbSpecTools.ContextScope.Tests.ps1),
and the observed 5.1 skip count moved 169 -> 190. Exactly +21.

They run on 7 -- the pwsh7 leg is unchanged at 2 skipped over 2006 passed,
against its ceiling of 8 -- so this is the PS7 gate doing its job, not lost
coverage. All three required Describes ran on 5.1 with 0 skips.

Raised to 206, keeping the same +16 headroom over measured that 185 had over
169, with the measurement, the run id and the attribution recorded inline as
this file asks for. Worth noting the gate behaved correctly here: it is
designed to red on a stale ceiling, and a merge to main is one of the ways a
ceiling goes stale.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
juemerson-at-purestorage added a commit that referenced this pull request Aug 14, 2026
Rebasing this lane onto a main carrying PR #98 (regenerated capability map)
and PR #107 changes the drift denominator, so the measured figures quoted as
the justification for the 0.55 -> 0.60 top-10 aggregation ceiling were stale.

Record the re-measured values -- main alone 54.81% (570/1040), this lane on
top of it 55.79% (564/1011) -- alongside the original pre-#98 pair. The
conclusion is unchanged: 0.55 is genuinely exceeded and 0.60 still holds.

Co-Authored-By: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment