"""Foundation Phase 2 smoke gate — every journal:X check goes through tracing.""" from __future__ import annotations import ast import importlib from pathlib import Path import pytest from roboco.foundation.policy import lifecycle as spec from roboco.foundation.policy import tracing _GATEWAY_DIR = Path(__file__).resolve().parents[2] / "roboco" / "services" / "gateway" def _enclosing_function(tree: ast.AST, lineno: int) -> str | None: """Return the name of the (async) function whose body contains ``lineno``.""" candidate: str | None = None candidate_start = -1 for node in ast.walk(tree): if isinstance(node, (ast.AsyncFunctionDef, ast.FunctionDef)): start = node.lineno end = getattr(node, "end_lineno", None) or start if start <= lineno <= end and start > candidate_start: candidate = node.name candidate_start = start return candidate def test_no_inline_has_decision_for_task_remains_in_choreographer() -> None: """All journal:decision checks must use tracing.check_requirements via the unified helpers (_check_pm_decision_required, _check_complete_gates, _check_submit_up_gates, _check_tracing_gates, _check_claim_journal_at_claim, _post_claim_journal_gate).""" allowed_helpers = { "_check_pm_decision_required", "_check_complete_gates", "_check_submit_up_gates", "_check_tracing_gates", "_check_claim_journal_at_claim", "_post_claim_journal_gate", } suspicious: list[str] = [] for py_path in _GATEWAY_DIR.rglob("*.py"): source = py_path.read_text() if "has_decision_for_task" not in source: continue tree = ast.parse(source, filename=str(py_path)) for lineno, line in enumerate(source.splitlines(), start=1): if "has_decision_for_task" not in line: continue enclosing = _enclosing_function(tree, lineno) # Helper definition / docstring references don't count; only # call-site usages matter, but if the line is inside a helper # whose body is allowed, we accept it. if enclosing in allowed_helpers: continue # Allow references within docstrings (no executable impact). stripped = line.strip() if stripped.startswith("#") or stripped.startswith('"'): continue suspicious.append(f"{py_path}:{lineno}:{line.strip()}") assert suspicious == [], f"inline has_decision_for_task remains: {suspicious}" def test_tracing_gate_module_removed() -> None: with pytest.raises(ModuleNotFoundError): importlib.import_module("roboco.services.gateway.tracing_gate") def test_every_intent_verb_has_tracing_decision() -> None: """Mirror of the foundation parity test, as a smoke-gate.""" intent_verbs = set(spec._INTENT_VERBS.keys()) in_table = set(tracing.VERB_REQUIREMENTS) in_waived = set(tracing.VERBS_WITHOUT_TRACING) assert intent_verbs - in_table - in_waived == set(), ( f"verbs in lifecycle.spec without tracing decision: " f"{intent_verbs - in_table - in_waived}" ) def test_no_dangling_requirements() -> None: """Every Requirement value is referenced by at least one verb.""" used: set[tracing.Requirement] = set() for reqs in tracing.VERB_REQUIREMENTS.values(): used.update(reqs) assert set(tracing.Requirement) - used == set()