From 34b331c242f5972ad743657eb00a3c223c950dcd Mon Sep 17 00:00:00 2001 From: Caleb <46907094+rotationalphysics495@users.noreply.github.com> Date: Thu, 4 Jun 2026 05:44:40 -0500 Subject: [PATCH] feat(epic-execute): wire contract validation gate + self-heal fix loop MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Final piece of the contract execution increment. - run_contract_validation: env up → backend cases + UI flows → env down - contract_validation_gate: bounded self-heal loop (execute_contract_fix_phase feeds CONTRACT_EXEC_FAILURES back to a focused fix prompt, commits, re-runs) - Wired as a per-epic gate after the story loop (v1 granularity: the app reflects the whole epic before contracts run), opt-in by harness presence and --skip-contract-validation - Exit-code-honest: CONTRACT_VALIDATION_FAILED makes the epic exit non-zero if contracts never pass, mirroring the preflight gate Tested: orchestrator brings the env up/down and runs backend cases against a live mock server with correct pass/fail + failure detail. Live UI flows and the fix loop's Claude calls need the real app/CLI. This completes the UI-contract + execution work held on this branch; ready to bundle into one PR. Co-Authored-By: Claude Opus 4.8 (1M context) --- scripts/epic-execute-lib/contract-exec.sh | 115 ++++++++++++++++++++++ scripts/epic-execute.sh | 23 +++++ 2 files changed, 138 insertions(+) diff --git a/scripts/epic-execute-lib/contract-exec.sh b/scripts/epic-execute-lib/contract-exec.sh index 27c34782b..9aeb32bd7 100644 --- a/scripts/epic-execute-lib/contract-exec.sh +++ b/scripts/epic-execute-lib/contract-exec.sh @@ -21,6 +21,10 @@ CONTRACT_EXEC_FAILURES="" # PID of the app started by contract_env_up (so contract_env_down can stop it). CONTRACT_APP_PID="" +# Set true when contract validation ultimately fails (drives the epic exit code). +CONTRACT_VALIDATION_FAILED=false +# Max self-heal attempts for contract failures. +MAX_CONTRACT_FIX_ATTEMPTS="${MAX_CONTRACT_FIX_ATTEMPTS:-2}" # ============================================================================= # Environment Lifecycle @@ -349,3 +353,114 @@ run_ui_flows() { log_error " UI flows failed" return 1 } + +# ============================================================================= +# Orchestration + Fix Loop +# ============================================================================= + +# Run the full contract validation: bring the env up, run backend cases and UI +# flows, tear the env down. Detail of any failures lands in CONTRACT_EXEC_FAILURES. +# Returns 0 if everything passes, 1 otherwise. +run_contract_validation() { + local h="$1" + CONTRACT_EXEC_FAILURES="" + + if [ "${DRY_RUN:-false}" = true ]; then + echo "[DRY RUN] Would run contract validation (backend cases + UI flows)" + return 0 + fi + + log ">>> CONTRACT VALIDATION: bringing up sample environment" + if ! contract_env_up "$h"; then + contract_env_down "$h" + CONTRACT_EXEC_FAILURES="- sample environment failed to start"$'\n' + return 1 + fi + + local rc=0 + run_backend_cases "$h" || rc=1 + run_ui_flows "$h" || rc=1 + + contract_env_down "$h" + return $rc +} + +# A focused fix pass for contract validation failures (epic-level self-heal, +# mirroring the traceability fix loop). Returns 0 if the model reports success. +execute_contract_fix_phase() { + local failures="$1" + local attempt="$2" + + log ">>> CONTRACT FIX: attempt $attempt (addressing contract validation failures)" + + local fix_prompt="You are fixing failures found by automated contract validation - real API calls and browser flows run against a running app. + +## Failures to Fix + + +$failures + + +## Rules +- Fix the IMPLEMENTATION so these contract checks pass +- Do NOT weaken, skip, or delete the contract checks themselves +- Do NOT use 'git add -A' or 'git add .' - stage explicit paths +- This is attempt $attempt of $MAX_CONTRACT_FIX_ATTEMPTS + +## Completion Signal +When done, output exactly: CONTRACT FIX COMPLETE +If you cannot fix everything, output: CONTRACT FIX INCOMPLETE: " + + if [ "${DRY_RUN:-false}" = true ]; then + echo "[DRY RUN] Would run contract fix (attempt $attempt)" + return 0 + fi + + run_claude_to_file "$fix_prompt" + local result + result=$(read_phase_tail) + + if echo "$result" | grep -q "CONTRACT FIX COMPLETE"; then + log_success "Contract fix reported complete (attempt $attempt)" + return 0 + fi + log_warn "Contract fix incomplete (attempt $attempt)" + return 1 +} + +# Top-level gate: run contract validation with a bounded self-heal loop. +# Non-blocking for the run itself, but sets CONTRACT_VALIDATION_FAILED so the +# epic exits non-zero if contracts never pass. +contract_validation_gate() { + local h="$1" + + local attempt=0 + while true; do + if run_contract_validation "$h"; then + log_success "Contract validation passed" + return 0 + fi + + if [ -z "$CONTRACT_EXEC_FAILURES" ]; then + log_warn "Contract validation failed without captured detail - continuing" + CONTRACT_VALIDATION_FAILED=true + return 1 + fi + + attempt=$((attempt + 1)) + if [ "$attempt" -gt "$MAX_CONTRACT_FIX_ATTEMPTS" ]; then + log_error "Contract validation still failing after $MAX_CONTRACT_FIX_ATTEMPTS fix attempt(s)" + type add_metrics_issue >/dev/null 2>&1 && add_metrics_issue "epic-${EPIC_ID:-?}" "contract_validation_failed" "Contract checks failing after $MAX_CONTRACT_FIX_ATTEMPTS attempts" + CONTRACT_VALIDATION_FAILED=true + return 1 + fi + + log_warn "Contract failures found, attempting fix $attempt of $MAX_CONTRACT_FIX_ATTEMPTS" + execute_contract_fix_phase "$CONTRACT_EXEC_FAILURES" "$attempt" || true + + if [ "${NO_COMMIT:-false}" = false ] && [ "${DRY_RUN:-false}" = false ]; then + git -C "$PROJECT_ROOT" add -u 2>/dev/null || true + git -C "$PROJECT_ROOT" commit -m "fix(epic-${EPIC_ID:-0}): contract validation fixes (attempt $attempt)" 2>/dev/null || true + fi + done +} diff --git a/scripts/epic-execute.sh b/scripts/epic-execute.sh index 6cba74beb..a45818a17 100755 --- a/scripts/epic-execute.sh +++ b/scripts/epic-execute.sh @@ -133,6 +133,7 @@ LIB_DIR="$SCRIPT_DIR/epic-execute-lib" [ -f "$LIB_DIR/json-output.sh" ] && source "$LIB_DIR/json-output.sh" [ -f "$LIB_DIR/tdd-flow.sh" ] && source "$LIB_DIR/tdd-flow.sh" [ -f "$LIB_DIR/contract-harness.sh" ] && source "$LIB_DIR/contract-harness.sh" +[ -f "$LIB_DIR/contract-exec.sh" ] && source "$LIB_DIR/contract-exec.sh" STORIES_DIR="$PROJECT_ROOT/docs/stories" SPRINT_ARTIFACTS_DIR="$PROJECT_ROOT/docs/sprint-artifacts" @@ -3192,6 +3193,21 @@ for story_file in "${STORIES[@]}"; do fi done +# ============================================================================= +# Contract Validation (Per-Epic: execute the harness against the live app) +# ============================================================================= +# v1 granularity: runs once after all stories are implemented (the app reflects +# the full epic). Brings the sample env up, runs backend cases + UI flows, and +# self-heals via a bounded fix loop. Non-blocking mid-run, but sets the epic +# exit code if contracts never pass. +if [ "$SKIP_CONTRACT_VALIDATION" != true ] && [ -n "${CONTRACT_HARNESS_FILE:-}" ] && type contract_validation_gate >/dev/null 2>&1; then + echo "" + log "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + log "Contract Validation (API + UI)" + log "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + contract_validation_gate "$CONTRACT_HARNESS_FILE" || true +fi + # ============================================================================= # Traceability Check (Per-Epic, with Self-Healing) # ============================================================================= @@ -3288,6 +3304,13 @@ if [ "${PREFLIGHT_FAILED:-false}" = true ]; then exit 1 fi +# Contract validation is an exit-code-honest gate: if API/UI contracts never +# passed (after self-heal attempts), fail the epic. +if [ "${CONTRACT_VALIDATION_FAILED:-false}" = true ]; then + log_warn "Contract validation did not pass - see failures above" + exit 1 +fi + if [ $FAILED -gt 0 ]; then log_warn "$FAILED stories failed - check log for details" log "Checkpoint preserved for resume capability"