Error ownership
This page is the contributor contract for Algeon errors: which layer owns a
failure, how it is created and converted, and which facts each surface may
carry. Applications that only read errors start from
Handling Errors, which covers the public
identity, retry steps and Flight SQL statuses. The canonical construction rules
live in the owner rustdoc of crates/algeon-query-planning/src/error.rs and
crates/algeon-datafusion/src/error.rs.
Ownership by layer
Algeon keeps an error typed at the layer that owns the failure and converts it only when crossing a layer boundary.
- The
algeon-serverpackage only launches the server module. - Fix a missing classification at its owner. If a cuDF or cuGraph error lacks the typed information Algeon needs, extend the owning component crate; the adapter does not parse its message.
- Ordinary DataFusion errors that carry no Algeon identity, and transport errors below the server, keep their own policies. Nothing in the adapter or server upgrades them to Algeon errors.
- Protocol policy outside the built-in server belongs to the application. The embedded REST example maps typed errors before applying its ordinary DataFusion fallback, and it does not expose raw facts wholesale.
Normal native-planning declines are no errors. NotSupported and
NotSelectedByCost are candidate outcomes in a PlanningReport, never error
codes or DataFusionErrors, and the original DataFusion plan remains
executable. A malformed plan, an unsatisfied final-plan requirement, or a
runtime failure is an error.
Creating and wrapping errors
Create the error where the failed contract is known:
- Use the shared
QueryErrorfor DataFusion-independent planning, runtime and engine behavior, and add its canonical code and mappings inquery-planning. - Use adapter
Errorfor DataFusion-specific planning, wrappers and public integration behavior. - A
QueryErrorreaching the adapter is converted at that boundary. The constructor is contributor-internal and no public integration entry point. The conversion maps the native code and preserves native code, kind, status, operation, message, non-colliding facts and source. Integrators need only the public constructors andfind_algeon_error(). - Use the existing typed lower-layer constructors and provenance at cuDF, cuGraph or cuVS boundaries. Their mappings use enums and status, never message text.
kindandstatusare mappings derived fromcode. Add a newcodefirst, then update the exhaustive mappings and contract tests.
Configuration validation failures use invalid_configuration at both layers,
never a plan code. The input is a service, backend, device or server setting;
the code has kind invalid_input and status permanent. The runtime reports
the setting name from the device profile (the same name as the TOML key under
[[admission.device_profiles]]), the configured value, the limit it was
checked against, and the largest accepted value where one exists. Byte values
print as exact bytes followed by GiB truncated to two decimals, for example
102005473280 bytes (95.00 GiB). invalid_plan and invalid_execution_plan
remain for malformed query plans and engine-internal consistency checks.
Operations are stable dotted identifiers. Messages explain the failure to a human. Facts carry the machine-readable context that policy or diagnosis needs, and the source preserves the original cause. A feature module or transport adapter does not create a second error taxonomy.
Use the result type owned by the current layer: native Result<T> across
planning, runtime and engine, adapter Result<T> before DataFusion, and
DataFusion's result type in DataFusion traits. Let ? perform the single
adapter-to-DataFusion conversion, DataFusionError::External(Box::new(algeon_error)).
There is deliberately no blanket conversion in the other direction.
Retry evidence
Once native GPU execution starts, a failure closes the attempt, and Algeon does not replay the retained CPU plan. Native-owned failures stay typed, while a DataFusion-owned boundary failure keeps its DataFusion identity. Engine-internal bounded retries are owned by the exact execution operator and recognize only their explicit resource cases.
QueryAdmissionOutcome projections are policy-neutral; the application still
decides whether to queue, shed or return a protocol error. A post-admission OOM
goes through from_execution_error, because it is an execution failure and
no queue capacity can be waited on retroactively.
The sealed QueryAttemptReport::retry_advice is the authoritative recovery
signal for an attempted query. It is derived only after terminal outcome, GPU
cleanup, capacity disposition, device health, native-fault isolation and
resource evidence are committed.
The engine never retries from this advice. A caller satisfies the paired prerequisite and submits a new attempt. Cancellation, drop and panic do not become retries, and a transport delivery failure does not rewrite the sealed attempt outcome.
Disclosure model
Raw facts and source chains are trusted in-process diagnostics. They may contain query literals, object paths, identifiers, backend messages or credentials. Each surface below carries a different projection of one error, and tests compare each surface on its own terms, never as equal strings.
- Trusted in-process diagnostics are native
QueryErrorformatting,QueryError::to_json()and the coverage API'strusted_diagnostic. None of them is a client response. - Adapter
with_factstays hidden from log and retained-diagnostic projections.with_log_safe_factis an explicit opt-in for bounded operational classifications. Error::message()never carries blocking text or caller column names.blocking_kindis a log-safe fact next toreason.- The sealed attempt report does not copy arbitrary messages or facts.
- The Flight status and the rejected
EXPLAIN GPUsummary row read their code and text from the same client projection. - Metadata and query stats are machine-readable projections beside the status message and do not replace it. The allowlist and masking rules apply to them, and neither the status message nor metadata exposes raw facts or the source chain.
- Preserve the typed source for internal diagnosis unless the owning boundary deliberately omits it because the foreign text can expose a sensitive location.
Projected facts and wire limits
The Flight server projects only these facts into metadata, and only when the error carries them:
- Each key is the fact name with
x-algeon-prepended and each_written as-, for examplex-algeon-requested-allocation-bytes. - Operation values are code identifiers. Byte values are re-rendered from a parsed integer, and a fact that is not a number is omitted.
- Internal, dependency, invariant and execution failures never carry raw native text in metadata, however their status message reads.
- A cuGraph precondition failure is invalid input. Its
native_messageends with libcugraph's reason without source location, for exampleSSSP source vertex is not in the graph. - Client-visible fact selection is independent of
with_log_safe_fact. A producer attachesnative_messageas a trusted in-process diagnostic whose content is already safe to expose on the allowlisted validation paths, and the server gate decides the projection. - The statement boundary adds query and correlation identifiers.
GetQueryStatsretains projected facts asfact.<name>, such asfact.reason,fact.unsupported_column,fact.unsupported_column_type,fact.unsupported_column_role,fact.blocking_kindandfact.blocking_subject.
Metadata values must be transmittable ASCII and are capped at 256 bytes per
value with a ... marker. A value that cannot cross the wire, such as a
non-ASCII schema detail or column name, is omitted and listed under
x-algeon-omitted-facts. The full diagnosis stays available in the status
message and query stats.
Memory failure facts
- The allocation peak is the highest byte count the allocator recorded for the failing scope, including the working set of the failing operation. A request that failed against the grant did not fit beside it.
- An operation that runs under a budget token reports the peak of its stream's allocator member. Any other operation reports the peak of the attempt's allocation domain, whose limit is the grant.
- The message carries no current-usage figure, because a sample taken once the error is returned reflects the working set the failing operation has already released. A figure the error does not carry is left out of the message.
GetQueryStatskeeps the requested, peak, grant and current byte counts as facts of its[structured_error]section. Theallocator_high_water_bytescolumn of its[execution]row holds the peak of the attempt's allocation domain, for failed attempts as well as successful ones.
Extending the contract
When adding an error:
- Add the most specific code at the owning layer.
- Update the code-to-kind and code-to-status mappings; never infer either from the variant name.
- Map native codes exhaustively at the adapter boundary while preserving the original native classification.
- Decide explicitly whether each fact is raw, log-safe, attempt-report-safe or Flight-visible. Each is a separate choice in the disclosure model.
- Extend the existing error, admission, attempt-report or Flight contract tests. A useful regression test fails when the mapping or boundary behavior is reverted.
The canonical implementation points are:
crates/algeon-datafusion/src/error.rscrates/algeon-datafusion/src/error/code.rscrates/algeon-query-planning/src/error.rscrates/algeon-query-runtime/src/observability/attempt_report.rscrates/algeon-query-engine/src/exec/crates/algeon-datafusion/src/server/error.rs