""" Integration test: full pipeline flow. Simulates a complete ADW-style pipeline run with: 1. State initialization + core_fields filter 2. Multiple phases, each producing a Verdict 3. Rejection envelope for blocked actions 4. State gate at the end 5. Expertise entry for what was learned """ import json import sys import os if hasattr(sys.stdout, "reconfigure"): sys.stdout.reconfigure(encoding="utf-8") sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) # Also add the parent of pipeline to ensure direct imports work _parent = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) if _parent not in sys.path: sys.path.insert(0, _parent) from pipeline.confidence_ladder import Verdict, VerdictLevel, VerdictReport from pipeline.rejection_envelope import RejectionEnvelope, Blocker from pipeline.state_gate import StateGate, FieldSpec, PipelineState from pipeline.expertise import append_entry, render_context, purge_domain errors = 0 # ── Step 1: Pipeline state with core_fields ── state = PipelineState( {"plan_file": "plans/feat-123.md", "branch_name": "feat/instant-polls", "issue_number": 42}, core_fields=["plan_file", "branch_name", "issue_number", "backend_port", "frontend_port", "worktree_path"], ) assert state.get("plan_file") == "plans/feat-123.md" print("✓ Step 1: State init with core_fields") # ── Step 2: Plan phase verdict ── report = VerdictReport(pipeline_id="adw-abc12345") report.add(Verdict(VerdictLevel.VERIFIED, "Plan covers all acceptance criteria", ["AC1 covered", "AC2 covered"], source="plan-review")) print(f" Plan verdict: {report.aggregate_label}") assert report.aggregate == VerdictLevel.VERIFIED # ── Step 3: Build phase produces artifacts ── state.update(backend_port=9105, frontend_port=9205, worktree_path="/tmp/worktrees/adw-abc12345") report.add(Verdict(VerdictLevel.PERFECT, "Build completed, tests pass in worktree", ["exit code 0", "all 14 tests pass"], source="build")) print(f" Build verdict: PERFECT") # ── Step 4: A blocked action (leader tries to write a file) ── no_write_blocker = Blocker( name="no_direct_write", match=lambda a: a.get("tool") in ("write_file", "patch", "execute_code"), reject=lambda a: RejectionEnvelope.block( received=a.get("tool", "unknown"), reason="LEADER_MUST_NOT_WRITE_FILES", allowed="Use delegate_task with a writer subagent", hint="Leaders read, delegate, synthesize. Delegate file writes.", ), ) blocked = no_write_blocker.evaluate({"tool": "write_file", "path": "src/main.py"}) assert blocked is not None, "write_file should be blocked" print(f"✓ Step 4: Action blocked — {blocked.reason}") print(f" Allowed: {blocked.allowed}") # Verify a non-blocked action passes allowed_action = no_write_blocker.evaluate({"tool": "read_file", "path": "state.json"}) assert allowed_action is None, "read_file should NOT be blocked" print("✓ Step 4b: read_file passes through") # ── Step 5: Security review finds gaps (PARTIAL) ── report.add(Verdict(VerdictLevel.PARTIAL, "No vulnerabilities found, but no live penetration test was run", ["static analysis clean", "dependency scan clean", "NOTE: no live pen test"], source="security-review")) print(f" Security verdict: PARTIAL (important: nothing failed, but gap is documented)") # ── Step 6: Check aggregate after all phases ── print(f" Aggregate: {report.aggregate_label}") assert report.aggregate == VerdictLevel.PARTIAL, f"Aggregate should be PARTIAL (lowest of VERIFIED+PERFECT+PARTIAL), got {report.aggregate_label}" assert report.passed == True, "PARTIAL should pass (green but documented gap)" print("✓ Step 6: Aggregate correctly identifies PARTIAL gate") # ── Step 7: State completeness gate before ship ── ship_gate = StateGate( FieldSpec("plan_file", required=True, predicate=lambda v: isinstance(v, str) and v.startswith("plans/")), FieldSpec("branch_name", required=True), FieldSpec("issue_number", required=True, type_hint=int), FieldSpec("backend_port", required=True, type_hint=int), FieldSpec("frontend_port", required=True, type_hint=int), FieldSpec("worktree_path", required=True), label="pre-ship", ) gate_result = ship_gate.validate(state) assert gate_result.passed, f"Complete state should pass ship gate: {gate_result.summary()}" print(f"✓ Step 7: Ship gate passed ({gate_result.fields_checked} fields validated)") # ── Step 8: State gate catches missing fields ── incomplete = PipelineState( {"plan_file": "plans/feat-123.md", "branch_name": "feat/instant-polls"}, core_fields=state._core_fields, ) fail_result = ship_gate.validate(incomplete) assert not fail_result.passed assert "issue_number" in fail_result.missing assert "backend_port" in fail_result.missing print(f"✓ Step 8: Ship gate correctly catches missing: {fail_result.missing}") # ── Step 9: Store expertise about what was learned ── append_entry( "integration-test", domain="pipeline", insight="PARTIAL verdict allows documented gaps to pass without blocking — critical for green-but-dead prevention", source="cron:pipeline-integration-test", verified=True, ) append_entry( "integration-test", domain="state-gate", insight="Required fields must include worktree_path and ports, not just plan_file and branch_name", source="cron:pipeline-integration-test", ) context = render_context("integration-test") assert "PARTIAL" in context assert "worktree_path" in context print("✓ Step 9: Expertise entries stored and loadable") # ── Step 10: JSON serialization of final report ── serialized = report.to_json() restored = VerdictReport.from_json(serialized) assert restored.aggregate == report.aggregate assert len(restored.verdicts) == len(report.verdicts) print("✓ Step 10: Full VerdictReport JSON round-trip") # Cleanup test expertise file purge_domain("integration-test", "pipeline") purge_domain("integration-test", "state-gate") print(f"\n✅ Integration test passed ({10 - errors}/10)") if __name__ == "__main__": sys.exit(0 if errors == 0 else 1)