feat(epic-execute): wire contract validation gate + self-heal fix loop
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) <noreply@anthropic.com>
This commit is contained in:
parent
c45d4fa875
commit
34b331c242
|
|
@ -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>
|
||||
$failures
|
||||
</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: <what remains>"
|
||||
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
Loading…
Reference in New Issue