Skip to content

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

Merged
juemerson-at-purestorage merged 15 commits into
dmann000:integration/capability-map-2026-08from
juemerson-at-purestorage:feat/fusion-context-phase-0
Aug 5, 2026
Merged

feat(fusion): Phase 0 prerequisites - contextScope in the capability map, centralised component resolution, staleness warning#96
juemerson-at-purestorage merged 15 commits into
dmann000:integration/capability-map-2026-08from
juemerson-at-purestorage:feat/fusion-context-phase-0

Conversation

@juemerson-at-purestorage

Copy link
Copy Markdown
Collaborator

Phase 0 of the Fusion context_names work (#25, design spec rev 3 in #72). Prerequisites
only — nothing in this PR injects a context or changes any request on the wire. It
populates the data and centralises the contracts that Phase 1's injection path needs.

Closes the contextScope half of #25's Phase 0 scope. Sequencing agreed in
#84.

What shipped

18 files across 15 commits (3 docs, 10 code, 2 from the whole-branch review).

Task Deliverable
1 Resolve-PfbParameterComponent — the single runtime home of the three-step component-resolution contract (Private/Resolve-PfbParameterComponent.ps1, Private/PfbContextConstants.ps1)
2 tools/lib consumes that Private/ resolver instead of inlining its own copy
3 Get-PfbSpecContextScope — reads the three x-pure-* remote-execution annotations
4 Per-endpoint contextScope emitted; capability map schemaVersion 1 → 2
5 Drift guard on the curated contextScope table
6 Connect-PfbArray warns once when the array outruns the bundled capability map
7 Top-level MemberName/FleetName on Get-PfbFleetMember

Five new test files, two modified.

The regenerated artifact

SHA-256       2E59D07D49B70A7CE90CBA2C6D5C243A6FECA199B4CE7E1C800DFEC4AA56A875
schemaVersion 2      versions 29      endpoints 632
provenance    default 604 | unknown 19 | declared 5 | live-tested 4
scope         array 606   | unknown 19 | fleet 7

Both distributions sum to 632. The full cross-tab:

scope provenance count
array default 604
array declared 1
array live-tested 1
fleet declared 4
fleet live-tested 3
unknown unknown 19

A contextScope of unknown is a designed degradation, not a gap. It suppresses
validation and leaves today's behaviour intact, so Phase 1 degrades rather than breaks.
Adding an entry later is a generator-table edit plus a regenerate — deliberately cheap.

Two things about the data worth not generalising

The /presets/workload read/write asymmetry is intentional. GET is array-scoped;
all four write verbs on the same path are fleet-scoped. Both sides are declared
provenance — curated deliberately, each individually evidenced. This is not a
"reads array, writes fleet" verb rule. That rule was proposed, tested against the wire,
and falsified in #73. It should not be reintroduced.

Every /fleets/* endpoint takes array/default. That is correct: scope describes
remote-execution context, not resource family, and fleet membership is array-local. The 7
fleet-scoped entries are the 4 /presets/workload writes plus the 3 /topology-groups*
reads.

Review history

Two rounds. Eight substantive issues found, all fixed and each mutation-verified. No
correctness defect survives in shipped code and there is no 5.1 compatibility break.

Folded below rather than cut: the round-1 findings existed only in conversation context
that has since been compacted, so this PR body is now their only durable home.

Round 1 — five defects found during per-task review

None of these were caught by the original testing.

# Finding Fixed in
1 Component keys resolved on PSCustomObject only. The generator builds [ordered]@{} in memory, and on an IDictionary .PSObject.Properties.Name returns the CLR surface (Keys, Count, …) — not the keys — so resolution silently missed every in-memory lookup b5bd095
2 Four wrong resolver implementations survived mutation — the tests did not pin the contract. Killed by adding multi-key overrides (the real map has 40 such objects, 18 carrying context_names) and an ids/ids_or_names decoy b5bd095
3 Zero last-seen-wins coverage. contextScope is assigned unconditionally per spec iteration while minVersion/parameters/bodyProperties use a first-sight guard three lines away. The fixture was a single spec version — precisely what cannot demonstrate non-version-gating 4241778
4 Fixture inverted reality on declared-vs-flagged precedence 4241778
5 The total_item_count sentinel was decorated into a nameless member, producing an object that looks like a fleet member whose name is $null — the exact silent-wrong-binding failure the decoration exists to prevent dd6041a

Task 6's review changed the design

25 mutations, 21 killed. The question was whether an unguarded version parse in the warning
branch could break connecting. It could not — that line was a byte-for-byte replay of the
helper's own expression, so a $true return was itself proof the expression had succeeded.

But that safety rested on an invariant in a different file, with nothing at the call
site saying so. One mutation proved it fragile: making the helper's catch return $true
sent the call site throwing outside any try, and no connection was returned. Given the
asymmetry — the warning is worth little, a broken connect costs everything — the duplicate
parse was removed and the helper now returns the maximum via a [ref] out-param. That
mutation now degrades to a spurious warning.

Three survivors were genuine test gaps, all closed: a swapped-version warning
("running REST 2.28 … covers through REST 2.29" — backwards and actively misleading)
passed all 11 tests because each version was only asserted to appear somewhere; an
excluded property could be added to $defaultProps undetected; and a Find-Module source
grep was brittle in exactly the way it had been claimed strong — it trips on a harmless
comment and missed 3 of the 5 files in the call graph. Replaced with an AST walk over
invoked command names.

Round 2 — whole-branch review across all 13 commits (three findings)

Three gaps, all fixed in 6ae04e8 and 4f07080:

  • A test was withholding 9 assertions from Windows PowerShell 5.1 for no reason of its
    own.
    Tests/PfbSpecTools.ContextScope.Tests.ps1's synthetic Describe carried an
    edition skip, but 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 those fixtures are four levels deep. Dropped -Depth, removed the skip; the block
    now runs on both editions. The sibling Describe keeps its guard, which is genuine
    (the real fb2.28 spec does need -Depth 64), and now states why. This matters because
    on 5.1 the file reported 0 passed for that block — indistinguishable from health.
  • One more surviving mutation, now killed. IsIncompleteGre/BlocksRemoteExec test
    key presence and value, but all 304 occurrences of those extensions across the 29
    cached specs are literal true, so a presence-only implementation passed everything.
    An endpoint upstream deliberately un-flagged would have been forced to
    unknown/unknown silently. Now pinned, and the mutation fails on both editions.
  • tools/README.md, the tracked contract doc for the map's shape, was stale. It did
    not mention contextScope or schemaVersion 2, and its parameter-component resolution
    prose was the one remaining copy of that contract not pointing at
    Private/Resolve-PfbParameterComponent.ps1 — the prose form of exactly the drift Move context_names component resolution from tools/lib into Private/ before the Fusion injection path is built #74
    exists to close.

Five mutations were run with hash-verified application and restore plus dual-edition
parse checks. Four were killed by existing tests: first-sight-wins substituted for
last-seen-wins on contextScope; min-scanned substituted for max in the staleness helper;
and the total_item_count sentinel guard neutralised. The three load-bearing behaviours
are each genuinely defended by a test that dies when you break it.

Verification

Live, against FB-A (REST 2.26 / Purity//FB 4.8.2). 7 ledger rows for
Get-PfbFleetMember, all at feat/fusion-context-phase-0@33fbd32, 185–1217ms.

  • Unfiltered: 3 members, stable over 3 repeats. Decoration correct on all rows; nested
    member/fleet preserved.
  • -MemberName 'FB-A'exactly 1 row, decorated, 208ms. Never → 0.
  • Bogus name → HTTP 400 "Fleet member does not exist." A specific rejection is
    positive evidence the key was parsed and acted on; an empty set would prove nothing.
  • Control against main: identical filter counts, and PropertyNotFoundException on
    MemberName for every row. Property absent before, present after, same array, same
    call. This is the strongest single piece of evidence for Task 7.

Task 6 negative case: connect produced 0 warnings, and the silence is provably correct
rather than accidental — the shipped map's max is 2.28, so 2.26 is in range. Helper
verdicts against the real artifact: 2.26→False, 2.28→False (exact match must not warn),
2.29→True/2.28, 3.0→True/2.28, unparseable→False with HighestScanned untouched.

Tests. All seven of the branch's test files, scoped run, both editions:
pwsh 7 108 passed / 0 failed / 0 skipped; Windows PowerShell 5.1
48 / 0 / 60. Container ok on both. (The 60 skips on 5.1 are the tools/Build-*
suites, which carry a deliberate edition guard.)

One pre-existing failure you will hit locally, which this branch does not cause.
With real specs present, Tests/Build-PfbApiDriftReport.Tests.ps1 fails one assertion on
pwsh 7 — the "nothing vanishes" invariant, on
POST /file-system-replica-links|query|remote_default_exports. This was verified as
pre-existing by exporting the merge base with git archive into a clean tree with the same
29 specs and reproducing the identical 1757-vs-1756 result. Structurally confirmed too:
parsing both capability maps and comparing endpoints with contextScope stripped gives
byte-identical output. Reports/PfbApiDriftReport.json on main is one gap stale and has
been since before this branch — it is #84 PR 1's to fix, and is deliberately untouched here
(see limit 6).

Limits — please read these before reviewing the artifact

Stated plainly rather than buried, because overstating any of them is the specific way
this PR could mislead.

  1. The drift guard gives ZERO CI protection. tools/specs/ is gitignored, and
    cross-platform-tests.yml — the only PR-triggered workflow — never fetches it, so every
    real-artifact assertion skips. Locally "7 passed" and in CI "7 skipped" look identical in
    a summary line. It is a maintainer gate only.
  2. The artifact diff review is the actual verification for contextScope, not the live
    check. Do not overstate the live item.
  3. Task 6's positive case is not lab-reachable. FB-A caps at 2.26, below the 2.29 needed
    to fire the warning. Verified at helper level against the real map, not on the wire.
  4. Task 6 has no ledger row. Hand-connect plus helper probe. Sound, but weaker in kind
    than Task 7's evidence, not merely narrower.
  5. The total_item_count guard is unreachable from this cmdletGet-PfbFleetMember
    has no TotalOnly parameter. Purely defensive, resting on its unit test.
  6. Reports/ drift-report delta is pre-existing on main, established by reverting
    tools/lib/PfbContextRuleTools.ps1 to HEAD and re-running (identical 64/1), and confirmed
    independently. contextScope provably does not reach drift accounting: reports generated
    from the before- and after-maps are byte-identical, contextScope string count 0 in both.
  7. 3 of the 4 curated contextScope entries are not live-checkable — no topology-group
    cmdlets exist on this branch.

Restating limit 2, because it is the one that matters for reviewing this PR: the
Data/PfbCapabilityMap.json diff is the verification for contextScope. There is no CI
gate comparing the committed artifact to what the generator produces (limit 1), and the live
check covers Task 7, not the map. Reading that diff is the review.

Sequencing — this is the first of two consecutive map regenerations

Data/PfbCapabilityMap.json will be regenerated again, immediately, by #84's PR 1
(the #71/#82 schema-walk fix). That is deliberate and agreed in
#84 — not
churn, and not someone undoing this work.

The order is Phase 0 first because its verification is the artifact diff (limit 2), which
only holds against an unmoved base. PR 1's verification is unit fixtures plus a diff
characterised in advance, which survives being re-derived on a moved base.

schemaVersion 1 → 2 is claimed here. PR 1 does not contest it; #83 will increment from
whatever it finds.

Deliberately not in this PR

  • No Reports/ changes, and no claim to fix the 64/1 drift-report red. That belongs to
    PR 1, which owns the whole Reports/ diff.
  • No version bump, no CHANGELOG.md. Maintainer's call.
  • No fix for the CI gap in limit 1 — deferred until after Phase 0.
  • Nothing reads contextScope at runtime yet. Kind-vs-scope validation and scope-aware
    error messages are Phase 1, which is the field's first consumer.
What this unblocks
Phase Work
1 The PfbContext object, connection context state, and the injection path — needs resolved component names to call the cardinality predicate at all
1 Kind-vs-scope validation and scope-aware error messages — the first runtime consumer of contextScope
1 Set-PfbContext pipeline binding — depends on Task 7's top-level MemberName/FleetName, because ValueFromPipelineByPropertyName matches top-level names only
2 allow_errors surfacing and the HTTP 207 branch — will need the same component resolution Task 1 centralised
#38 Topology-group and fleet/realm cmdlets — not blocked on this; measured, those writes succeed with no context_names from the coordinator

juemerson-at-purestorage and others added 15 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/ (dmann000#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 dmann000#25, dmann000#74

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The spec treated contextScope + schemaVersion 2 as standalone. It is not:
issue dmann000#83 also needs schemaVersion 2, and tracking issue dmann000#84 plans both as
a single bump in a single PR (PR 2, behind PR 1 = dmann000#71 + dmann000#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 dmann000#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 dmann000#25, dmann000#83, dmann000#84

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

Decision: Phase 0 ships contextScope and takes schemaVersion 2. #84s
generator work (dmann000#71, dmann000#82, dmann000#83) moves to its own PR, with dmann000#83 incrementing
from whatever it finds rather than being pre-assigned 3.

The test was whether Phase 0 could absorb all of dmann000#84, which would have
preserved the one-bump intent. It cannot, structurally: dmann000#71, dmann000#82 and dmann000#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 -- dmann000#44 is blocked on PR 1,
whose four endpoints are the ones dmann000#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 dmann000#84 so its PR 2 no longer claims contextScope.

Refs dmann000#25, dmann000#83, dmann000#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 dmann000#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 dmann000#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 dmann000#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 dmann000#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 dmann000#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 dmann000#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 dmann000#83 increments from whatever it finds.

Refs dmann000#25, dmann000#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 dmann000#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 dmann000#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 dmann000#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 dmann000#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 dmann000#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 dmann000#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 dmann000#74 exists to close.
@juemerson-at-purestorage
juemerson-at-purestorage changed the base branch from main to integration/capability-map-2026-08 August 5, 2026 03:57
@juemerson-at-purestorage
juemerson-at-purestorage merged commit 9d5e893 into dmann000:integration/capability-map-2026-08 Aug 5, 2026
4 checks passed
juemerson-at-purestorage added a commit that referenced this pull request Aug 13, 2026
Integration: Fusion Phase 0 prerequisites + capability-map schema-walk fixes (#96, #97)
juemerson-at-purestorage added a commit that referenced this pull request Aug 13, 2026
…ility-map workflow (#63)

No CI job has ever regenerated Data/PfbCapabilityMap.json or
Data/PfbResponseShapeMap.json and compared the result against what is
committed, so a generator change committed without regenerating passes
silently. That comparison had to be done by hand for #96 and #97.

Hashes the two maps before the build steps overwrite them, then names the
outcome in the run summary. Deliberately reports rather than fails: a
legitimate diff is this workflow's whole purpose, since a newly published REST
version changes both maps, so a hard failure would red the scheduled run every
time one lands. The value is that drift is now stated instead of inferred from
the PR body.

Also retires the third copy of the Pester invocation. It could not be replaced
by a workflow_call to cross-platform-tests.yml -- a reusable workflow runs on a
fresh runner with a fresh checkout, so it would test the committed Data/ and
Reports/ rather than the ones this job just regenerated, which is the entire
reason the step was inline. A script runs in this job's workspace.

Step ordering verified unchanged: the response-shape map is still built after
the spec fetch and before the drift report, per the load-bearing note already
in this file.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant