traffix-live / scripts /check_architecture.py
REBELLION2002's picture
Deploy live TraffiX app (fix/live-container-ci e8a6317, showcase network omitted)
ec5ee81 verified
Raw History Blame Contribute Delete
2.89 kB
"""
Proves the architecture rules hold in the code:
1. Only traffix/simulation/executor.py changes a traffic light,
and only orchestration/ (the masters) calls the executor's request().
2. Agents never import the simulation backend, the executor or the world.
3. The Synapse layer (explainer, alert writer, operator copilot) has no path to the simulation.
4. The calendar assumptions are hidden from agents: no agent imports the calendar model.
python scripts/check_architecture.py
"""
import ast
import pathlib
import sys
ROOT = pathlib.Path(__file__).resolve().parent.parent
LIGHT_WRITES = {"setRedYellowGreenState", "setPhase", "setPhaseDuration", "setProgram",
"setProgramLogic", "setCompleteRedYellowGreenDefinition"}
AGENT_FORBIDDEN = {"backend", "traci", "libsumo", "executor", "world", "calendar_model"}
EXPLAINER_FORBIDDEN = AGENT_FORBIDDEN | {"sensors", "orchestration", "master", "supermaster"}
def names(tree):
out = set()
for n in ast.walk(tree):
if isinstance(n, ast.Import):
out |= {a.name.split(".")[-1] for a in n.names}
elif isinstance(n, ast.ImportFrom):
out |= {(n.module or "").split(".")[-1]} | {a.name for a in n.names}
return out
fails = []
for path in sorted((ROOT / "traffix").rglob("*.py")):
rel = path.relative_to(ROOT).as_posix()
tree = ast.parse(path.read_text(encoding="utf-8"))
writes = [n.attr for n in ast.walk(tree) if isinstance(n, ast.Attribute) and n.attr in LIGHT_WRITES]
if writes and rel != "traffix/simulation/executor.py":
fails.append(f"{rel} writes a traffic light: {writes}")
calls_request = any(isinstance(n, ast.Attribute) and n.attr == "request" and
isinstance(n.value, ast.Attribute) and n.value.attr in ("ex", "executor")
for n in ast.walk(tree))
if calls_request and not rel.startswith("traffix/orchestration/"):
fails.append(f"{rel} calls the executor; only masters may")
if rel.startswith("traffix/agents/") and names(tree) & AGENT_FORBIDDEN:
fails.append(f"{rel} imports {sorted(names(tree) & AGENT_FORBIDDEN)}")
if (rel == "traffix/agents/network/explainer.py" or rel.startswith("traffix/synapse/")) \
and names(tree) & EXPLAINER_FORBIDDEN:
fails.append(f"{rel} imports {sorted(names(tree) & EXPLAINER_FORBIDDEN)}")
if fails:
print("ARCHITECTURE CHECK FAILED")
for f in fails:
print(" -", f)
sys.exit(1)
print("Architecture check passed:")
print(" - only the executor writes lights, and only masters call it")
print(" - agents have no access to the simulation backend, executor or world")
print(" - agents never see the calendar assumptions")
print(" - the Synapse layer (explainer, alert writer, copilot) has no path to the simulation")