orchestrata_ultimate.factory.contracts.models
Reference › orchestrata_ultimate › factory › contracts › models
models defines the smallest design-node contract schema that can say which modules and symbols a node owns and which requirements it implements (SPEC-033 row B3, ADR-0007). DesignNode holds a node id, title, modules, symbols and requirements; DesignContract holds a contract id, a change reference, a tuple of nodes and a schema_version (currently 1). Persistence is JSON with an atomic write (temporary file and os.replace): save_contract writes and load_contract reads and validates. validate_contract raises ContractValidationError for non-unique node ids, a module claimed by more than one node, or a malformed requirement id (_is_valid_req_id). Validation runs on load, not on construction, so a contract built in memory may be inconsistent until written. The conformance engine, trace_validation and the scripts.factory_conformance and scripts.factory_trace_check CLIs use it.
At a glance
| Source | src/orchestrata_ultimate/factory/contracts/models.py |
| Lines of code | 201 |
| Classes | 3 |
| Functions | 8 |
| Imports (internal) | orchestrata_ultimate.factory.assumptions.sources |
| Imported by | orchestrata_ultimate.factory.conformance.engine, orchestrata_ultimate.factory.contracts.trace_validation, scripts.factory_conformance, scripts.factory_trace_check |
| Tests that import it | tests/factory/test_conformance_engine.py, tests/factory/test_contracts_models.py, tests/factory/test_trace_validation.py, tests/scripts/test_factory_conformance_cli.py, tests/scripts/test_factory_trace_check_cli.py |
Class diagram
Dependency diagram
API
Classes
DesignNode (dataclass)
Decorators: dataclass(frozen=True) · Line 33
One design node's contract.
| Attribute | Type | Default |
|---|---|---|
node_id |
str |
|
title |
str |
|
modules |
tuple[str, ...] |
() |
symbols |
tuple[str, ...] |
() |
requirements |
tuple[str, ...] |
() |
DesignContract (dataclass)
Decorators: dataclass(frozen=True) · Line 53
A change's full set of design nodes.
| Attribute | Type | Default |
|---|---|---|
contract_id |
str |
|
change_ref |
str |
|
nodes |
tuple[DesignNode, ...] |
() |
schema_version |
int |
1 |
ContractValidationError
Bases: ValueError · Line 67
Raised when a :class:DesignContract fails structural validation.
Functions
| Function | Signature | Summary |
|---|---|---|
_is_valid_req_id |
_is_valid_req_id(req_id: str) -> bool |
True iff req_id matches the repo's REQ-... id convention. |
validate_contract |
validate_contract(contract: DesignContract) -> None |
Raise :class:ContractValidationError if contract is invalid. |
_node_to_dict |
_node_to_dict(node: DesignNode) -> dict[str, Any] |
|
_node_from_dict |
_node_from_dict(data: dict[str, Any]) -> DesignNode |
|
_contract_to_dict |
_contract_to_dict(contract: DesignContract) -> dict[str, Any] |
|
_contract_from_dict |
_contract_from_dict(data: dict[str, Any]) -> DesignContract |
|
save_contract |
save_contract(contract: DesignContract, path: Path | str) -> None |
Save contract as JSON at path, atomically (temp + os.replace). |
load_contract |
load_contract(path: Path | str) -> DesignContract |
Load + structurally validate a :class:DesignContract from JSON at path. |
Source
"""The minimal design-node contract schema (SPEC-033 B3, ADR-0007).
ADR-0007 requires checking that a traced unit's AST "lies within the cited
design node's contract" -- that needs a contract representation to check
against, which is what this module defines: the smallest schema that can
name which modules/symbols a design node owns and which requirements it
implements, nothing more (orchestrator-settled; see
``tasks/b3-trace-validation/instructions.md``).
Persistence mirrors the pattern in
:mod:`orchestrata_ultimate.factory.dor_procedure.artifact` -- JSON, atomic
write (temp file + ``os.replace``), exact round-trip. Structural validation
(unique node ids, no module double-claimed, well-formed requirement ids) runs
on :func:`load_contract`, not on construction -- a ``DesignContract`` built
in-process (e.g. by ``scripts/factory_trace_check.py template``) is not
required to already be internally consistent before it is written out.
"""
from __future__ import annotations
import json
import os
from dataclasses import dataclass
from pathlib import Path
from typing import Any
from orchestrata_ultimate.factory.assumptions.sources import _REQ_HEADING_RE
_SUPPORTED_SCHEMA_VERSION = 1
@dataclass(frozen=True)
class DesignNode:
"""One design node's contract.
``node_id`` is a kebab-case identifier, unique within a
:class:`DesignContract` (enforced on load). ``modules`` are repo-relative
``.py`` paths this node owns -- a module belongs to AT MOST one node
across the whole contract (also enforced on load). ``symbols`` are
optional qualified names within those modules; an empty tuple means "the
whole module" is in-contract. ``requirements`` are the ``REQ-...`` ids
this node implements.
"""
node_id: str
title: str
modules: tuple[str, ...] = ()
symbols: tuple[str, ...] = ()
requirements: tuple[str, ...] = ()
@dataclass(frozen=True)
class DesignContract:
"""A change's full set of design nodes.
``change_ref`` is the branch/commit context the contract was authored
against (free text, e.g. a branch name or commit sha) -- not itself
structurally validated.
"""
contract_id: str
change_ref: str
nodes: tuple[DesignNode, ...] = ()
schema_version: int = 1
class ContractValidationError(ValueError):
"""Raised when a :class:`DesignContract` fails structural validation."""
def _is_valid_req_id(req_id: str) -> bool:
"""True iff ``req_id`` matches the repo's ``REQ-...`` id convention.
Reuses -- imports, does not copy -- the ``ref`` capture group of
:data:`orchestrata_ultimate.factory.assumptions.sources._REQ_HEADING_RE`,
which is built to scan ``### REQ-...`` markdown headings. A bare id is
checked by matching a synthetic one-line heading built from it: the
match must consume the *entire* id as the ``ref`` group with no trailing
``text`` group left over, so ``req_id`` has to be exactly a valid ref,
not merely a prefix of one.
"""
match = _REQ_HEADING_RE.match(f"# {req_id}")
return bool(match and match.group("ref") == req_id and not match.group("text"))
def validate_contract(contract: DesignContract) -> None:
"""Raise :class:`ContractValidationError` if ``contract`` is invalid.
Exactly the three structural checks the brief specifies -- no more:
- node ids are unique within the contract;
- no module is claimed by more than one node;
- every requirement id matches the repo's ``REQ-...`` id convention.
"""
seen_ids: set[str] = set()
module_owner: dict[str, str] = {}
for node in contract.nodes:
if node.node_id in seen_ids:
raise ContractValidationError(
f"duplicate node_id in contract: {node.node_id!r}"
)
seen_ids.add(node.node_id)
for module in node.modules:
if module in module_owner:
raise ContractValidationError(
f"module {module!r} claimed by both node "
f"{module_owner[module]!r} and node {node.node_id!r}"
)
module_owner[module] = node.node_id
for req_id in node.requirements:
if not _is_valid_req_id(req_id):
raise ContractValidationError(
f"node {node.node_id!r} has a malformed requirement id: "
f"{req_id!r}"
)
def _node_to_dict(node: DesignNode) -> dict[str, Any]:
return {
"node_id": node.node_id,
"title": node.title,
"modules": list(node.modules),
"symbols": list(node.symbols),
"requirements": list(node.requirements),
}
def _node_from_dict(data: dict[str, Any]) -> DesignNode:
return DesignNode(
node_id=data["node_id"],
title=data["title"],
modules=tuple(data.get("modules", ())),
symbols=tuple(data.get("symbols", ())),
requirements=tuple(data.get("requirements", ())),
)
def _contract_to_dict(contract: DesignContract) -> dict[str, Any]:
return {
"contract_id": contract.contract_id,
"change_ref": contract.change_ref,
"nodes": [_node_to_dict(n) for n in contract.nodes],
"schema_version": contract.schema_version,
}
def _contract_from_dict(data: dict[str, Any]) -> DesignContract:
version = data.get("schema_version")
if version != _SUPPORTED_SCHEMA_VERSION:
raise ValueError(
f"Unsupported DesignContract schema_version: {version!r} "
f"(this loader understands {_SUPPORTED_SCHEMA_VERSION!r})"
)
return DesignContract(
contract_id=data["contract_id"],
change_ref=data["change_ref"],
nodes=tuple(_node_from_dict(n) for n in data.get("nodes", ())),
schema_version=version,
)
def save_contract(contract: DesignContract, path: Path | str) -> None:
"""Save ``contract`` as JSON at ``path``, atomically (temp + ``os.replace``)."""
target = Path(path)
target.parent.mkdir(parents=True, exist_ok=True)
tmp = target.with_name(f"{target.name}.tmp.{os.getpid()}")
try:
with open(tmp, "w", encoding="utf-8") as f:
json.dump(_contract_to_dict(contract), f, indent=2, sort_keys=True)
f.flush()
os.fsync(f.fileno())
os.replace(tmp, target)
finally:
if tmp.exists():
tmp.unlink()
def load_contract(path: Path | str) -> DesignContract:
"""Load + structurally validate a :class:`DesignContract` from JSON at ``path``.
Raises:
ValueError: If the stored ``schema_version`` is not one this loader
understands.
ContractValidationError: If the loaded contract fails the structural
checks in :func:`validate_contract`.
"""
with open(Path(path), encoding="utf-8") as f:
data = json.load(f)
contract = _contract_from_dict(data)
validate_contract(contract)
return contract
__all__ = [
"ContractValidationError",
"DesignContract",
"DesignNode",
"load_contract",
"save_contract",
"validate_contract",
]