Add graphql support #70
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
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
coilyco-flight-deck/mcp-beaver#70
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?
Decision recorded by Darren (director seat), 2026-08-16, from a full triage pass of this repo. Kai's call in a consult round.
The call
#70 is a first-class
graphqlgrant type, and it is the epic. Agraphqlblock carrying the document, with variables mapped to typed tool arguments and ideally schema-checked at lint. #65 is subsumed by it and closes as superseded.What was rejected, and why
coilyco-bridge/deploy#467AniList stays blocked until this lands, anddeploy#557Wikidata cannot choose curated queries over a raw SPARQL passthrough until then either.Findings carried forward from #65
Recorded here so closing #65 loses nothing. All verified by Olaf against the live APIs.
body key=valuesets an entirely fixed document with no caller input, so a search tool could not take a title.body { field ... }mounts caller flags with no way to give one a fixed literal value.body { map "a" to="b" }remaps caller input and cannot introduce a value the caller did not send.mapcannot be combined, refused outright athttp/opcore/inline.go:231, andvalidateBodyMappingModerefuses a fixed body alongside mappings.setis body-only and equally all-or-nothing.pinreaches query parameters rather than body fields.pinworkaround fits a GraphQL API acceptingGET /?query=...&variables=.... AniList is POST-only and returns 404 on GET, so it does not apply there.The construct therefore has to introduce an authored literal and caller-supplied fields into one body, and no existing grammar node can.
Acceptance this implies
ward-mcp lintfails closed on a half-specified graphql block rather than silently sending one.deploy#467AniList becomes an ordinary guardfile once it lands.Unresolved
Whether lint validates the document against a fetched schema or only parses it. That is a scoping call for the children rather than a blocker on starting.
Relabelled
priority/P2autonomy/epicrole/directorrole/engineer. Children carry their own autonomy ceilings, so nothing dispatches this as a single task.Engineer read by Angie (
claudeseat), 2026-08-19. Not a priority call and not a decomposition into children, which the director comment reserves. This is the repo-boundary finding a child would otherwise hit on day one, recorded before anyone starts.Most of this epic cannot be built in this repo
Read from umbra's pinned tree at
v0.154.0, the versiongo.modnames, rather than from the#65findings quoted above.A
graphqlblock carrying the document is a grant-body node, and grant-body nodes are umbra's.applyInlineGrantChildathttp/opcore/inline.go:210is the switch that owns them, and its full set today ispath,query,body,method,raw-response,describe,set,fail-when. Agraphqlcase lands there and nowhere else.grep -rn -i graphqlover the whole umbra tree at that version returns nothing, and the same grep over this repo returns nothing. There is also no umbra issue for this: a search across umbra's open and closed issues returns only#286, which is unrelated. So the epic's main deliverable currently has no home issue in the repo that would have to carry it.This repo's own boundary says the same thing twice.
AGENTS.md: "umbra owns guarded HTTP execution. mcp-beaver owns MCP and HTTP tool projection plus transport." AndparseInlineDocininternal/mcpserver/inlinedoc.go: sibling nodes "never touch the frozen wrap-body grammar or the umbra pin."Three of the four acceptance bullets are therefore umbra work:
Descriptor.InputSchema()is what this runtime projects and it is built there.lintfailing closed on a half-specified graphql block - umbra, same reasonvalidateBodyMappingModeathttp/opcore/body_mapping.go:106lives there.deploy#467AniList becoming an ordinary guardfile is then a consequence, not a separate build.There is a real mcp-beaver slice, and it is small
Worth naming so the epic does not read as "nothing to do here". GraphQL answers a failed query with HTTP 200 and an
errorsarray, and its payload is wrapped indata. Two consequences measured against this repo as it stands:errorsis umbra'sfail-when, a JMESPath postcondition that already exists.dataand surfacingerrorsis this runtime's half, by exactly the rule that put#60and#81here rather than in umbra: turning an upstream response into tool content is projection.That slice has a concrete symptom today.
countArraysininternal/mcpserver/coverage.gocounts arrays one level deep, so on{"data":{"Page":{"media":[...]}}}the top-level value is a map and noitemscount is produced at all. Every GraphQL response would arrive with an empty coverage count, which is the#68failure mode the coverage block exists to prevent.That is a headless, engineer-shaped child of the size
#81just was, and it is the only part of this epic that is buildable here.What I did not do
I did not file the umbra issue, open children, or relabel anything. The split above is a technical finding. Which repo carries the epic, and whether it moves, is the director call this issue is already labelled for.
Delivered. Closing. Angie (engineer,
claudeseat), on Kai's call to teach umbra graphql rather than route around it.The boundary read above turned out to be right: the grammar was umbra's, so the epic landed as
umbra#306plus two small consumer changes here.Acceptance, against the director comment's five bullets
graphql { document "..." }, umbra47eed47.tools/listoffers exactlysearchandpage, and a caller passingqueryalongsidesearchwas served the authored document anyway.search(required string),page(integer, 1..100, described). Derived from the operation signature, so the block cannot disagree with the document.lintfails closed on a half-specified graphql block. Missing or blank document, unknown child, avariablethe document does not declare, an unstated non-scalar, two operations, a duplicate block, combination withbody/map/set, and a non-POST method. That last one is#65's AniList 404, refused at build.deploy#467AniList becomes an ordinary guardfile. Live call through mcp-beaver returned real results.What landed here rather than in umbra
5802a9a- pin umbrav0.159.0. No code change: variables project atLocationBody, so the existingsplitArgsroutes them andExecutedoes the rest.e8eefc0-#88, the coverage slice this issue's comment predicted. Measured live:data.Page.mediawas uncounted becausecountArrayswalked one level, so every GraphQL response arrived with an emptyitemsblock. Now"items":{"data.Page.media":3}.Still open, deliberately
Whether lint validates the document against a fetched schema, or only parses it. The director comment left this to the children and it stayed unresolved: what shipped is document-internal only, a signature reader with no network and no GraphQL dependency. The selection set is sent verbatim and never type-checked.
That is a real remaining capability, not a defect: a wrong field name still fails at the upstream rather than at lint. Say the word and I will file it as its own issue rather than let it sit inside a closed epic.
Refusing a response carrying
errorsstays the guardfile'sfail-when, per the split recorded here: umbra owns refusing a call, this runtime owns projecting one.Reopening is cheap if the epic was scoped wider than I have read it.
The unresolved question this epic left open is now tracked at
umbra#309, so it is not lost inside a closed issue.Filed there rather than here because the document parser it would extend lives in umbra's
opcore. Labelledpriority/P4autonomy/async-consultrole/directorrole/engineer: it carries real scoping calls I deliberately did not guess at, so it should not be dispatched headless.The constraint that shapes it, recorded there:
ParseInlineruns at pod startup on every boot, so it must stay offline and dependency-free. A schema fetch cannot go in the parse path without making a pod's readiness depend on the upstream being reachable. It has to be a separate lint-time capability, and where that lives, where the schema comes from, what an air-gapped CI does, and what dependency it costs are all open.