feat(fusion): Phase 0 prerequisites - contextScope in the capability map, centralised component resolution, staleness warning - #96
Merged
juemerson-at-purestorage merged 15 commits intoAug 5, 2026
Conversation
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
changed the base branch from
main
to
integration/capability-map-2026-08
August 5, 2026 03:57
juemerson-at-purestorage
merged commit Aug 5, 2026
9d5e893
into
dmann000:integration/capability-map-2026-08
4 checks passed
This was referenced Aug 5, 2026
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Phase 0 of the Fusion
context_nameswork (#25, design spec rev 3 in #72). Prerequisitesonly — 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
contextScopehalf 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).
Resolve-PfbParameterComponent— the single runtime home of the three-step component-resolution contract (Private/Resolve-PfbParameterComponent.ps1,Private/PfbContextConstants.ps1)tools/libconsumes thatPrivate/resolver instead of inlining its own copyGet-PfbSpecContextScope— reads the threex-pure-*remote-execution annotationscontextScopeemitted; capability mapschemaVersion1 → 2contextScopetableConnect-PfbArraywarns once when the array outruns the bundled capability mapMemberName/FleetNameonGet-PfbFleetMemberFive new test files, two modified.
The regenerated artifact
Both distributions sum to 632. The full cross-tab:
A
contextScopeofunknownis a designed degradation, not a gap. It suppressesvalidation 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/workloadread/write asymmetry is intentional.GETisarray-scoped;all four write verbs on the same path are
fleet-scoped. Both sides aredeclaredprovenance — 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 takesarray/default. That is correct: scope describesremote-execution context, not resource family, and fleet membership is array-local. The 7
fleet-scoped entries are the 4
/presets/workloadwrites 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.
PSCustomObjectonly. The generator builds[ordered]@{}in memory, and on anIDictionary.PSObject.Properties.Namereturns the CLR surface (Keys,Count, …) — not the keys — so resolution silently missed every in-memory lookupb5bd095context_names) and anids/ids_or_namesdecoyb5bd095contextScopeis assigned unconditionally per spec iteration whileminVersion/parameters/bodyPropertiesuse a first-sight guard three lines away. The fixture was a single spec version — precisely what cannot demonstrate non-version-gating42417784241778total_item_countsentinel 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 preventdd6041aTask 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
$truereturn 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
catchreturn$truesent the call site throwing outside any
try, and no connection was returned. Given theasymmetry — 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. Thatmutation 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
$defaultPropsundetected; and aFind-Modulesourcegrep 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
6ae04e8and4f07080:own.
Tests/PfbSpecTools.ContextScope.Tests.ps1's syntheticDescribecarried anedition skip, but
Get-PfbSpecContextScopeis a purePSObjectwalk with no pwsh-7dependency — 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 blocknow runs on both editions. The sibling
Describekeeps its guard, which is genuine(the real fb2.28 spec does need
-Depth 64), and now states why. This matters becauseon 5.1 the file reported
0 passedfor that block — indistinguishable from health.IsIncompleteGre/BlocksRemoteExectestkey 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/unknownsilently. Now pinned, and the mutation fails on both editions.tools/README.md, the tracked contract doc for the map's shape, was stale. It didnot mention
contextScopeorschemaVersion 2, and its parameter-component resolutionprose 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 #74exists 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_countsentinel guard neutralised. The three load-bearing behavioursare 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 atfeat/fusion-context-phase-0@33fbd32, 185–1217ms.member/fleetpreserved.-MemberName 'FB-A'→ exactly 1 row, decorated, 208ms. Never → 0.HTTP 400 "Fleet member does not exist."A specific rejection ispositive evidence the key was parsed and acted on; an empty set would prove nothing.
main: identical filter counts, andPropertyNotFoundExceptiononMemberNamefor every row. Property absent before, present after, same array, samecall. 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 withHighestScanneduntouched.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 okon both. (The 60 skips on 5.1 are thetools/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.ps1fails one assertion onpwsh 7 — the "nothing vanishes" invariant, on
POST /file-system-replica-links|query|remote_default_exports. This was verified aspre-existing by exporting the merge base with
git archiveinto a clean tree with the same29 specs and reproducing the identical 1757-vs-1756 result. Structurally confirmed too:
parsing both capability maps and comparing
endpointswithcontextScopestripped givesbyte-identical output.
Reports/PfbApiDriftReport.jsononmainis one gap stale and hasbeen 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.
tools/specs/is gitignored, andcross-platform-tests.yml— the only PR-triggered workflow — never fetches it, so everyreal-artifact assertion skips. Locally "7 passed" and in CI "7 skipped" look identical in
a summary line. It is a maintainer gate only.
contextScope, not the livecheck. Do not overstate the live item.
to fire the warning. Verified at helper level against the real map, not on the wire.
than Task 7's evidence, not merely narrower.
total_item_countguard is unreachable from this cmdlet —Get-PfbFleetMemberhas no
TotalOnlyparameter. Purely defensive, resting on its unit test.Reports/drift-report delta is pre-existing onmain, established by revertingtools/lib/PfbContextRuleTools.ps1to HEAD and re-running (identical 64/1), and confirmedindependently.
contextScopeprovably does not reach drift accounting: reports generatedfrom the before- and after-maps are byte-identical,
contextScopestring count 0 in both.contextScopeentries are not live-checkable — no topology-groupcmdlets exist on this branch.
Restating limit 2, because it is the one that matters for reviewing this PR: the
Data/PfbCapabilityMap.jsondiff is the verification forcontextScope. There is no CIgate 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.jsonwill 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.
schemaVersion1 → 2 is claimed here. PR 1 does not contest it; #83 will increment fromwhatever it finds.
Deliberately not in this PR
Reports/changes, and no claim to fix the 64/1 drift-report red. That belongs toPR 1, which owns the whole
Reports/diff.CHANGELOG.md. Maintainer's call.contextScopeat runtime yet. Kind-vs-scope validation and scope-awareerror messages are Phase 1, which is the field's first consumer.
What this unblocks
PfbContextobject, connection context state, and the injection path — needs resolved component names to call the cardinality predicate at allcontextScopeSet-PfbContextpipeline binding — depends on Task 7's top-levelMemberName/FleetName, becauseValueFromPipelineByPropertyNamematches top-level names onlyallow_errorssurfacing and the HTTP 207 branch — will need the same component resolution Task 1 centralisedcontext_namesfrom the coordinator