#!/usr/bin/env python3 """P3-B T7 cutover 준비 검증 — standalone check(pytest 아님). exit 0=통과. 두 축을 증명한다: T7.1 fixture 격리 활성화-방탄: 전 v2 profile 을 active 로 가정(full-activation)해도 디스크 fixture report 가 method-execution 위반을 내지 않는다 — TST-* 는 v2 계약이 아니라 강제 대상 아님(격리). T7.2 실제 active 역할 통합(조건#3): DES-PROD/post-direction(실제 active)에 대해 method-execution negative(누락→Hard Fail)·positive(실계약 파생→통과)·drift(hash 불일치→Hard Fail)를 검증. 이 테스트가 green 이면 "전 참조 profile 일괄 활성화 시에도 테스트 스위트가 견딘다"는 readiness 증명이다. 활성화 자체는 HUMAN signoff 게이트(guard 차단) — 이 테스트는 활성화 전 안전성 사전검증. """ import glob import importlib.util import os import sys import yaml ROOT = os.environ.get("CLAUDE_PROJECT_DIR", os.getcwd()) HOOKS = os.path.join(ROOT, ".claude", "hooks") FIX = os.path.join(ROOT, ".claude", "tests", "fixtures") passed = failed = 0 def check(name, ok, detail=""): global passed, failed if ok: passed += 1 print(f" ✅ {name}") else: failed += 1 print(f" ❌ {name} {detail}") def _load(mod, path): spec = importlib.util.spec_from_file_location(mod, os.path.join(HOOKS, path)) m = importlib.util.module_from_spec(spec) if HOOKS not in sys.path: sys.path.insert(0, HOOKS) spec.loader.exec_module(m) return m mc = _load("method_contracts", "method_contracts.py") rm = mc.load_role_methods() # 전 v2 profile 을 active 로 가정한 full-activation activations(일괄 활성화 시뮬레이션). full_acts = {} for r, e in rm.items(): if (e.get("method-contract") or {}).get("version") == 2: full_acts[r] = {"methods": { m["method-id"]: {"status": "active", "contract-sha256": mc.canonical_contract_hash(mc.resolve_method_profile(r, m["method-id"]))} for m in e.get("methods", [])}} print("== T7.1: fixture 격리 활성화-방탄(full-activation 시뮬레이션) ==") _tst = {"role-id": "TST-GENERIC-FIXTURE", "tier": "standard"} check("TST-GENERIC-FIXTURE 는 full-activation 에도 method-execution 무강제(계약 아님)", mc.validate_method_execution(_tst, activations=full_acts, methods=rm) == []) # fixture 는 test_enforcement 가 런타임 재생성한다(run_all 알파벳순: enforcement < cutover 라 먼저). # 스탠드얼론에서 디스크가 스테일(구 role-id)이면 스캔이 오탐하므로 canary 로 freshness 확인 후에만 스캔. _canary = os.path.join(FIX, "agentrep.report.yaml") _fresh = os.path.exists(_canary) and "TST-GENERIC-FIXTURE" in open(_canary, encoding="utf-8").read() if _fresh: _residual = [] for f in sorted(glob.glob(os.path.join(FIX, "*.report.yaml"))): try: d = yaml.safe_load(open(f, encoding="utf-8")) except Exception: # noqa: BLE001 continue if not isinstance(d, dict): continue if mc.validate_method_execution(d, activations=full_acts, methods=rm): _residual.append(os.path.basename(f)) check("생성된 fixture는 full activation에서도 계약 위반 잔여가 없음", _residual == [], detail=str(_residual)) _lowercase_missing_trace = {"role-id": "arch-solution", "tier": "standard"} check("등록 역할의 소문자 표기는 active method 계약을 우회하지 못함", bool(mc.validate_method_execution( _lowercase_missing_trace, activations=full_acts, methods=rm))) else: check("디스크 fixture 스캔 — 재생성 대기(run_all 순서상 test_enforcement 후 유효, 스탠드얼론 생략)", True) print("== T7.2: 실제 active 역할(DES-PROD/post-direction) 통합 — 조건#3 ==") DESPROD, MID = "DES-PROD", "post-direction" real_acts = mc.load_activations() _active = mc._active_methods(DESPROD, real_acts) check(f"{DESPROD}/{MID} 실제 active(활성화 계약 존재)", MID in _active) prof = mc.resolve_method_profile(DESPROD, MID) _hash = mc.canonical_contract_hash(prof) # negative: active 인데 method-execution 없음 → 자기신고 차단 _neg = {"role-id": DESPROD, "tier": "standard"} check("negative: active + method-execution 없음 → Hard Fail", any("method-execution.method-id 없음" in e for e in mc.validate_method_execution(_neg, activations=real_acts))) # positive: 실계약에서 파생한 유효 method-execution → 통과(hash·step-results·artifact-ref 정합) _steps = [] for s in prof.get("workflow", []): r = {"step-id": s["step-id"], "status": "completed"} if s.get("required-output"): r["artifact-refs"] = [{"report-id": f"artifact-{s['step-id']}", "sha256": "a" * 64}] _steps.append(r) _me = {"role-id": DESPROD, "method-id": MID, "contract-sha256": _hash, "step-results": _steps} _ap = prof.get("alternatives-policy") or {} _min = _ap.get("min-alternatives") or _ap.get("min") or 0 if _min: _me["decisions"] = [{"decision-id": "d1", "alternatives": [f"alt{i}" for i in range(_min)]}] _pos = {"role-id": DESPROD, "tier": "standard", "method-execution": _me} check("positive: 실계약 파생 유효 method-execution → 통과", mc.validate_method_execution(_pos, activations=real_acts) == [], detail=str(mc.validate_method_execution(_pos, activations=real_acts))) # drift: 구버전 hash → 차단(재활성 강제) _stale = {**_me, "contract-sha256": "0" * 64} _pos_stale = {"role-id": DESPROD, "tier": "standard", "method-execution": _stale} check("drift: 구버전 contract-sha256 → Hard Fail(재활성 전 실행 차단)", any("contract-sha256" in e for e in mc.validate_method_execution(_pos_stale, activations=real_acts))) print(f"\n{passed} passed · {failed} failed") sys.exit(1 if failed else 0)