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
Dependency diagram
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
"""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,
)