graphql grant: a fixed document with caller-supplied variables, which no existing body construct can express #306
Labels
No labels
burndown-2026-06
sunday-sprint
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/umbra#306
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) on Kai's call, as the umbra half ofmcp-beaver#70. That epic is filed on mcp-beaver, but the grammar it asks for is this repo's:applyInlineGrantChildathttp/opcore/inline.go:210owns every grant-body node, and its set today ispath,query,body,method,raw-response,describe,set,fail-when. Kai's call is to teach umbra graphql rather than route around it.The gap
A GraphQL upstream needs one request carrying an authored document the caller cannot influence plus caller-supplied variables. No node here can build that body. Findings carried from
mcp-beaver#65, verified against the live APIs:body key=valuesets a fully fixed body with no caller input, so a search tool could not take a title.body { field ... }mounts caller fields 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 never sent.mapcannot be combined, refused atinline.go:231, andvalidateBodyMappingMode(body_mapping.go:106) refuses a fixed body alongside mappings.setis body-only and equally all-or-nothing.pinreaches query parameters, not body fields.The
pinworkaround fits an API acceptingGET /?query=...&variables=.... AniList is POST-only and 404s on GET, so it does not apply.Shape
DescriptorgainsGraphQL *GraphQLcarryingDocument,Operation, and derivedVariables []Field.The document is the source of truth for the variable set. opcore parses the operation signature and derives each variable's name, type, and requiredness from it, so a
variablenode only decorates one that already exists. Naming an undeclared variable is a build error. This removes the whole class of guardfile where the block and the document disagree.Type mapping is the GraphQL scalars only:
String/IDto string,Intto integer,Floatto number,Booleanto boolean,[X]to array, trailing!to required. A non-scalar named type is a build error unless avariablenode states its type, since an enum and an input object are not distinguishable from the document alone, and guessing is the failure this repo exists to avoid.Variables project into
InputSchemaatLocationBodyandassembleBodynests them undervariables. That is deliberate: it means a consumer pinned to today'ssplitArgspicks them up with no lockstep change.Acceptance
document, emptydocument, an unknown child, avariablenaming something the document does not declare, a non-scalar type left unstated, agraphqlblock combined withbody/set/map, or a method that is not POST.coilyco-bridge/deploy#467AniList becomes an ordinary guardfile once this and a consumer bump land.Scope call
Validation is document-internal only. The parse checks the operation signature and cross-checks the declared variables against it, with no network and no GraphQL dependency. Fetching a remote schema to type-check the selection set is a separate question and explicitly not this issue, matching the open question the
mcp-beaver#70director comment left to the children.Refs
mcp-beaver#70,mcp-beaver#65,coilyco-bridge/deploy#467,coilyco-bridge/deploy#557.Landed on
mainas47eed47. Built by Angie (engineer,claudeseat).Acceptance
graphql { document "..." }sends the document verbatim asquerywith input nested undervariables.queryis dropped rather than forwarded, and only declared variables ride.$search: String!becomes a required string and$ids: [ID!]an array of strings.document, unknown child, avariablethe document does not declare, an unstated non-scalar, two operations in one document, a duplicategraphql, combination withbody/map/set, and a non-POST method.Verified live
Against
graphql.anilist.co, the APIcoilyco-bridge/deploy#467is blocked on:Two design calls worth recording
The document owns the variable set. I had planned for
variablenodes to state name and type, then changed it: the document already carries that in its signature, and two sources of truth for one fact is a guardfile that can disagree with itself. Avariablenode now only decorates something that already exists, and naming a variable the document does not take is a build error. A non-null variable carrying a default is optional, since the server fills it.Variables ride
LocationBody. They are not really body fields, but a consumer that already splits arguments byLocationpicks them up with no lockstep change, andassembleBodyis the single place that nests them undervariables. mcp-beaver needs only a version bump.Scope held
Validation is document-internal, as filed: a signature reader rather than a GraphQL parser, no dependency and no schema fetch. The selection set is sent verbatim and never inspected. Whether lint should type-check it against a fetched schema is still open and still not this issue.
make test,make lint(0 issues), andpre-commit run --all-filesall clean.docs/opcore-inline.mdgained a GraphQL grants section, which needed the existing prose condensed to stay inside the band's line cap.One incidental finding
make lintwas failing on this repo before I touched it, reporting a G101 in/var/folders/.../T/umbra-headless-issues/pkg/credseed/credseed.go. That path does not exist: it is a deleted temporary clone still in golangci-lint's cache.golangci-lint cache cleanclears it. Not a code issue, but it reproduces on a cleanmainand would waste the next agent's time.