#!/usr/bin/env bash # Disposable runtime restored from Phase 5; primary server is never selected. set -euo pipefail cd "$(dirname "${BASH_SOURCE[0]}")/.." evidence="$PWD/.tools/phase6-evidence" root="$PWD/.tools/phase6-server" loop_root="$PWD/.tools/phase6-loop-server" mkdir -p "$evidence" [[ ! -e "$root" && ! -e "$loop_root" ]] || { echo 'Phase 6 fixtures already exist; use new roots to repeat.' >&2; exit 1; } snapshot=$(cat .tools/phase5-evidence/snapshot-path.txt) ./mcpack restore --backup "$snapshot" --destination "$root" export MC_ROOT="$root" MC_RELEASE_DIR="$root/release" cp .tools/phase4-server/.env "$root/.env" chmod 600 "$root/.env" # A real cold JVM start cannot finish within two seconds. if scripts/server.sh up --wait --wait-timeout 2 --stable-seconds 1 > "$evidence/timeout.txt" 2>&1; then echo 'Short startup timeout unexpectedly passed' >&2; exit 1 fi scripts/server.sh health --wait-timeout 120 --stable-seconds 3 > "$evidence/healthy.txt" 2>&1 scripts/server.sh logs --tail 100 > "$evidence/healthy-start.log" # Abrupt termination only affects this disposable copied world. docker kill --signal KILL minecraft-lab-phase4-minecraft-1 > "$evidence/kill.txt" if scripts/server.sh health --wait-timeout 30 --stable-seconds 2 > "$evidence/crash.txt" 2>&1; then echo 'Crashed container unexpectedly healthy' >&2; exit 1 fi scripts/server.sh up --wait --wait-timeout 120 --stable-seconds 3 > "$evidence/recovered.txt" 2>&1 scripts/server.sh down # Deliberately broken Compose fixture, separate from immutable public release. mkdir -p "$loop_root/release" cp -a "$MC_RELEASE_DIR/." "$loop_root/release/" cp "$root/.env" "$loop_root/.env" LOOP_ROOT="$loop_root" node --input-type=module <<'JS' import fs from 'node:fs'; import crypto from 'node:crypto'; import {parse,stringify} from 'yaml'; const release=process.env.LOOP_ROOT+'/release'; const composePath=release+'/server/compose.yaml'; const compose=parse(fs.readFileSync(composePath,'utf8')); compose.services.minecraft.restart='on-failure:5'; compose.services.minecraft.entrypoint=['/bin/sh','-c','echo phase6-intentional-restart-loop; sleep 1; exit 42']; fs.writeFileSync(composePath,stringify(compose)); const hash=file=>crypto.createHash('sha256').update(fs.readFileSync(release+'/'+file)).digest('hex'); const manifest=JSON.parse(fs.readFileSync(release+'/manifest.json')); manifest.pack.version='phase6-failure-fixture'; for(const file of manifest.release_files)file.sha256=hash(file.path); fs.writeFileSync(release+'/manifest.json',JSON.stringify(manifest,null,2)+'\n'); const files=[...manifest.release_files.map(file=>file.path),'manifest.json'].sort(); fs.writeFileSync(release+'/SHA256SUMS',files.map(file=>`${hash(file)} ${file}\n`).join('')); JS export MC_ROOT="$loop_root" MC_RELEASE_DIR="$loop_root/release" scripts/server.sh up -d # Wait until the real Docker container has restarted at least once. for attempt in {1..20}; do count=$(docker inspect minecraft-lab-phase4-minecraft-1 --format '{{.RestartCount}}') if [[ "$count" -ge 1 ]]; then break; fi sleep 0.2 done docker inspect minecraft-lab-phase4-minecraft-1 --format '{{json .State}} {{.RestartCount}}' > "$evidence/loop-docker-state.txt" if scripts/server.sh health --wait-timeout 15 --stable-seconds 2 > "$evidence/restart-loop.txt" 2>&1; then echo 'Restart loop unexpectedly healthy' >&2; exit 1 fi scripts/server.sh down ROOT="$root" LOOP_ROOT="$loop_root" node --input-type=module <<'JS' > "$evidence/assertions.txt" import fs from 'node:fs'; import assert from 'node:assert/strict'; const reports=root=>fs.readdirSync(root+'/diagnostics').filter(name=>name.endsWith('.json')).map(name=>JSON.parse(fs.readFileSync(root+'/diagnostics/'+name))); const normal=reports(process.env.ROOT),loop=reports(process.env.LOOP_ROOT); assert(normal.some(report=>report.reason==='timeout')); assert(normal.some(report=>report.reason==='crash'&&report.state.exit_code===137)); assert(loop.some(report=>report.reason==='restart-loop')); for(const root of [process.env.ROOT,process.env.LOOP_ROOT]){ assert(!fs.existsSync(root+'/.mcpack-runtime.lock')); assert(fs.existsSync(root+'/.mcpack-runtime.json')); for(const report of reports(root)){ assert(fs.existsSync(report.logs)); assert.equal(report.recovery.automatic_rollback,false); assert.equal(report.recovery.runtime_preserved,true); } } assert(fs.existsSync(process.env.ROOT+'/runtime/mod-state/phase5.json')); console.log('Real success, timeout, crash (exit 137), restart-loop, diagnostics and runtime/binding preservation passed.'); JS scripts/spike-server.sh ps > "$evidence/primary-server-status.txt" printf 'Phase 6 integration passed.\n'