117 lines
4.9 KiB
Python
117 lines
4.9 KiB
Python
"""ORCH-009 TC-21: pipeline invariants are untouched by the onboarding capability.
|
|
|
|
The onboarding kit/CLI lives entirely OUTSIDE the runtime (NFR-1): `src/**` is
|
|
byte-for-byte untouched. These tests pin that contract:
|
|
|
|
* a literal snapshot of ``STAGE_TRANSITIONS`` (the stage machine) and of the
|
|
``QG_CHECKS`` registry — any drift fails loudly;
|
|
* ``src/**`` never references the onboarding tree (no runtime coupling);
|
|
* the CLI's read-only imports from ``src`` stay within the CLOSED list of
|
|
ADR-001 D4 (ORCH-009) — extending the list requires an ADR update;
|
|
* kit prompt templates name only real quality gates (no phantom ``check_*``).
|
|
"""
|
|
import ast
|
|
import os
|
|
import re
|
|
|
|
from src.qg.checks import QG_CHECKS
|
|
from src.stages import STAGE_TRANSITIONS
|
|
|
|
_REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
_SCRIPT_PATH = os.path.join(_REPO_ROOT, "scripts", "onboard_project.py")
|
|
_KIT_AGENTS = os.path.join(_REPO_ROOT, "onboarding", "repo-skeleton", ".openclaw", "agents")
|
|
|
|
# Literal snapshot of the stage machine (src/stages.py). Byte-exact NFR-1 pin:
|
|
# the onboarding work item must not move a single edge/agent/gate.
|
|
_EXPECTED_TRANSITIONS = {
|
|
"created": {"next": "analysis", "agent": "analyst", "qg": None},
|
|
"analysis": {"next": "architecture", "agent": "architect", "qg": "check_analysis_approved"},
|
|
"architecture": {"next": "development", "agent": "developer", "qg": "check_architecture_done"},
|
|
"development": {"next": "review", "agent": "reviewer", "qg": "check_ci_green"},
|
|
"review": {"next": "testing", "agent": "tester", "qg": "check_reviewer_verdict"},
|
|
"testing": {"next": "deploy-staging", "agent": "deployer", "qg": "check_tests_passed"},
|
|
"deploy-staging": {"next": "deploy", "agent": "deployer", "qg": "check_staging_status"},
|
|
"deploy": {"next": "done", "agent": None, "qg": "check_deploy_status"},
|
|
"done": {"next": None, "agent": None, "qg": None},
|
|
"cancelled": {"next": None, "agent": None, "qg": None},
|
|
}
|
|
|
|
# Snapshot of the QG registry KEYS (src/qg/checks.py::QG_CHECKS).
|
|
_EXPECTED_QG_KEYS = {
|
|
"check_analysis_approved",
|
|
"check_analysis_complete",
|
|
"check_architecture_done",
|
|
"check_ci_green",
|
|
"check_review_approved",
|
|
"check_tests_passed",
|
|
"check_reviewer_verdict",
|
|
"check_tests_local",
|
|
"check_deploy_status",
|
|
"check_staging_status",
|
|
"check_branch_mergeable",
|
|
"check_staging_image_fresh",
|
|
"check_security_gate",
|
|
"check_coverage_gate",
|
|
}
|
|
|
|
# Closed read-only import list of the onboarding CLI (ADR-001 D4 ORCH-009).
|
|
_ALLOWED_SRC_IMPORTS = {"src.config", "src.plane_sync", "src.projects"}
|
|
|
|
|
|
def test_tc21_stage_transitions_snapshot():
|
|
assert STAGE_TRANSITIONS == _EXPECTED_TRANSITIONS, (
|
|
"STAGE_TRANSITIONS drifted — ORCH-009 must not touch the stage machine (NFR-1)"
|
|
)
|
|
|
|
|
|
def test_tc21_qg_checks_registry_snapshot():
|
|
assert set(QG_CHECKS) == _EXPECTED_QG_KEYS, (
|
|
"QG_CHECKS registry drifted — ORCH-009 must not touch the gates (NFR-1)"
|
|
)
|
|
|
|
|
|
def test_tc21_src_never_references_onboarding():
|
|
"""No runtime coupling: src/** must not import/reference the onboarding tree."""
|
|
offenders = []
|
|
for root, _dirs, files in os.walk(os.path.join(_REPO_ROOT, "src")):
|
|
for name in files:
|
|
if not name.endswith(".py"):
|
|
continue
|
|
path = os.path.join(root, name)
|
|
with open(path, encoding="utf-8") as f:
|
|
if "onboard" in f.read().lower():
|
|
offenders.append(os.path.relpath(path, _REPO_ROOT))
|
|
assert not offenders, f"src/** references onboarding: {offenders}"
|
|
|
|
|
|
def test_tc21_cli_src_imports_stay_in_closed_list():
|
|
"""ADR-001 D4: the CLI may import ONLY src.config / src.plane_sync / src.projects."""
|
|
with open(_SCRIPT_PATH, encoding="utf-8") as f:
|
|
tree = ast.parse(f.read())
|
|
found = set()
|
|
for node in ast.walk(tree):
|
|
if isinstance(node, ast.ImportFrom) and node.module and node.module.startswith("src"):
|
|
found.add(node.module)
|
|
elif isinstance(node, ast.Import):
|
|
for alias in node.names:
|
|
if alias.name.startswith("src"):
|
|
found.add(alias.name)
|
|
assert found, "the CLI must use the closed src imports (round-trip via real parser)"
|
|
assert found <= _ALLOWED_SRC_IMPORTS, (
|
|
f"onboard_project.py imports outside the closed ADR D4 list: "
|
|
f"{sorted(found - _ALLOWED_SRC_IMPORTS)} — extend ONLY via an ADR update"
|
|
)
|
|
|
|
|
|
def test_tc21_kit_prompts_name_only_real_gates():
|
|
"""A kit prompt naming a phantom gate would mislead every onboarded project."""
|
|
pattern = re.compile(r"check_[a-z_]+")
|
|
for name in sorted(os.listdir(_KIT_AGENTS)):
|
|
path = os.path.join(_KIT_AGENTS, name)
|
|
with open(path, encoding="utf-8") as f:
|
|
text = f.read()
|
|
for gate in sorted(set(pattern.findall(text))):
|
|
assert gate in QG_CHECKS, (
|
|
f"kit prompt {name} references gate {gate!r} absent from QG_CHECKS"
|
|
)
|