#!/usr/bin/env python3 """P3-B enforcement 강제기 — standalone check(pytest 아님). exit 0=통과. validate_method_execution(보고서 step-results 증명) + handoff gate + transition gate + debt. 합성 계약/활성화를 methods/activations 인자로 직접 주입해 policy 분기를 검증한다(디스크 미오염). """ import importlib.util import os import sys import yaml # noqa: F401 ROOT = os.environ.get("CLAUDE_PROJECT_DIR", os.getcwd()) HOOKS = os.path.join(ROOT, ".claude", "hooks") passed = failed = 0 def check(name, ok): global passed, failed if ok: passed += 1 print(f" ✅ {name}") else: failed += 1 print(f" ❌ {name}") 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") # --- 합성 계약(v2) + 활성화 -------------------------------------------------- _PROF = { "method-id": "m1", "workflow": [ {"step-id": "s1", "required-output": "brief"}, {"step-id": "s2"}, {"step-id": "s3", "skippable": True, "skip-rules": [{"rule-id": "no-visual"}]}, ], "alternatives-policy": {"min-alternatives": 2}, } METHODS = { "TST": {"method-contract": {"version": 2}, "methods": [_PROF]}, "V1": {"working-method": ["..."]}, } HASH = mc.canonical_contract_hash(_PROF) ACTS = {"TST": {"methods": {"m1": {"status": "active", "contract-sha256": HASH}}}} ACTS_DRAFT = {"TST": {"methods": {"m1": {"status": "draft", "contract-sha256": HASH}}}} def _me_ok(): return { "role-id": "TST", "method-id": "m1", "contract-sha256": HASH, "step-results": [ {"step-id": "s1", "status": "completed", "artifact-refs": [{"report-id": "r1", "sha256": "a" * 64}]}, {"step-id": "s2", "status": "completed"}, {"step-id": "s3", "status": "skipped", "skip-rule-id": "no-visual"}, ], "decisions": [{"decision-id": "d1", "alternatives": [{"option-id": "a"}, {"option-id": "b"}], "selected-option-id": "a"}], } def _vme(me, role="TST", tier="standard", acts=ACTS): rep = {"role-id": role, "tier": tier} if me is not None: rep["method-execution"] = me return mc.validate_method_execution(rep, activations=acts, methods=METHODS) print("== T4.1: validate_method_execution 무강제 조건 ==") check("v1 역할 → 무강제", _vme(None, role="V1") == []) check("draft 계약 → 무강제(trace-only)", _vme(None, acts=ACTS_DRAFT) == []) check("light tier → 무강제", _vme(None, tier="light") == []) print("== T4.1: validate_method_execution 강제(active·standard) ==") check("정상 실행 → 통과", _vme(_me_ok()) == []) check("method-execution 누락 → 에러(자기신고 금지)", any("method-execution.method-id 없음" in e for e in _vme(None))) _bad = _me_ok(); _bad["contract-sha256"] = "0" * 64 check("contract-sha256 불일치 → 에러(구버전 실행)", any("contract-sha256" in e for e in _vme(_bad))) _bad = _me_ok(); _bad["step-results"] = [s for s in _bad["step-results"] if s["step-id"] != "s2"] check("필수 step 누락(s2) → 에러", any("필수 step 's2'" in e for e in _vme(_bad))) _bad = _me_ok(); _bad["step-results"][0].pop("artifact-refs") check("completed·required-output인데 artifact-ref 없음 → 에러", any("artifact-ref 없음" in e for e in _vme(_bad))) _bad = _me_ok(); _bad["step-results"][1]["status"] = "skipped"; _bad["step-results"][1]["skip-rule-id"] = "x" check("skippable 아닌 step(s2) skip → 에러", any("skippable 아님" in e for e in _vme(_bad))) _bad = _me_ok(); _bad["step-results"][2]["skip-rule-id"] = "unauthorized" check("허용 안 된 skip-rule → 에러", any("skip-rule-id" in e for e in _vme(_bad))) _bad = _me_ok(); _bad["decisions"][0]["alternatives"] = [{"option-id": "a"}] check("대안 부족(