orchestrata_ultimate.factory.amendment.models

Reference › orchestrata_ultimate › factory › amendment › models

models holds the frozen dataclasses of the amendment-direction classifier (SPEC-033 row B16). Direction has three members: TIGHTENING, LOOSENING and UNDETERMINED (ADR-0009, ADR-0020). ChangeKind names how a clause differs between old and new text: CLAUSE_REMOVED, CLAUSE_ADDED, BODY_CONJUNCT_ADDED, BODY_CONJUNCT_REMOVED and BODY_OTHER. A ClauseChange records the predicate, the kind and a detail string. DirectionVerdict carries the direction, a reason, the list of changes and a requires_human flag; __post_init__ enforces the invariant that the flag is true exactly when the direction is LOOSENING or UNDETERMINED, because only a TIGHTENING verdict may proceed on the amending quorum alone (ADR-0009). DirectionVerdict.build derives the flag from the direction. The module has no internal imports; classify, coupling, syntactic and scripts.factory_amend use it.

At a glance

Source src/orchestrata_ultimate/factory/amendment/models.py
Lines of code 91
Classes 4
Functions 0
Imports (internal) none
Imported by orchestrata_ultimate.factory.amendment.classify, orchestrata_ultimate.factory.amendment.coupling, orchestrata_ultimate.factory.amendment.syntactic, scripts.factory_amend
Tests that import it tests/factory/test_amendment_classify.py, tests/factory/test_amendment_coupling.py, tests/factory/test_amendment_syntactic.py

Class diagram

Class diagram orchestrata_ultimate.factory.amendment.models

Dependency diagram

Dependency diagram orchestrata_ultimate.factory.amendment.models

API

Classes

Direction (enum)

Bases: StrEnum · Line 17

The three amendment-direction verdicts (ADR-0009/0020).

Member Value
TIGHTENING 'tightening'
LOOSENING 'loosening'
UNDETERMINED 'undetermined'

ChangeKind (enum)

Bases: StrEnum · Line 29

How one matched clause (or unmatched clause) differs old -> new.

Member Value
CLAUSE_REMOVED 'clause_removed'
CLAUSE_ADDED 'clause_added'
BODY_CONJUNCT_ADDED 'body_conjunct_added'
BODY_CONJUNCT_REMOVED 'body_conjunct_removed'
BODY_OTHER 'body_other'

ClauseChange (dataclass)

Decorators: dataclass(frozen=True) · Line 46

One diffed clause, keyed by the predicate it belongs to.

Attribute Type Default
predicate str
kind ChangeKind
detail str

DirectionVerdict (dataclass)

Decorators: dataclass(frozen=True) · Line 62

The result of one :func:~...classify.classify_amendment call.

Attribute Type Default
direction Direction
reason str
changes list[ClauseChange]
requires_human bool
Method Signature Summary
__post_init__ __post_init__() -> None
build classmethod build(direction: Direction, reason: str, changes: list[ClauseChange]) -> DirectionVerdict Construct with requires_human derived from direction -- the invariant :meth:__post_init__ enforces, kept in one place so every caller in :mod:~...classify gets it for free.

Source

Permalink: https://gitlab.com/MSamy/orchestrata-ultimate-architecture/-/blob/a4cbc2bdf/src/orchestrata_ultimate/factory/amendment/models.py

"""Data model for the SPEC-033 amendment-direction classifier (B16).

Every dataclass here is frozen. ``DirectionVerdict.requires_human`` is a
stored field (not computed by callers) but its value is an invariant enforced
in ``__post_init__``: True iff ``direction`` is ``LOOSENING`` or
``UNDETERMINED`` (ADR-0009 -- both route to the human; only a
:attr:`Direction.TIGHTENING` verdict may proceed on the amending quorum
alone).
"""

from __future__ import annotations

from dataclasses import dataclass
from enum import StrEnum


class Direction(StrEnum):
    """The three amendment-direction verdicts (ADR-0009/0020)."""

    #: The amended rulebase accepts strictly less -- quorum suffices.
    TIGHTENING = "tightening"
    #: The amended rulebase newly accepts something -- human ratification.
    LOOSENING = "loosening"
    #: Not provably tightening -- ADR-0020 step 3 treats this as the human's,
    #: same as LOOSENING (fail-closed: unknown direction is never assumed safe).
    UNDETERMINED = "undetermined"


class ChangeKind(StrEnum):
    """How one matched clause (or unmatched clause) differs old -> new.

    See :mod:`orchestrata_ultimate.factory.amendment.syntactic` for the
    monotonicity argument each kind carries.
    """

    CLAUSE_REMOVED = "clause_removed"
    CLAUSE_ADDED = "clause_added"
    BODY_CONJUNCT_ADDED = "body_conjunct_added"
    BODY_CONJUNCT_REMOVED = "body_conjunct_removed"
    #: Anything not decidable by the syntactic test (reordering that is not a
    #: pure subsequence extension/reduction, argument changes, etc).
    BODY_OTHER = "body_other"


@dataclass(frozen=True)
class ClauseChange:
    """One diffed clause, keyed by the predicate it belongs to.

    ``predicate`` is ``name/arity`` (e.g. ``"permission_granted/2"``).
    ``detail`` is a short human-readable description of what changed (e.g.
    the added/removed conjunct text, or the clause text for a whole-clause
    add/remove) -- never used for classification itself, only for the
    CLI/report surface.
    """

    predicate: str
    kind: ChangeKind
    detail: str


@dataclass(frozen=True)
class DirectionVerdict:
    """The result of one :func:`~...classify.classify_amendment` call."""

    direction: Direction
    reason: str
    changes: list[ClauseChange]
    requires_human: bool

    def __post_init__(self) -> None:
        expected = self.direction is not Direction.TIGHTENING
        if self.requires_human != expected:
            raise ValueError(
                "requires_human must be True iff direction is LOOSENING or "
                f"UNDETERMINED (ADR-0009); got direction={self.direction!r}, "
                f"requires_human={self.requires_human!r}"
            )

    @classmethod
    def build(
        cls, direction: Direction, reason: str, changes: list[ClauseChange]
    ) -> DirectionVerdict:
        """Construct with ``requires_human`` derived from ``direction`` --
        the invariant :meth:`__post_init__` enforces, kept in one place so
        every caller in :mod:`~...classify` gets it for free."""
        return cls(
            direction=direction,
            reason=reason,
            changes=changes,
            requires_human=direction is not Direction.TIGHTENING,
        )