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