Design spec rev 3: cardinality is not the HTTP verb (stacked on #22) - #72
Conversation
Rev 2's central claim -- that cardinality follows the HTTP verb, every GET being multi-context-capable -- is false. Live testing against a Fusion fleet disproved it for four fleet-scoped endpoints, and the replacement rule is data-driven from the capability map: An endpoint is multi-context-capable if and only if its context_names parameter resolves to component Context_names_get AND the endpoint also declares allow_errors. Verified 9 of 9 against a remote fleet member, and reproduced independently from the specs by a second agent implementing the predicate. Also closes gaps rev 2 left open: - allow_errors is specified. Rev 2 did not mention it, though it is one of the two parameters this feature exists to inject. - context_names is a family of context kinds -- array, fleet, topology group, and fan-out via the undocumented .arrays suffix -- not an array name. - HTTP 207 and the errors response array are specified as a response-layer requirement. - A cross-array authorization precondition (authorization_model) is specified. - The capability-map staleness question is decided, with the dissent recorded. - Phasing is corrected: rev 2 put Invoke-PfbInContext in Phase 2 while relying on it in Phase 1 as the escape hatch from the Phase 1 hard throw. Signal counts are version-stamped throughout. They move between 2.27 and 2.28 and single-version figures read as stable when they are not. Appendix A records the empirical basis, including that GET /topology-groups/arrays cannot be probed bare -- its own parameter validation returns code 24 before the context check is reached. Appendix B tracks the spec defects so each workaround can be retired with evidence; GET /audits is already fixed at 2.28. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The predicate shipped in dmann000#73 without its input producer, so the prerequisite is now concrete rather than anticipated. Records that the refactor belongs in the implementation PR's first commit rather than as separate work, since only the resolution step moves and the surrounding function stays in tools/. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Pushed one more commit: prerequisite 3 now points at #74. Context — #73 merged after this PR was opened, and it shipped Nothing is broken — the predicate is inert, with no runtime callers — but it means the implementation PR has to do the This PR is not blocked by #74. It's docs-only, and it's what identifies the prerequisite in the first place. Reviewable as-is. |
…ive testing Rev 3 characterized fleet-scoped endpoints from GET evidence alone. Live testing of POST/PUT/DELETE /presets/workload on FB-A (2026-08-02, dynamic-model admin, full create -> PUT -> delete round trip) shows mutations differ in the row that matters most. Omitting context_names on a fleet-scoped endpoint does not resolve to a local view -- it fails (code 13 on POST, code 6 on PUT/DELETE). The local-context short-circuit is read-only. A member array name is rejected on every verb, so only the bare fleet name is ever legal, confirming for writes what rev 3 had established for reads. And a no-context GET is list-only: unfiltered returns the object, but ?names= returns code 6, so name resolution is in the mutation case regardless of verb. This is not prospective. No cmdlet in Public/Presets/ has ever sent context_names, so five of six preset operations are non-functional today. Context injection is what makes those cmdlets work at all rather than an enhancement to them. Also corrects section 1. "Every request funnels through Invoke-PfbApiRequest" was stated as settled in rev 2 and has five counterexamples: three connection lifecycle cmdlets that are legitimately outside the choke point, plus two write cmdlets that merely bypassed it. dmann000#76 closed one; dmann000#77 is the last, which makes it a prerequisite for this design rather than a parallel cleanup. Adds: - "Fleet-scoped mutations have no usable default" with the measured verb matrix - a read-only caveat on the local-context short-circuit - section 11: code 20 masks every other finding as a static-model admin, which already produced one confident wrong conclusion - Open Question 7: send-and-fail vs client-side throw vs default-to-fleet - Appendix A: the 2026-08-02 evidence block - Appendix B: the "array in the fleet or the fleet itself" description defect, and the absence of any fleet-scoped/array-scoped marker in the spec - two testing-plan cases, and a dynamic-admin precondition on live verification Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two sections already promised client-side kind-vs-scope validation without saying where scope comes from, and the new fleet-scoped-mutation guidance needs the same input. It is in no spec field, so the module has to carry it. Checked at fb2.28: operation tags are resource groupings and do not separate fleet-scoped from array-scoped endpoints. Cardinality is not a proxy either -- fleet scope implies size-1, but every array-scoped mutation is size-1 too, so the implication does not run backwards. Curation is affordable. Context-capable endpoints under fleet-level resource families: Presets 5, Topology Groups 8, both live-confirmed; Realms 3, untested and marked unknown; Fleets and Realm Connections take no context at all. Thirteen confirmed entries across two families out of 376 endpoints recording context_names -- fleet-scoped endpoints are not scattered, they are exactly the resource types living in the fleet database. Proposes a contextScope field per endpoint in the capability map, defaulting to array, which is the fail-safe direction: mis-marking a fleet-scoped endpoint costs guidance and leaves current behavior, while the reverse throws on a call that would have worked. Three consumers: data-driven kind-vs-scope validation, error and pre-call messages that name the requirement and the cmdlet that satisfies it, and generated .NOTES help so the requirement is discoverable before the failure. The failures this replaces are code 13, code 42, and a code 6 that actively misleads by reporting a preset that does exist as missing. This subsumes open question 3, whose annotation half is the same mechanism, and unblocks open question 7: throw-client-side is only attractive if the throw can be specific, and scope metadata is what makes it specific. Default-to-fleet stays open, blocked on discovering the fleet name without an extra call. Lands in Phase 1, since kind-vs-scope validation cannot be implemented without it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Section 12 as committed in 337dcb3 claimed there is no scope signal in the spec and proposed a fully hand-curated table. That claim was wrong. It was based on checking tags and cardinality and stopping there, without looking at the vendor extensions. fb2.28 carries three x-pure-* extensions, all new or newly expanded at that version: x-pure-remote-execution-context-domains-override 5 endpoints x-pure-block-remote-execution 266 endpoints (2 at 2.24) x-pure-incomplete-gre 28 endpoints The override declares scope directly, and agrees with the 2026-08-02 live testing on every one of the five preset operations including the read/write asymmetry: GET is ARRAY|FLEET, the four mutations are FLEET. Two independent derivations of the same fact. It covers only those five, so curation does not disappear -- but x-pure-incomplete-gre explains the gap and shrinks it. That flag marks the 28 operations whose remote-execution annotation upstream considers unfinished, and it contains every endpoint this design has had to establish by live testing: all four fleet-scoped GETs from section 8, all five preset operations, GET /realms, and GET /workloads/tags. It is upstream's machine-readable version of the defect Appendix B tracks as open. Curation drops from thirteen hand-maintained entries to eight, each justified against a declared flag, with an exit condition as the override is filled in. Adds an encoding subsection: Build-PfbCapabilityMap.ps1 reads none of these today and the map is schemaVersion 1, so this needs the generator to read the extensions, an additive contextScope field, a schema bump to 2, the curated table kept at build time rather than in Private/, and a drift test that retires curated entries superseded by an override. Rides the existing capability-map workflow. Ordering consequence: the generator work moves to the front of Phase 1. Also records two further spec defects: block-remote-execution contradicts context_names on 11 endpoints (all inside the flagged 28), and info.x-pure-description-ref points at an introduction markdown that ships in neither the JSON nor the public Redoc rendering. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The document had accumulated several rounds of live-testing findings patched in inline. Design sections carried the archaeology of how each rule was established -- dates, who found what and in what order, raw per-version counts, and "what changed since rev N" corrections -- so a reader wanting the design had to wade through the evidence trail to reach it. Reorganisation only. No design decision, recommendation, or open question changes substance, and no section numbering moves: sections 1-12, Open Questions 1-7, and Appendices A and B keep their identities, so every existing cross-reference still resolves. The body now states each rule, its rationale, and its consequences in the present tense, with a compact pointer to the evidence rather than the discovery story. Three appendices absorb what moved out: - Appendix C (new) -- spec and capability-map figures. The per-version signal populations pulled out of section 8's reliability table, the unknown-207 endpoint lists, the four component-flip endpoints, the vendor-extension occurrence counts from section 12, and the spec-analysis and capability-map paragraphs relocated from Appendix A. Appendix A is now purely live-array measurement; Appendix C is purely static counts. - Appendix D (new) -- live-testing preconditions. The self-context trap, the code-20 masking anecdote, the /topology-groups/arrays code-24 probing caveat, and the hand-assembled-request artifact behind ordering requirement 1. - Appendix E (new) -- revision history. The former "What changed since rev 2" section from the top of the document, plus the rev-correction notes that were sitting inside sections 1, 7, Ergonomics, Phasing and Alternatives, and a table of the dated decisions and consults. Nothing is dropped: every error code, endpoint, figure, caveat and open question is still present. Verified by diffing the sets of error codes, endpoint paths, numeric figures, vendor extensions, cmdlet names, issue numbers and dates between the two revisions -- all empty. Two consolidations. The GET /audits 2.28 fix was stated three times; the full statement now lives only in the Appendix B row it belongs to. The 113-endpoint silent-accept audit figures were stated in both section 4 and Appendix A; section 4 now states the behavior and points at Appendix A for the proportion. Two small editorial fixes: section 5's heading said "two non-obvious requirements" over a list of three, and section 12 opened by asserting scope "cannot come from the API" immediately before the subsection showing that the 2.28 spec declares it -- residue of the rev 3 correction. Both corrected. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Rev 3 as first drafted modelled the .arrays suffix as a FanOut boolean on each context entry, and offered Get-PfbTopologyGroupMember | Set-PfbContext as reaching "the same outcome as <group>.arrays". Both are wrong. The field becomes an enum-valued Form (Object | AllArrays). A boolean can express exactly one alternative to the default, and the suffix vocabulary is already known to be open -- an all-sub-groups equivalent and realm-as-context would each need their own flag, and two booleans on one entry can encode a meaningless both-set state. Retrofitting the enum later is a breaking change to a published parameter, since -AllArrays and -Form cannot coexist cleanly. "Fan-out" is retired as a name for .arrays and reserved for a possible future client-side serial loop over N arrays, which differs in request count, failure semantics and mutability. The switch is -AllArrays. A terminology note in the Background separates the three senses; the non-goal already used the reserved one. The member-pipeline idiom is withdrawn as the whole-group form. A topology group's members may themselves be groups, so the pipeline yields group names where array names are required; .arrays is transitive over the sub-tree and is re-resolved server-side per request rather than snapshotting membership. The cmdlet stays correct for scoping to specific members, which is a different request. -AllArrays resolves membership before storing the context, via GET /topology-groups/arrays (2.26) or GET /fleets (2.17) -- an asymmetry forced by there being no /fleets/arrays. This is a bounded exception to the no-hidden-network-calls preference and settles Open Question 2: the rejected alternative was inferring Kind from topology on every context set, unbidden; this confirms a name the caller explicitly supplied, once. Without it a mistyped group name is stored and then silently misdirects every later call. Adds an ownership boundary under Phasing. Topology-group object management stays with issue dmann000#38; membership resolution is this design's; and dmann000#38 carries a stated contract that Get-PfbTopologyGroup emit a top-level Name that binds by property name -- without it the documented pipeline silently no-ops in exactly the way Ergonomics describes for Get-PfbFleetMember. All seven topology-group endpoints declare context_names and none declares allow_errors, so they are uniformly single-context under section 8 and need nothing further. Section 1 records Set-PfbWorkloadTag as closed pending merge (dmann000#77 / PR dmann000#81), and corrects its open question: /workloads/tags/batch is array-scoped, the exact inverse of /presets/workload, so it does not repeat the fleet-scoped failure -- but it declares context_names at 2.23 and still needs injection. Appendix A gains the CLI-versus-REST parity table for .arrays across seven cases, Appendix B's .arrays row records that the CLI man page documents the form the OpenAPI spec omits, and Appendix E carries the revision record. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Closing this as superseded rather than merging it. The work it was written for has shipped: Phase 0 in #98 and Phase 1 in #109, both on Rev 3's substantive conclusion was not lost. The component-based cardinality rule replacing the "every GET is multi-context" rule was implemented and merged in #73, and Phase 1's gates enforce it. The one later correction worth recording here: the rule is keyed on the value count, not on the parameter's form, so a single-element multi-value context behaves as size-1 — which is why A rev 4 exists locally that carries the |
Stacked on #22 — base is
design/fusion-context, notmain, so the diff here is exactly rev 2 → rev 3. Merging this updates #22; #22 still lands onmainon your call.Why
Rev 2's cardinality rule — every
GETis multi-context-capable, mutations are size-1 — is false. Live testing against a Fusion fleet found four fleet-scopedGETs that reject a multi-value context outright.Replacement rule, now data-driven from the capability map:
Both facts are already recorded per endpoint, so this is a map lookup — no new derivation, no new tooling.
Evidence
Against FB-A (Purity//FB 4.8.2, REST 2.26), fleet coordinator of
cc-test-fleet:code 15fires before the cross-array authorization gate that producescode 20, so it is structural to the endpoint, not a permission artifact — and it is independent of name validity. These four are fleet-scoped: the only meaningful context is the fleet itself, so multi-value there is not merely restricted, it is meaningless. Their spec entries referenceContext_names_getin error, which is confirmed upstream as incomplete remote-execution support (IR-268610, in review, fixes in flight against both the spec and the server) and still present at 2.28.Re-verified independently against a remote fleet member — 9 of 9 endpoints match the rule. Probing the local array proves nothing, because the middleware short-circuits a local context before validating scope. The figures were also reproduced from the specs by a second agent implementing the predicate.
Other gaps rev 2 left open
allow_errorsis specified. Rev 2 did not mention it, though it is one of the two parameters this feature exists to inject.context_namesis a family of context kinds — array, fleet, topology group, and fan-out via the undocumented.arrayssuffix — not an array name. Rev 2 treated it throughout as "a specific fleet member."errorsresponse array are specified as a response-layer requirement.Invoke-PfbApiRequesthas no branch forerrorstoday.authorization_model: staticvsdynamic) is specified.Invoke-PfbInContextin Phase 2 while relying on it in Phase 1 as the documented escape hatch from the Phase 1 hard throw.Worth your attention specifically
tools/, which is not shipped with the module, so wiring the cardinality check before moving it intoPrivate/would silently force a second copy of the rule.GETis treated as capable). Rationale: a wrong "capable" yields a readable wire-400, a wrong "size-1" locally blocks a call that would have worked. It has one live instance,GET /snmp-managers/test.GET /auditsis already fixed at 2.28.Docs only — no code, no version bump, no CHANGELOG.
🤖 Generated with Claude Code