222 lines
11 KiB
Python
222 lines
11 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Pipeline Demo — end-to-end walkthrough of all pipeline modules.
|
|
|
|
Demonstrates:
|
|
1. Confidence Ladder (5-level Verdict)
|
|
2. Rejection Envelope (4-field blocked action)
|
|
3. State Gate (pre-merge completeness check)
|
|
4. Expertise file (per-cron compounding memory)
|
|
|
|
Run: python -m pipeline.demo
|
|
"""
|
|
import json
|
|
import sys
|
|
import os
|
|
|
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
|
|
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
|
|
|
|
|
|
def hr(title: str):
|
|
print(f"\n{'=' * 60}")
|
|
print(f" {title}")
|
|
print(f"{'=' * 60}")
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
# 1. STATE INIT
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
hr("1. STATE INIT — PipelineState with core_fields")
|
|
|
|
state = PipelineState(
|
|
{
|
|
"plan_file": "plans/feat-123.md",
|
|
"branch_name": "feat/instant-polls",
|
|
"issue_number": 42,
|
|
"rogue_field": "should be silently dropped",
|
|
},
|
|
core_fields=["plan_file", "branch_name", "issue_number", "backend_port", "frontend_port"],
|
|
)
|
|
print(f" plan_file: {state.get('plan_file')}")
|
|
print(f" branch_name: {state.get('branch_name')}")
|
|
print(f" issue_number: {state.get('issue_number')}")
|
|
print(f" rogue dropped: {state.get('rogue_field')}") # should be None
|
|
assert state.get("rogue_field") is None, "rogue fields are silently dropped"
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
# 2. PLAN PHASE — PERFECT
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
hr("2. PLAN PHASE — Verdict: PERFECT")
|
|
|
|
report = VerdictReport(pipeline_id="demo-2026-06-11")
|
|
report.add(Verdict(
|
|
VerdictLevel.PERFECT,
|
|
"Plan covers all acceptance criteria with test coverage map",
|
|
evidence=["AC1 coverage: tests/test_instant_polls.py", "AC2 coverage: tests/test_instant_polls.py"],
|
|
source="plan-review",
|
|
))
|
|
print(f" Ve rdict: {report.aggregate_label}")
|
|
print(f" Pass? {report.passed}")
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
# 3. BUILD PHASE — VERIFIED
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
hr("3. BUILD PHASE — Verdict: VERIFIED")
|
|
|
|
state.update(backend_port=9105, frontend_port=9205)
|
|
report.add(Verdict(
|
|
VerdictLevel.VERIFIED,
|
|
"Build completed, CI confirmed all 14 tests pass",
|
|
evidence=["exit code 0", "CI run #4185: all tests green"],
|
|
source="ci",
|
|
))
|
|
print(f" Verdict: {report.aggregate_label}")
|
|
print(f" Pass? {report.passed}")
|
|
print(f" Verdicts: {len(report.verdicts)} phases so far")
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
# 4. SECURITY REVIEW — PARTIAL (the critical gate)
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
hr("4. SECURITY REVIEW — Verdict: PARTIAL (green-but-dead check)")
|
|
|
|
report.add(Verdict(
|
|
VerdictLevel.PARTIAL,
|
|
"Static analysis clean, dependency scan clean — but no live penetration test was run",
|
|
evidence=["semgrep: 0 findings", "pip audit: 0 known vulns", "NOTE: no live pen test — needs staging env"],
|
|
source="security-review",
|
|
))
|
|
print(f" Verdict: {report.aggregate_label}")
|
|
print(f" Pass? {report.passed} (PARTIAL = green + documented gap)")
|
|
print(f" Blocked for merge? {report.aggregate.is_blocking}")
|
|
print()
|
|
print(" ▶ THIS is the green-but-dead prevention:")
|
|
print(" The pipeline passes (green) but the security gap is")
|
|
print(" documented, not forgotten. The reviewer knows what's missing.")
|
|
assert report.aggregate == VerdictLevel.PARTIAL
|
|
assert report.passed is True
|
|
assert report.aggregate.is_blocking is False
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
# 5. REJECTION ENVELOPE — leader tries to write a file
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
hr("5. REJECTION ENVELOPE — leader blocked from writing files")
|
|
|
|
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.",
|
|
),
|
|
)
|
|
|
|
# This gets blocked
|
|
blocked = blocker.evaluate({"tool": "write_file", "path": "src/main.py"})
|
|
print(f" Action: write_file('src/main.py')")
|
|
print(f" Result: BLOCKED")
|
|
print(f" Reason: {blocked.reason}")
|
|
print(f" Allowed: {blocked.allowed}")
|
|
print(f" Hint: {blocked.hint}")
|
|
print(f" Timestamp: {blocked.timestamp}")
|
|
assert blocked is not None
|
|
|
|
# This passes through
|
|
allowed = blocker.evaluate({"tool": "read_file", "path": "state.json"})
|
|
print(f"\n Action: read_file('state.json')")
|
|
print(f" Result: {'PASSES THROUGH (not blocked)' if allowed is None else 'BLOCKED'}")
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
# 6. STATE GATE — pre-merge completeness check
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
hr("6. STATE GATE — pre-merge completeness check")
|
|
|
|
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),
|
|
label="pre-ship",
|
|
)
|
|
|
|
# Complete state should pass
|
|
result = ship_gate.validate(state)
|
|
print(f" Fields checked: {result.fields_checked}")
|
|
print(f" Pass: {result.passed}")
|
|
if result.missing:
|
|
print(f" Missing: {result.missing}")
|
|
assert result.passed, "Complete state should pass ship gate"
|
|
|
|
# Incomplete state should fail
|
|
incomplete = PipelineState(
|
|
{"plan_file": "plans/feat-123.md", "branch_name": "feat/instant-polls"},
|
|
core_fields=["plan_file", "branch_name", "issue_number", "backend_port", "frontend_port"],
|
|
)
|
|
fail_result = ship_gate.validate(incomplete)
|
|
print(f"\n --- With missing fields ---")
|
|
print(f" Pass: {fail_result.passed}")
|
|
print(f" Missing: {fail_result.missing}")
|
|
assert not fail_result.passed
|
|
assert "issue_number" in fail_result.missing
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
# 7. EXPERTISE — store what was learned
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
hr("7. EXPERTISE — store what the run learned")
|
|
|
|
append_entry(
|
|
"demo-pipeline",
|
|
domain="pipeline",
|
|
insight="PARTIAL verdict is the key innovation — it lets documented gaps pass without blocking, preventing green-but-dead",
|
|
source="cron:pipeline-demo",
|
|
verified=True,
|
|
)
|
|
append_entry(
|
|
"demo-pipeline",
|
|
domain="confidence-ladder",
|
|
insight="VERIFIED level requires independent confirmation (CI, review) — the agent trusting itself is only PERFECT",
|
|
source="cron:pipeline-demo",
|
|
verified=True,
|
|
)
|
|
append_entry(
|
|
"demo-pipeline",
|
|
domain="state-gate",
|
|
insight="Core_fields filter ensures no rogue keys leak into pipeline state — strict struct, not a dict",
|
|
source="cron:pipeline-demo",
|
|
verified=False,
|
|
)
|
|
|
|
print(render_context("demo-pipeline"))
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
# 8. JSON PORTABILITY
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
hr("8. JSON PORTABILITY — serializable for cron/API delivery")
|
|
|
|
serialized = report.to_json()
|
|
restored = VerdictReport.from_json(serialized)
|
|
print(f" Round-trip aggregate matches: {restored.aggregate == report.aggregate}")
|
|
print(f" Verdict count: {len(restored.verdicts)}")
|
|
print(f"\n Final JSON payload (compact):")
|
|
print(f" {json.dumps(json.loads(serialized), indent=2)}")
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
# CLEANUP
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
purge_domain("demo-pipeline", "pipeline")
|
|
purge_domain("demo-pipeline", "confidence-ladder")
|
|
purge_domain("demo-pipeline", "state-gate")
|
|
|
|
if __name__ == "__main__":
|
|
|
|
print(f"\n{'=' * 60}")
|
|
print(f" ✅ DEMO COMPLETE — all 8 stages verified")
|
|
print(f"{'=' * 60}")
|