Skip to content

refactor(sdk): move GraphQL error policy out of the transport seam [#354 Rung 3]#394

Draft
Danielmoraisg wants to merge 3 commits into
devfrom
refactor/graphql-seam-rung3-354
Draft

refactor(sdk): move GraphQL error policy out of the transport seam [#354 Rung 3]#394
Danielmoraisg wants to merge 3 commits into
devfrom
refactor/graphql-seam-rung3-354

Conversation

@Danielmoraisg

@Danielmoraisg Danielmoraisg commented Jul 10, 2026

Copy link
Copy Markdown
Collaborator

What

Rung 3 of #354: move GraphQL error policy out of the transport seam.

Rung 2 collapsed the seam to one protocol and one result type. This rung takes the two decisions the seam still baked in (raise-on-error and how to format the error) out of the transport layer and into the pure core, and deletes the [code=...] [correlation_id=...] string envelope round-trip.

  • Add PipefyGraphQLError (owned, carries the raw structured errors list) and the pure data_or_raise(GraphQLResult) -> dict.
  • execute_query is now data_or_raise over the execute primitive and raises PipefyGraphQLError for every endpoint. No gql transport type leaks to services, CLI, or MCP.
  • Drop the per-endpoint on_graphql_error formatter and delete internal_api_errors.py.
  • Delete the [code=...] [correlation_id=...] envelope and its three parsers in the MCP tool layer. Codes and correlation ids now come off the structured errors for both endpoints (extract_graphql_correlation_id reads errors[].extensions, mirroring extract_graphql_error_codes). This removes a serialize-then-reparse round-trip: the envelope only existed to encode codes into a string that the tool layer regexed back out.
  • Migrate the except TransportQueryError sites (SDK services + CLI) to except PipefyGraphQLError, and unify portal_service's two permission mappers into one structured mapper.
  • Export PipefyGraphQLError from pipefy_sdk.

Why the seam keeps two methods

The protocol still exposes execute (the primitive) and execute_query (the raise-on-error convenience). Collapsing to a single execute was considered and rejected: with no static type checker in this repo, (await ex.execute(...)).data would silently drop GraphQL errors, and ~140 of ~142 call sites want raise-on-error. Keeping execute_query as the ergonomic default means those callers cannot forget to raise, and it is not a second transport capability (it does no I/O; it is data_or_raise over execute).

Behavior

  • The ~152 execute_query callers are unchanged at the call site. On GraphQL errors they now catch PipefyGraphQLError instead of a gql TransportQueryError / a ValueError envelope.
  • Debug output is preserved: MCP tools still surface error codes and correlation ids under debug=True, now read from the structured errors.
  • Genuine transport failures (non-2xx, timeouts) still propagate as gql TransportError subclasses, unchanged.
  • One deliberate CLI change: a non-permission internal-API GraphQL error now exits 1 (with the clean message (code) format) instead of 2. Exit 2 is the usage-error code, so a server-side GraphQL failure exiting 2 was wrong; this aligns internal-API CLI errors with public-API ones. Permission errors are unchanged. No CLI test pinned the old exit 2 (every exit-2 assertion is a client-side validation path that never reaches the API).

Testing

  • Full suite green: uv run pytest packages -m "not integration" -> 3068 passed, 5 skipped. Ruff check + format clean.
  • Added data_or_raise / PipefyGraphQLError unit tests; converted the error-simulation tests across SDK, MCP, and CLI to raise PipefyGraphQLError with structured errors.

Stacking

Stacked on #382 (Rung 2); this PR targets that branch so its diff is only the Rung 3 delta. It retargets to dev automatically once #382 merges. Tracked under #354.

…esult type

Collapse partial-success handling from a second protocol and a partial-specific
verb into the default shape of one primitive (issue #354, Rung 2).

- Replace PartialQueryResult with GraphQLResult and delete PartialGraphQLExecutor.
- GraphQLExecutor now exposes execute (the primitive that returns data and errors
  together, deciding nothing about what the errors mean) and execute_query (the
  raise-on-error convenience reimplemented over it). Transport and network
  failures still raise.
- Preserve _on_graphql_error: execute_query applies the internal-API envelope as
  a ValueError, while formatter-less endpoints re-raise a TransportQueryError
  carrying the structured errors the MCP tool layer reads.
- ObservabilityService depends on the one protocol and calls execute, handing
  errors to its own classifier.

Refs #354
Rung 3 of #354. The seam now returns owned types and raises one owned exception;
the [code=...] string envelope round-trip is gone.

- Add PipefyGraphQLError (owned, carries the structured errors list) and the pure
  data_or_raise. execute_query is now data_or_raise over the execute primitive and
  raises PipefyGraphQLError for every endpoint (no gql type leaks to callers).
- Drop the per-endpoint on_graphql_error formatter and delete internal_api_errors.
- Delete the [code=...] [correlation_id=...] envelope and its three parsers in the
  MCP tool layer; codes and correlation ids come off the structured errors for both
  endpoints (extract_graphql_correlation_id reads errors[].extensions).
- Migrate the except TransportQueryError sites (SDK services and CLI) to
  PipefyGraphQLError; unify the two portal permission mappers into one.
- Export PipefyGraphQLError from pipefy_sdk.

The protocol keeps execute_query as the raise-on-error default so the ~152 callers
are unchanged and cannot silently drop errors; execute stays for partial-success
callers.

Refs #354
- Guard _graphql_error_message against a non-dict error item, matching the
  defensive isinstance checks in the MCP tool-layer extractors; add a regression
  test.
- Convert a stale AI-automation test to raise a structured PipefyGraphQLError
  instead of the deleted [code=...] envelope string (it had passed vacuously).
- Drop the removed marker-stripping line from enrich_behavior_error's docstring;
  rename four tests whose names referenced the deleted envelope and parsers.

Refs #354
@Danielmoraisg Danielmoraisg self-assigned this Jul 13, 2026
Base automatically changed from refactor/unify-graphql-seam-354 to dev July 14, 2026 18:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant