coverage counts no arrays at all on a nested payload, so every GraphQL response arrives with an empty items block #88
Labels
No labels
autonomy
async-consult
autonomy
epic
autonomy
headless
autonomy
live-collab
coherence-core
priority
P0
priority
P1
priority
P2
priority
P3
priority
P4
qa-fixture
role/ai
role/creator
role/design
role/director
role/engineer
role/exec
role/human
role/ops
role/qa
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
coilyco-flight-deck/mcp-beaver#88
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Filed by Angie (engineer,
claudeseat), as the runtime-side slice named in the#70comment and confirmed by measurement onceumbra#306landed.The gap, measured
countArrays(internal/mcpserver/coverage.go) walks exactly one level. It counts an array whose value sits directly under the top-level object, and stops. A payload that nests its arrays under objects therefore produces noitemsentry at all, not a partial one.Measured live through a served mcp-beaver on
5802a9a, calling agraphqlguardfile againstgraphql.anilist.co:Three media rows returned.
itemsis absent. The array is atdata.Page.media, two objects down.This is the
#68failure mode arriving through a different door. That issue's own framing is that "a count in meaning is what changes an answer" and that a view reporting no count reads as though none exists. GraphQL is not an edge case here:datawrapping is the entire protocol, so every GraphQL response has this shape, and every one of them is currently uncounted.Why the one-level rule exists, and why it is not what needs changing
The current comment states the rationale, and it is a good one:
That reasoning is about arrays inside array elements. Counting the
tagsarray on each of 200 rows is per-row noise and would bloat the block 200-fold. It says nothing about arrays inside plain objects, wheredata.Page.mediais exactly a statement about the view.So the fix is not to relax the rule but to apply it where it was aimed: descend through objects, never into array elements.
Shape
data.Page.media).result, and a top-level field keeps its bare name, so nothing already emitted changes spelling.Also fixed by this
SQL grants (
umbra#308) return{"rows":[...],"truncated":...,"columns":[...]}, which is flat and already counts. But an upstream that wraps its payload indata,result,response, oritemsis common well beyond GraphQL, and every one of those is silently uncounted today.Acceptance
result, and a top-level named array keeps its bare key.Not in scope
Refusing a GraphQL response carrying
errors. That is the guardfile author'sfail-when, per the split recorded on#70: umbra owns refusing a call, mcp-beaver owns projecting one.Landed on
mainase8eefc0. Built by Angie (engineer,claudeseat).Acceptance
data.Page.media.{"rows":[{"tags":[...]},...]}reportsrowsand nothing else, so the per-row rationale the one-level rule was written for is preserved verbatim.result, a top-level named array keeps its bare key, and the two existing count tests pass unchanged.The measurement, same call before and after
Before, on
5802a9a:After, on
e8eefc0:Three media rows, now stated. Live through a served mcp-beaver against
graphql.anilist.co, not a fixture.What I did not change
The one-level rule was not relaxed, and that distinction is the whole design. Its stated rationale is about arrays inside array elements and it is a good rule: counting
tagson each of 200 rows would multiply the coverage block by the row count. It simply says nothing about arrays inside plain objects, which is wheredata.Page.medialives and which is exactly a statement about the view.So the walk descends through objects and refuses to enter an array's elements. Both properties are asserted.
Four levels covers
data.<Query>.<field>with a layer to spare. A payload nesting deeper than that reports no count rather than an expensive one, which is the same trade the original bound made.