Skip to content

refactor(core): stop lossy GfError stringification at stage boundaries #1018

Description

@DecisionNerd

Found during the architecture audit (sub-agent review).

Finding (MEDIUM)

Stages define structured errors (ParseError+kind in ast; BindError{kind,span} ir/binder.rs:111-118; LoweringError{UnknownFunction|UnsupportedExpr|UnboundVar|InvalidType} rel/expr.rs:74-95; AlgorithmError in exec) — but every public entry flattens to GfError strings:

  • rel → GfError::Plan(e.to_string()) at every public fn (rel/lowerer.rs:520,550,568,586,606,649); even LoweringError::InvalidType (a validation rejection) surfaces as GF_PLAN
  • exec round-trips GfErrorDataFusionError: to_df_err (exec:457-458) wraps into DataFusionError::External, then back via e.to_string() (exec:5911,5678) — double-stringification
  • Entry-point divergence is tracked separately in the cypher-pure-parser issue

Why it matters

Error taxonomy at the public API is accidental (string content decides the code), and diagnostics lose span/kind information the stages already computed.

Acceptance criteria

  • Structured stage errors preserved to the facade boundary (GfError variants carry kind/spans where stages provide them)
  • LoweringError::InvalidType no longer surfaces as GF_PLAN (test)
  • GfError↔DataFusionError round-trip no longer double-stringifies
  • Error-code matrix test enumerating entry point x failure kind

Architecture review decision

Treat this as a parent public-error-contract issue. Define the conversion matrix before changing individual call sites.

Additional acceptance criteria:

  • One matrix covers stage error kind/span -> GfError -> Python exception -> Node error code
  • Existing stable public codes are preserved unless a versioned release decision explicitly changes them
  • DataFusion wrapping preserves a downcastable structured source when it originated as GfError
  • refactor(cypher): make graphforge-cypher a pure parser crate #1007 consumes this mapping rather than defining a second binder-error policy

The release impact is intentionally left undecided until public-code compatibility is proven.

Metadata

Metadata

Assignees

No one assigned

    Labels

    coreCore source code changes

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions