Source code for melder.utilities.custom_exceptions.meld_execution_error

from typing import Optional



[docs] class MeldExecutionError(RuntimeError): """ Purpose: Represent any failure of DAG-based meld execution inside the Meld runtime as ONE stable error type, so the Conduit/Meld boundary has a single "resolution failed" signal instead of leaking a dozen low-level exception types. Contract: - Wraps the underlying failure rather than replacing it; `inner` is preserved untouched. - Carries root spell identity always, and node/parameter attribution when the runtime can determine it. - `__str__` renders identity, node, parameter, message, and inner exception into one diagnostic line, degrading gracefully when optional fields are absent. - Subclasses `RuntimeError`. This is a **high-level wrapper** around any internal exception thrown while resolving a spell via the Meld engine. Why this exists --------------- * From the Conduit / Meld boundary we want a **single, stable** error type to represent "DI / resolution failed". * Internally, the engine may encounter: - Constructor/type errors - Missing dependency instances - Invalid override payloads - Unexpected DAG structure * `MeldExecutionError` captures: - The **root spell identity** (spell_id, spell_name). - Optional **node_id** if the error occurred at a specific DAG node. - Optional **param_name** if the failure is tied to a specific constructor parameter. - The underlying **inner exception**, if any. This gives callers and tooling enough context to log, trace, or present detailed diagnostics without having to interpret dozens of low-level exception types. Raised When: - A constructor, override payload, or DAG node fails while the Meld runtime is resolving a spell. - Change control has marked the target root dirty for the active conduit, so execution is gated until revalidation clears it. What To Do About It: Start with `inner` - it holds the real cause. `node_id` and `param_name` narrow the failure to a specific DAG node or constructor parameter when the runtime could attribute it. A dirty-root gate is not a defect: it means structure changed under a live conduit and the next resolution revalidates. Owned State: - `spell_id` / `spell_name`: the root spell being resolved. - `node_id`: DAG node where the failure occurred, when attributable. - `param_name`: constructor parameter involved, when attributable. - `inner`: the underlying exception. Registration: Exported on the public root surface; import, raise, and catch freely. Subsystem Context: One of the 11 `utilities/custom_exceptions/` types and the primary error of the Meld resolution runtime. It is the runtime counterpart to `SpellbookValidationError`, which is raised at build time before any Conduit exists. System Context: Fires in the resolution layer of the DGR, after conjure has produced a Conduit. Because instances may already be registered in `Creations` when it raises, a failed meld does NOT roll back the conduit - the object world stays live and the caller decides whether to retry, rebind, or tear down. AGENT_ACCESS: public AGENT_PURPOSE: access: public. The single 'resolution failed' signal from the Meld runtime; start at inner for the real cause, node_id/param_name narrow it. A dirty-root gate means revalidate, not a defect. """ __slots__ = ( "spell_id", "spell_name", "node_id", "param_name", "inner", "_message", ) def __init__( self, *, spell_id: str, spell_name: str, message: str, node_id: Optional[str] = None, param_name: Optional[str] = None, inner: Optional[BaseException] = None, ) -> None: """ Construct a new `MeldExecutionError`. Args: spell_id: The root spell's spell_id (typically SHA256). spell_name: Human-readable name of the root spell. message: High-level error message describing what went wrong. node_id: Optional DAG node identifier where the failure occurred. param_name: Optional parameter name associated with the failure. inner: Optional underlying exception that triggered this error. Contract: - Preserves all supplied diagnostic fields on the instance for downstream logging and tooling. - Uses `message` as the base `RuntimeError` payload while keeping the richer metadata separate. Returns: None. """ super().__init__(message) self.spell_id: str = spell_id self.spell_name: str = spell_name self.node_id: Optional[str] = node_id self.param_name: Optional[str] = param_name self.inner: Optional[BaseException] = inner self._message: str = message def __str__(self) -> str: """ Render a diagnostic string from the stored spell and node metadata. Contract: - Composes the message from the spell name and id, and APPENDS THE NODE ID only when one is present - so a node-less failure reads cleanly rather than showing `None`. - Identifies the spell by BOTH name and id, because names are not unique and the id alone is not human-readable. Threading: Pure formatting over instance fields. Lifecycle / Cleanup: None - it is an exception value. Returns: str: Stable human-readable summary including the root spell id/name plus any available node, parameter, message, and inner-exception details. """ base = ( f"[MELD] Execution failed for spell {self.spell_name!r} " f"({self.spell_id})" ) if self.node_id is not None: base += f" at node {self.node_id}" if self.param_name is not None: base += f" (parameter {self.param_name!r})" if self._message: base += f": {self._message}" if self.inner is not None: base += f" (inner: {self.inner!r})" return base