Skip to content

Protocol scaffold

protocol_scaffold

ContractDecl

Bases: _StrictDecl

Declare a named data contract used by a protocol component.

Source code in packages/axm-init/src/axm_init/models/protocol_scaffold.py
Python
class ContractDecl(_StrictDecl):  # type: ignore[explicit-any]
    """Declare a named data contract used by a protocol component."""

    name: Segment

    @property
    def model_name(self) -> str:
        return _pascal_case(self.name)

NodeDecl

Bases: _StrictDecl

Declare a node with optional contract and prompt references.

Source code in packages/axm-init/src/axm_init/models/protocol_scaffold.py
Python
class NodeDecl(_StrictDecl):  # type: ignore[explicit-any]
    """Declare a node with optional contract and prompt references."""

    name: Segment
    contract: Segment | None = None
    prompt: Segment | None = None

    @property
    def factory_name(self) -> str:
        return f"build_{self.name}"

PhaseDecl

Bases: _StrictDecl

Declare a phase and the nodes it contains, if any.

Source code in packages/axm-init/src/axm_init/models/protocol_scaffold.py
Python
class PhaseDecl(_StrictDecl):  # type: ignore[explicit-any]
    """Declare a phase and the nodes it contains, if any."""

    name: Segment
    nodes: list[Segment] = Field(default_factory=list)

    @property
    def factory_name(self) -> str:
        return f"build_{self.name}"

PromptDecl

Bases: _StrictDecl

Declare a named prompt and its text.

Source code in packages/axm-init/src/axm_init/models/protocol_scaffold.py
Python
class PromptDecl(_StrictDecl):  # type: ignore[explicit-any]
    """Declare a named prompt and its text."""

    name: Segment
    text: str

ProtocolScaffoldDecl

Bases: _StrictDecl

Validate a complete protocol declaration and its component references.

Source code in packages/axm-init/src/axm_init/models/protocol_scaffold.py
Python
class ProtocolScaffoldDecl(_StrictDecl):  # type: ignore[explicit-any]
    """Validate a complete protocol declaration and its component references."""

    domain: Segment
    unit: Segment
    action: Segment
    contracts: list[ContractDecl]
    nodes: list[NodeDecl]
    prompts: list[PromptDecl] = Field(default_factory=list)
    phases: list[PhaseDecl] = Field(default_factory=list)
    ticket: TicketDecl | None = None

    @property
    def graph_name(self) -> str:
        return ".".join((self.domain, self.unit, self.action))

    @model_validator(mode="after")
    def _validate_declaration(self) -> Self:
        self._reject_duplicate_components()
        self._reject_public_name_collisions()
        self._validate_component_references()
        self._validate_ticket_binding()
        return self

    def _reject_duplicate_components(self) -> None:
        for components in (self.contracts, self.nodes, self.prompts, self.phases):
            names = [component.name for component in components]
            if len(names) != len(set(names)):
                msg = "component names must be unique within each component list"
                raise ValueError(msg)

    def _reject_public_name_collisions(self) -> None:
        public_names = [
            *(contract.model_name for contract in self.contracts),
            *(node.factory_name for node in self.nodes),
            *(phase.factory_name for phase in self.phases),
        ]
        if len(public_names) != len(set(public_names)):
            msg = "derived public names must be unique"
            raise ValueError(msg)

    def _validate_node_references(self) -> None:
        declared_contracts = {contract.name for contract in self.contracts}
        declared_prompts = {prompt.name for prompt in self.prompts}
        for node in self.nodes:
            if node.contract is not None and node.contract not in declared_contracts:
                msg = (
                    f"node contract {node.contract!r} must reference "
                    "a declared contract"
                )
                raise ValueError(msg)
            if node.prompt is not None and node.prompt not in declared_prompts:
                msg = f"node prompt {node.prompt!r} must reference a declared prompt"
                raise ValueError(msg)

    def _validate_phase_references(self) -> None:
        declared_nodes = {node.name for node in self.nodes}
        references = (reference for phase in self.phases for reference in phase.nodes)
        for node_reference in references:
            if node_reference not in declared_nodes:
                msg = f"phase node {node_reference!r} must reference a declared node"
                raise ValueError(msg)

    def _validate_component_references(self) -> None:
        self._validate_node_references()
        self._validate_phase_references()

    def _validate_ticket_binding(self) -> None:
        if self.ticket is None:
            return
        declared_contracts = {contract.name for contract in self.contracts}
        if self.ticket.input_contract not in declared_contracts:
            msg = (
                f"ticket input_contract {self.ticket.input_contract!r} "
                "must reference a declared contract"
            )
            raise ValueError(msg)

TicketDecl

Bases: _StrictDecl

Bind a ticket type to one declared input contract.

Source code in packages/axm-init/src/axm_init/models/protocol_scaffold.py
Python
class TicketDecl(_StrictDecl):  # type: ignore[explicit-any]
    """Bind a ticket type to one declared input contract."""

    ticket_type: QualifiedName
    input_contract: Segment