e044a2de0d
---ci--- project: acdl phase: 6 milestone: v1.1 status: plan-as-execute persona: lead-developer tasks: [T-6.1, T-6.2, T-6.3, T-6.4] ---/ci--- Archive the v1.0 demo under demo/ (D-037) and reorient the repo to the real platform. Wave 1 of the Phase 06 plan. - T-6.1: git mv modules/, scripts/, evidence-ui/, contracts/, contracts-repo/, .gitea/ -> demo/; mv ACDL_DEMO.md + runner-data/ -> demo/ - T-6.2: scaffold new v1.1 top-level dirs (platform/, schemas/, adapters/, terraform/, modules-ir/) with .gitkeep - T-6.3: create top-level scripts/verify_phase06.sh (v1.1 verify scripts live at top-level, NOT demo/scripts/ which holds the v1.0 demo verify scripts) - T-6.4: rewrite README.md to reflect the real platform (vision + architecture links, new layout, status v1.1 active); add runner-data/ to .gitignore All moves via git mv (history preserved). Repo root now contains only README.md, demo/, docs/, .ciagent/, and the new empty v1.1 dirs.
186 lines
7.5 KiB
Bash
Executable File
186 lines
7.5 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Phase 04 verification script.
|
|
# Confirms the pipeline workflow + issue trigger + finalize_evidence.py
|
|
# conform to the Phase 04 plan and the Gitea Actions topology in
|
|
# ARCHITECTURE.md. Does NOT execute a real Gitea Actions run (act_runner
|
|
# is not registered in this environment); validates structure + syntax
|
|
# + a dry-run of finalize_evidence.py against a dead host.
|
|
#
|
|
# Usage: scripts/verify_phase04.sh
|
|
# Exit codes: 0 = all checks passed; 1 = one or more checks failed.
|
|
|
|
set -uo pipefail
|
|
|
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
cd "$ROOT"
|
|
|
|
fail_count=0
|
|
pass() { printf ' [PASS] %s\n' "$1"; }
|
|
fail() { printf ' [FAIL] %s\n' "$1"; fail_count=$((fail_count + 1)); }
|
|
|
|
echo "== Phase 04 verification =="
|
|
echo "Root: ${ROOT}"
|
|
echo
|
|
|
|
# --- Check 1: typecheck ---
|
|
echo "-- Check 1: typecheck --"
|
|
if bash -n scripts/finalize_evidence.py 2>/dev/null || python3 -m py_compile scripts/finalize_evidence.py 2>/dev/null; then
|
|
pass "py_compile finalize_evidence.py"
|
|
else
|
|
fail "py_compile finalize_evidence.py"
|
|
fi
|
|
if python3 -c "import yaml; yaml.safe_load(open('.gitea/workflows/pipeline.yml')); yaml.safe_load(open('contracts-repo/.gitea/workflows/issue-to-contract.yml'))" 2>/dev/null; then
|
|
pass "yaml load both workflows"
|
|
else
|
|
fail "yaml load workflows"
|
|
fi
|
|
|
|
# --- Check 2: pipeline.yml structure ---
|
|
echo "-- Check 2: pipeline.yml structure (D-027, D-028) --"
|
|
p_struct=$(python3 << 'PYEOF'
|
|
import yaml, sys
|
|
try:
|
|
d = yaml.safe_load(open('.gitea/workflows/pipeline.yml'))
|
|
on = d.get('on', d.get(True)) or {}
|
|
assert 'workflow_dispatch' in on, 'no workflow_dispatch trigger'
|
|
inputs = on['workflow_dispatch']['inputs']
|
|
assert set(inputs.keys()) == {'contract-ref', 'approve_qa', 'approve_prod'}, f'inputs: {set(inputs.keys())}'
|
|
assert inputs['contract-ref']['type'] == 'string', 'contract-ref type'
|
|
assert inputs['approve_qa']['type'] == 'boolean', 'approve_qa type'
|
|
assert inputs['approve_prod']['type'] == 'boolean', 'approve_prod type'
|
|
jobs = d['jobs']
|
|
assert set(jobs.keys()) == {'dev', 'qa-gate', 'prod-gate', 'finalize'}, f'jobs: {set(jobs.keys())}'
|
|
dev_if = jobs['dev'].get('if', '')
|
|
assert 'approve_qa' in dev_if and 'approve_prod' in dev_if, f'dev.if: {dev_if}'
|
|
qa_if = jobs['qa-gate'].get('if', '')
|
|
assert 'approve_qa' in qa_if, f'qa-gate.if: {qa_if}'
|
|
prod_if = jobs['prod-gate'].get('if', '')
|
|
assert 'approve_prod' in prod_if, f'prod-gate.if: {prod_if}'
|
|
fin_needs = jobs['finalize'].get('needs', [])
|
|
assert fin_needs == ['prod-gate'] or fin_needs == 'prod-gate', f'finalize.needs: {fin_needs}'
|
|
# All jobs runs-on ubuntu-latest
|
|
for name, job in jobs.items():
|
|
assert job.get('runs-on') == 'ubuntu-latest', f'{name} runs-on: {job.get("runs-on")}'
|
|
print('OK')
|
|
except AssertionError as ex:
|
|
print(f'FAIL: {ex}')
|
|
sys.exit(1)
|
|
except Exception as ex:
|
|
print(f'FAIL: {ex}')
|
|
sys.exit(1)
|
|
PYEOF
|
|
)
|
|
if [ "$p_struct" = "OK" ]; then
|
|
pass "pipeline.yml: 3 inputs + 4 jobs + correct if: conditions + finalize.needs=prod-gate"
|
|
else
|
|
fail "pipeline.yml structure: $p_struct"
|
|
fi
|
|
|
|
# --- Check 3: pipeline.yml references core scripts ---
|
|
echo "-- Check 3: pipeline.yml references core scripts (D-029) --"
|
|
text=$(cat .gitea/workflows/pipeline.yml)
|
|
missing=""
|
|
for ref in policy_checker.py confidence_signal.py mock_executor.sh evidence_writer.py finalize_evidence.py; do
|
|
if ! echo "$text" | grep -qF "$ref"; then
|
|
missing="$missing $ref"
|
|
fi
|
|
done
|
|
if [ -z "$missing" ]; then
|
|
pass "pipeline.yml references all 5 core scripts"
|
|
else
|
|
fail "pipeline.yml missing references:$missing"
|
|
fi
|
|
# Branch-pin documentation
|
|
if echo "$text" | grep -q 'milestone/v1.0-initial'; then
|
|
pass "pipeline.yml documents branch-pin to milestone/v1.0-initial"
|
|
else
|
|
fail "pipeline.yml missing branch-pin reference"
|
|
fi
|
|
|
|
# --- Check 4: issue-to-contract.yml structure ---
|
|
echo "-- Check 4: issue-to-contract.yml structure (D-030) --"
|
|
i_struct=$(python3 << 'PYEOF'
|
|
import yaml, sys
|
|
try:
|
|
d = yaml.safe_load(open('contracts-repo/.gitea/workflows/issue-to-contract.yml'))
|
|
on = d.get('on', d.get(True)) or {}
|
|
assert 'issues' in on, 'no issues trigger'
|
|
assert on['issues']['types'] == ['opened'], f'types: {on["issues"]["types"]}'
|
|
assert 'parse-and-trigger' in d['jobs'], 'no parse-and-trigger job'
|
|
assert d['jobs']['parse-and-trigger'].get('runs-on') == 'ubuntu-latest', 'runs-on'
|
|
print('OK')
|
|
except AssertionError as ex:
|
|
print(f'FAIL: {ex}')
|
|
sys.exit(1)
|
|
PYEOF
|
|
)
|
|
if [ "$i_struct" = "OK" ]; then
|
|
pass "issue-to-contract.yml: issues[opened] + parse-and-trigger job"
|
|
else
|
|
fail "issue-to-contract.yml structure: $i_struct"
|
|
fi
|
|
|
|
# --- Check 5: issue-to-contract.yml references + dispatch endpoint ---
|
|
echo "-- Check 5: issue-to-contract.yml references + dispatch (D-014, D-030) --"
|
|
text=$(cat contracts-repo/.gitea/workflows/issue-to-contract.yml)
|
|
missing=""
|
|
for ref in l3b_agent_stub.py 'actions/workflows/pipeline.yml/dispatches' 'contract-ref' 'gitea.event.issue.number' 'GITEA_TOKEN' 'new_branch'; do
|
|
if ! echo "$text" | grep -qF "$ref"; then
|
|
missing="$missing $ref"
|
|
fi
|
|
done
|
|
if [ -z "$missing" ]; then
|
|
pass "issue-to-contract.yml: l3b_agent_stub + dispatch + contract-ref + issue number + token + new_branch"
|
|
else
|
|
fail "issue-to-contract.yml missing references:$missing"
|
|
fi
|
|
|
|
# --- Check 6: finalize_evidence.py --help + clean failure ---
|
|
echo "-- Check 6: finalize_evidence.py CLI + clean failure modes ---"
|
|
out=$(python3 scripts/finalize_evidence.py --help 2>&1); rc=$?
|
|
if [ "$rc" = "0" ] && echo "$out" | grep -qi 'usage\|--audit\|--owner'; then
|
|
pass "finalize_evidence.py --help exits 0 with usage"
|
|
else
|
|
fail "finalize_evidence.py --help: rc=$rc"
|
|
fi
|
|
|
|
# Missing audit file (with a fake token so it gets past the env check) → exit 1, no stack trace
|
|
out=$(ACDL_GITEA_TOKEN=fake python3 scripts/finalize_evidence.py --audit /tmp/definitely_nonexistent_audit.json 2>&1); rc=$?
|
|
if [ "$rc" = "1" ] && ! echo "$out" | grep -q 'Traceback'; then
|
|
pass "finalize_evidence.py missing file → exit 1, no stack trace"
|
|
else
|
|
fail "finalize_evidence.py missing file: rc=$rc, out='$out'"
|
|
fi
|
|
|
|
# Missing token env (audit file present) → exit 1, no stack trace
|
|
printf '[]\n' > /tmp/empty_audit.json
|
|
out=$(env -u ACDL_GITEA_TOKEN python3 scripts/finalize_evidence.py --audit /tmp/empty_audit.json 2>&1); rc=$?
|
|
if [ "$rc" = "1" ] && ! echo "$out" | grep -q 'Traceback'; then
|
|
pass "finalize_evidence.py missing token env → exit 1, no stack trace"
|
|
else
|
|
fail "finalize_evidence.py missing token: rc=$rc, out='$out'"
|
|
fi
|
|
|
|
# --- Check 7: finalize_evidence.py dry-run against a dead host (clean failure) ---
|
|
echo "-- Check 7: finalize_evidence.py dry-run against dead host ---"
|
|
# Use a real audit.json but point at a host that will refuse the connection.
|
|
printf '[{"seq":0,"ts":"2026-07-21T00:00:00Z","stage":"genesis","event":"init","prev_hash":"GENESIS","hash":"x"}]\n' > /tmp/real_audit.json
|
|
out=$(ACDL_GITEA_TOKEN=fake GITEA_HOST=http://127.0.0.1:0 python3 scripts/finalize_evidence.py --audit /tmp/real_audit.json --host http://127.0.0.1:0 2>&1); rc=$?
|
|
if [ "$rc" = "1" ] && ! echo "$out" | grep -q 'Traceback'; then
|
|
pass "finalize_evidence.py dead host → exit 1, no stack trace (clean API failure)"
|
|
else
|
|
fail "finalize_evidence.py dead host: rc=$rc, out='$out'"
|
|
fi
|
|
|
|
# Cleanup
|
|
rm -f /tmp/empty_audit.json /tmp/real_audit.json
|
|
|
|
echo
|
|
echo "== Summary =="
|
|
if [ "$fail_count" -eq 0 ]; then
|
|
echo "Phase 04 verification PASSED (pipeline + issue trigger + finalize helper, all checks ok)"
|
|
exit 0
|
|
else
|
|
echo "Phase 04 verification FAILED (${fail_count} check(s) failed)"
|
|
exit 1
|
|
fi |