Skip to content

docs: add specification for the error catalogue in the SDK - #1266

Draft
ogenstad wants to merge 1 commit into
infrahub-developfrom
pog-error-catalogue-IFC-3034
Draft

docs: add specification for the error catalogue in the SDK#1266
ogenstad wants to merge 1 commit into
infrahub-developfrom
pog-error-catalogue-IFC-3034

Conversation

@ogenstad

@ogenstad ogenstad commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Specification only — no behaviour change. Adds dev/specs/ifc-3034-error-catalogue/ covering how the SDK consumes Infrahub's GraphQL error catalogue, so that ordinary operations raise the specific error for the failure. GraphQLError remains the fallback for failures the catalogue does not cover and stays the common base class, so existing except GraphQLError code keeps working.

Ref: IFC-3034. Related: IFC-2279 (spike), INFP-468 (backend catalogue), GitHub #7498 (out of scope).

Decisions settled while drafting

  • A new ApiError base above both AuthenticationError and GraphQLError. Authentication failures reach consumers from the REST path as well as GraphQL, so they cannot simply be re-rooted under GraphQLError. Verified that a 401/403 on a GraphQL call is already handled as an httpx.HTTPStatusError and raises AuthenticationError before the body is parsed for GraphQL errors — so except GraphQLError never caught auth failures, and no dual inheritance is needed to preserve compatibility.
  • .code is a catalogue string or None. The /api/... envelope's extensions.code is an integer mirroring the HTTP status, a different thing with a different type; it is not surfaced through .code. The catalogue is GraphQL-only today.
  • Generated classes derive their parent from the declared HTTP status — 401/403 under the authentication branch, everything else under GraphQLError — rather than a hand-maintained per-code mapping.
  • Infrahub generates the bindings into this repo as its python_sdk submodule, matching how protocols.py and the generated schema models already arrive. No copy of the catalogue schema is vendored here, so there is one freshness invariant instead of two, policed by extending Infrahub's existing validate-generated check. No release-time gate is added on either side.
  • Query text is dropped from the message for catalogued errors only; uncatalogued errors keep today's message verbatim.
  • NodeNotFoundError, BranchNotFoundError and SchemaNotFoundError are unified with their catalogue counterparts and re-rooted under GraphQLError, accepting that except GraphQLError now also catches client-side lookup misses.

Findings from the code survey worth a reviewer's eye

These are in the spec's Edge Cases section as specific hazards, not hypotheticals:

  • An ordered isinstance ladder gets shadowed. infrahub_sdk/ctl/utils.py:58-72 tests GraphQLError at line 67 before (SchemaNotFoundError, NodeNotFoundError, ...) at line 70. Re-rooting those classes makes the later branch unreachable, silently changing CLI output for exactly the errors this feature makes specific. FR-018 requires the correction.
  • A renderer with no server errors to render. That same GraphQLError branch renders exc.errors, a list of server error dicts. A unified NodeNotFoundError raised purely client-side has no server response behind it, so the list is empty.
  • identifier carries two types. The existing client-side NodeNotFoundError has identifier as a mapping of filters; the catalogue payload has it as a single string. FR-016 mandates the unification; the reconciliation mechanism is left to the plan.
  • Eight more, including NodeInvalidError silently inheriting the re-rooting, a pre-existing call site passing a string where GraphQLError expects a list of error dicts, UNDEFINED_ERROR being a real code rather than the absence of one, and GraphQL data errors arriving as HTTP 200 while auth failures arrive as real 401/403 on a separate code path.

Scope

Six prioritised user stories, 28 functional requirements. FR-025 to FR-027 land in the Infrahub repository (generation plus the extended drift check) and are tagged as such; everything else lands here.

Checks

rumdl clean across 131 files; Vale flags nothing in the new files. Requirements checklist at dev/specs/ifc-3034-error-catalogue/checklists/requirements.md passes 16/16 with no [NEEDS CLARIFICATION] markers remaining.


Summary by cubic

Adds a specification describing how the SDK will consume Infrahub’s GraphQL error catalogue so ordinary operations raise specific errors. Documentation only; no behavior change.

  • Key decisions to validate

    • Introduces ApiError above AuthenticationError and GraphQLError to unify server-reported failures across transports.
    • Defines code as a catalogue string or None; the REST integer code is never surfaced as code.
    • Derives generated exception parents from declared HTTP status (401/403 under authentication; others under GraphQLError).
    • Has Infrahub generate bindings into this repo’s submodule; no vendored schema; extended validate-generated check enforces freshness.
    • Drops query text from messages for catalogued errors only; uncatalogued errors keep today’s message.
    • Unifies NodeNotFoundError, BranchNotFoundError, and SchemaNotFoundError with catalogue counterparts under GraphQLError.
  • Reviewer focus

    • Compatibility: except GraphQLError will also catch client-side lookup misses; spec calls out ordered isinstance ladders that must be reordered.
    • Transport split: catalogue is GraphQL-only; REST keeps its legacy envelope and surfaces no catalogue code.
    • Cross-version behavior: unknown codes/fields fall back without parse failures; behavior against pre-catalogue servers is preserved.
    • Ownership: FR-025–FR-027 land in the Infrahub repo (generation and validation); all other requirements land here.
    • Linked issue: IFC-3034 requirements are mapped to functional requirements and success criteria in the spec.

Written for commit 6597717. Summary will update on new commits.

Review in cubic

Specifies how the SDK consumes Infrahub's GraphQL error catalogue so that
ordinary operations raise the specific error for the failure, with
GraphQLError remaining the fallback and the common base class.

Key decisions settled while drafting:

- A new ApiError base sits above both AuthenticationError and GraphQLError,
  since authentication failures reach consumers from the REST path as well as
  GraphQL. Its code attribute is a catalogue string or None; the REST
  envelope's integer code is not surfaced through it.
- Generated exception classes derive their parent from the code's declared
  HTTP status (401/403 under the authentication branch, everything else under
  GraphQLError) rather than a hand-maintained mapping.
- Infrahub generates the bindings into this repo as its python_sdk submodule,
  matching how protocols.py and the generated schema models already arrive.
  No copy of the catalogue schema is vendored here, so there is one freshness
  invariant instead of two, policed by extending Infrahub's existing
  validate-generated check. No release-time gate is added.
- The query text is dropped from the message for catalogued errors only;
  uncatalogued errors keep today's message verbatim.
- NodeNotFoundError, BranchNotFoundError and SchemaNotFoundError are unified
  with their catalogue counterparts and re-rooted under GraphQLError,
  accepting that except GraphQLError now also catches client-side lookup
  misses.

Ref: IFC-3034
@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying infrahub-sdk-python with  Cloudflare Pages  Cloudflare Pages

Latest commit: 6597717
Status: ✅  Deploy successful!
Preview URL: https://37785af3.infrahub-sdk-python.pages.dev
Branch Preview URL: https://pog-error-catalogue-ifc-3034.infrahub-sdk-python.pages.dev

View logs

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 2 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="dev/specs/ifc-3034-error-catalogue/spec.md">

<violation number="1" location="dev/specs/ifc-3034-error-catalogue/spec.md:195">
P2: The unified `NodeNotFoundError` has no compatible `identifier` contract. Define how client-side mappings and catalogue strings are represented, and preserve existing readers while exposing the server identifier.</violation>
</file>

<file name="dev/specs/ifc-3034-error-catalogue/checklists/requirements.md">

<violation number="1" location="dev/specs/ifc-3034-error-catalogue/checklists/requirements.md:52">
P2: The multi-error response contract is still unresolved. Specify a deterministic precedence rule for selecting the raised class while retaining the complete error list.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

`identifier` as a mapping of filters, while the catalogue payload carries `identifier` as a single
string. Unifying the class puts two types and two meanings behind one attribute name. The spec
requires the unification (see FR-016); how the attribute is reconciled without breaking existing
readers is a design decision for the plan.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The unified NodeNotFoundError has no compatible identifier contract. Define how client-side mappings and catalogue strings are represented, and preserve existing readers while exposing the server identifier.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At dev/specs/ifc-3034-error-catalogue/spec.md, line 195:

<comment>The unified `NodeNotFoundError` has no compatible `identifier` contract. Define how client-side mappings and catalogue strings are represented, and preserve existing readers while exposing the server identifier.</comment>

<file context>
@@ -0,0 +1,367 @@
+  `identifier` as a mapping of filters, while the catalogue payload carries `identifier` as a single
+  string. Unifying the class puts two types and two meanings behind one attribute name. The spec
+  requires the unification (see FR-016); how the attribute is reconciled without breaking existing
+  readers is a design decision for the plan.
+- **A subclass inherits the re-rooting.** `NodeInvalidError` subclasses `NodeNotFoundError`, so it
+  silently becomes a `GraphQLError` too. Intended, but it must be asserted rather than assumed.
</file context>

- The reconciliation of the `identifier` attribute on the unified `NodeNotFoundError`, where the
client-side and catalogue meanings differ in type. FR-016 requires the unification; the spec records
the conflict as an edge case and leaves the mechanism to the plan.
- The rule for which error in a multi-error response selects the raised class. FR-013 requires the

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The multi-error response contract is still unresolved. Specify a deterministic precedence rule for selecting the raised class while retaining the complete error list.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At dev/specs/ifc-3034-error-catalogue/checklists/requirements.md, line 52:

<comment>The multi-error response contract is still unresolved. Specify a deterministic precedence rule for selecting the raised class while retaining the complete error list.</comment>

<file context>
@@ -0,0 +1,53 @@
+- The reconciliation of the `identifier` attribute on the unified `NodeNotFoundError`, where the
+  client-side and catalogue meanings differ in type. FR-016 requires the unification; the spec records
+  the conflict as an edge case and leaves the mechanism to the plan.
+- The rule for which error in a multi-error response selects the raised class. FR-013 requires the
+  rule to be explicit and documented; it does not pick one.
</file context>

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