#!/usr/bin/env bash set -euo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd -P)" SCENARIO_ROOT="$ROOT/.agents/skills/blacksite-native-qa/references/scenarios" STATE_ROOT="$ROOT/.codex/session" EVIDENCE_ROOT="$ROOT/.codex/evidence" usage() { echo "usage: $0 plan|launch|attach|capture|record|status|restore|close [arguments]" >&2 exit 2 } [[ $# -ge 2 ]] || usage ACTION="$1" NAME="$2" SCENARIO="$SCENARIO_ROOT/$NAME.yaml" [[ -f "$SCENARIO" ]] || { echo "native-qa: unknown scenario $NAME" >&2; exit 2; } field() { local key="$1" sed -n "s/^${key}:[[:space:]]*//p" "$SCENARIO" | head -n 1 } LANE="$(field build_lane)" [[ -n "$LANE" ]] || LANE="dev" PACKAGE="$(field package)" [[ -n "$PACKAGE" ]] || PACKAGE="editor" BINARY_NAME="$(field binary)" [[ -n "$BINARY_NAME" ]] || BINARY_NAME="editor" FIXTURE="$(field fixture)" mkdir -p "$STATE_ROOT" "$EVIDENCE_ROOT" lane_json() { python "$ROOT/scripts/codex/cargo_lane.py" env "$LANE" --json } list_section() { local section="$1" awk -v section="$section" ' $0 == section ":" { active = 1; next } active && /^[^[:space:]]/ { exit } active && /^[[:space:]]+-[[:space:]]+/ { sub(/^[[:space:]]+-[[:space:]]+/, "") print } ' "$SCENARIO" } snapshot_fixture() { local record="$STATE_ROOT/native-$NAME.json" local snapshot="$STATE_ROOT/native-$NAME.fixture.snapshot" python - "$ROOT" "$FIXTURE" "$record" "$snapshot" "$LANE" "$SCENARIO" <<'PY' import hashlib, json, os, pathlib, shutil, sys, time root = pathlib.Path(sys.argv[1]).resolve() fixture_text, record_text, snapshot_text, lane, scenario = sys.argv[2:] record = pathlib.Path(record_text) snapshot = pathlib.Path(snapshot_text) if record.is_file(): previous = json.loads(record.read_text(encoding="utf-8")) pid = previous.get("pid") if pid: try: os.kill(int(pid), 0) except ProcessLookupError: pass except PermissionError: raise SystemExit(f"native-qa: cannot verify existing runner PID {pid}; refusing launch") else: raise SystemExit(f"native-qa: existing runner PID {pid} is still active") old_fixture = previous.get("fixture") if old_fixture and not old_fixture.get("restored"): raise SystemExit("native-qa: restore the previous fixture before launching this scenario again") record.unlink() snapshot.unlink(missing_ok=True) data = { "pid": None, "lane": lane, "scenario": scenario, "spawned_by_runner": False, "prepared_at": time.time(), "assertions": {}, "evidence": [], } if fixture_text: fixture = (root / fixture_text).resolve() if fixture != root and root not in fixture.parents: raise SystemExit(f"fixture escapes workspace: {fixture}") if fixture.exists() and not fixture.is_file(): raise SystemExit(f"fixture is not a regular file: {fixture}") existed = fixture.is_file() baseline = hashlib.sha256(fixture.read_bytes()).hexdigest() if existed else None if existed: shutil.copy2(fixture, snapshot) data["fixture"] = { "path": str(fixture), "existed": existed, "baseline_sha256": baseline, "snapshot": str(snapshot) if existed else None, "restored": False, } record.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8") PY } case "$ACTION" in plan) LANE_JSON="$(lane_json)" PACKAGE="$PACKAGE" BINARY_NAME="$BINARY_NAME" python - "$SCENARIO" "$FIXTURE" <<'PY' import json, os, pathlib, sys layout = json.loads(os.environ["LANE_JSON"]) scenario = pathlib.Path(sys.argv[1]) fixture = sys.argv[2] binary = pathlib.Path(layout["target_dir"]) / layout["profile_dir"] / os.environ["BINARY_NAME"] print(f"PASS native-qa-plan — {scenario.stem}") print(f"Lane: {layout['lane']} ({layout['mode']})") print(f"Package: {os.environ['PACKAGE']}") print(f"Binary: {binary}") print(f"Runtime deps: {layout['runtime_deps']}") print(f"Fixture: {fixture or 'scenario-defined'}") print("Visual interaction remains manual/user-controlled until explicitly delegated.") PY echo "Purpose: $(field purpose)" echo "Preconditions: $(field preconditions)" echo "Steps:" list_section steps | nl -w2 -s'. ' echo "Assertions:" list_section assertions | nl -w2 -s'. ' echo "Evidence: $(field evidence)" echo "Cleanup: $(field cleanup)" ;; launch) snapshot_fixture python "$ROOT/scripts/codex/build_storage.py" enforce --phase pre python "$ROOT/scripts/codex/summarize_command.py" \ --gate "native-$NAME-build" \ --log "$ROOT/.codex/logs/native-$NAME-build.log" \ --json-result "$ROOT/.codex/logs/native-$NAME-build.json" \ -- python "$ROOT/scripts/codex/cargo_lane.py" exec "$LANE" -- cargo build -p "$PACKAGE" --bin "$BINARY_NAME" python "$ROOT/scripts/codex/build_storage.py" enforce --phase post LAUNCH_ARGS=() if [[ "$BINARY_NAME" = "editor" ]]; then LAUNCH_ARGS=(--project "$ROOT") fi nohup python "$ROOT/scripts/codex/cargo_lane.py" run "$LANE" -- \ "target/debug/$BINARY_NAME" "${LAUNCH_ARGS[@]}" \ >"$ROOT/.codex/logs/native-$NAME-launch.log" 2>&1 & PID=$! python - "$STATE_ROOT/native-$NAME.json" "$PID" <<'PY' import json, pathlib, sys, time path = pathlib.Path(sys.argv[1]) data = json.loads(path.read_text(encoding="utf-8")) data.update({"pid": int(sys.argv[2]), "spawned_at": time.time(), "spawned_by_runner": True}) path.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8") PY echo "PASS native-qa-launch — $NAME — PID $PID" ;; attach) [[ $# -eq 3 ]] || usage PID="$3" kill -0 "$PID" 2>/dev/null || { echo "native-qa: PID $PID is not running" >&2; exit 2; } CMDLINE="$(tr '\0' ' ' <"/proc/$PID/cmdline")" [[ "$CMDLINE" == *"$BINARY_NAME"* ]] || { echo "native-qa: refusing unexpected process: $CMDLINE" >&2; exit 2; } snapshot_fixture python - "$STATE_ROOT/native-$NAME.json" "$PID" <<'PY' import json, pathlib, sys, time path = pathlib.Path(sys.argv[1]) data = json.loads(path.read_text(encoding="utf-8")) data.update({"pid": int(sys.argv[2]), "attached_at": time.time(), "spawned_by_runner": False}) path.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8") PY echo "PASS native-qa-attach — $NAME — PID $PID" ;; capture) [[ $# -eq 3 ]] || usage PID="$3" kill -0 "$PID" 2>/dev/null || { echo "native-qa: PID $PID is not running" >&2; exit 2; } CLIENTS="$(hyprctl clients -j)" GEOMETRY="$(CLIENTS_JSON="$CLIENTS" python - "$PID" <<'PY' import json, os, sys pid = int(sys.argv[1]) matches = [c for c in json.loads(os.environ["CLIENTS_JSON"]) if c.get("pid") == pid] if len(matches) != 1: raise SystemExit(f"expected one Hyprland window for PID {pid}, found {len(matches)}") client = matches[0] x, y = client["at"] w, h = client["size"] if w <= 0 or h <= 0: raise SystemExit("target window has invalid geometry") print(f"{x},{y} {w}x{h}") PY )" STAMP="$(date -u +%Y%m%dT%H%M%SZ)" IMAGE="$EVIDENCE_ROOT/$NAME-$STAMP.png" grim -g "$GEOMETRY" "$IMAGE" HASH="$(sha256sum "$IMAGE" | awk '{print $1}')" python - "$STATE_ROOT/native-$NAME-evidence.json" "$PID" "$IMAGE" "$HASH" "$GEOMETRY" <<'PY' import json, pathlib, sys, time path = pathlib.Path(sys.argv[1]) path.write_text(json.dumps({ "pid": int(sys.argv[2]), "image": sys.argv[3], "sha256": sys.argv[4], "geometry": sys.argv[5], "captured_at": time.time(), }, indent=2) + "\n", encoding="utf-8") PY python - "$STATE_ROOT/native-$NAME.json" "$IMAGE" "$HASH" "$GEOMETRY" <<'PY' import json, pathlib, sys, time path = pathlib.Path(sys.argv[1]) if not path.is_file(): raise SystemExit("native-qa: missing runner record") data = json.loads(path.read_text(encoding="utf-8")) data.setdefault("evidence", []).append({ "image": sys.argv[2], "sha256": sys.argv[3], "geometry": sys.argv[4], "captured_at": time.time(), }) path.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8") PY echo "PASS native-qa-capture — $NAME — $IMAGE — $HASH" ;; record) [[ $# -ge 4 && $# -le 5 ]] || usage INDEX="$3" RESULT="$4" NOTE="${5:-}" [[ "$INDEX" =~ ^[1-9][0-9]*$ ]] || { echo "native-qa: assertion index must be positive" >&2; exit 2; } [[ "$RESULT" = "pass" || "$RESULT" = "fail" ]] || { echo "native-qa: result must be pass or fail" >&2; exit 2; } ASSERTION_COUNT="$(list_section assertions | wc -l)" (( INDEX <= ASSERTION_COUNT )) || { echo "native-qa: assertion $INDEX exceeds count $ASSERTION_COUNT" >&2; exit 2; } ASSERTION_TEXT="$(list_section assertions | sed -n "${INDEX}p")" python - "$STATE_ROOT/native-$NAME.json" "$INDEX" "$RESULT" "$NOTE" "$ASSERTION_TEXT" <<'PY' import json, pathlib, sys, time path = pathlib.Path(sys.argv[1]) if not path.is_file(): raise SystemExit("native-qa: missing runner record") data = json.loads(path.read_text(encoding="utf-8")) data.setdefault("assertions", {})[sys.argv[2]] = { "result": sys.argv[3], "note": sys.argv[4], "text": sys.argv[5], "recorded_at": time.time(), } path.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8") PY echo "PASS native-qa-record — $NAME — assertion $INDEX = $RESULT" ;; status) [[ $# -eq 2 ]] || usage RECORD="$STATE_ROOT/native-$NAME.json" [[ -f "$RECORD" ]] || { echo "native-qa: no runner record for $NAME" >&2; exit 2; } ASSERTION_COUNT="$(list_section assertions | wc -l)" python - "$RECORD" "$ASSERTION_COUNT" <<'PY' import json, pathlib, sys, time path = pathlib.Path(sys.argv[1]) data = json.loads(path.read_text(encoding="utf-8")) expected = int(sys.argv[2]) results = data.get("assertions", {}) passed = sum(item.get("result") == "pass" for item in results.values()) failed = sum(item.get("result") == "fail" for item in results.values()) print(f"native-qa-status — assertions {passed} pass, {failed} fail, {expected - len(results)} unrecorded") print(f"evidence: {len(data.get('evidence', []))} capture(s)") fixture = data.get("fixture") if fixture: print(f"fixture restored: {bool(fixture.get('restored'))}") complete = not failed and len(results) == expected and bool(data.get("evidence")) data["scenario_result"] = {"result": "PASS" if complete else "FAIL", "recorded_at": time.time()} path.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8") if not complete: raise SystemExit(1) PY ;; restore) [[ $# -eq 3 ]] || usage MODE="$3" [[ "$MODE" = "--dry-run" || "$MODE" = "--apply" ]] || usage RECORD="$STATE_ROOT/native-$NAME.json" [[ -f "$RECORD" ]] || { echo "native-qa: no runner record for $NAME" >&2; exit 2; } python - "$RECORD" "$MODE" "$ROOT" <<'PY' import hashlib, json, os, pathlib, shutil, sys, tempfile, time record = pathlib.Path(sys.argv[1]) apply = sys.argv[2] == "--apply" root = pathlib.Path(sys.argv[3]).resolve() data = json.loads(record.read_text(encoding="utf-8")) fixture = data.get("fixture") if not fixture: print("PASS native-qa-restore — scenario has no fixture") raise SystemExit(0) path = pathlib.Path(fixture["path"]).resolve() if path != root and root not in path.parents: raise SystemExit(f"native-qa: fixture escapes workspace: {path}") pid = data.get("pid") if apply and pid: try: os.kill(int(pid), 0) except ProcessLookupError: pass except PermissionError: raise SystemExit(f"native-qa: cannot verify runner PID {pid}; refusing fixture restore") else: raise SystemExit(f"native-qa: close runner PID {pid} before restoring the fixture") current = hashlib.sha256(path.read_bytes()).hexdigest() if path.is_file() else None baseline = fixture.get("baseline_sha256") action = "restore snapshot" if fixture.get("existed") else "remove scenario-created fixture" print(f"native-qa-restore-plan — {action}") print(f"fixture: {path}") print(f"baseline sha256: {baseline or '(absent)'}") print(f"current sha256: {current or '(absent)'}") sys.stdout.flush() if not apply: raise SystemExit(0) if fixture.get("existed"): snapshot = pathlib.Path(fixture["snapshot"]) if not snapshot.is_file(): raise SystemExit(f"native-qa: missing fixture snapshot: {snapshot}") snapshot_hash = hashlib.sha256(snapshot.read_bytes()).hexdigest() if snapshot_hash != baseline: raise SystemExit("native-qa: fixture snapshot hash does not match recorded baseline") path.parent.mkdir(parents=True, exist_ok=True) with tempfile.NamedTemporaryFile(dir=path.parent, prefix=f".{path.name}.", delete=False) as handle: temp = pathlib.Path(handle.name) handle.write(snapshot.read_bytes()) os.replace(temp, path) else: if path.exists() and not path.is_file(): raise SystemExit(f"native-qa: refusing to remove non-file fixture path: {path}") path.unlink(missing_ok=True) fixture["restored"] = True fixture["restored_at"] = time.time() record.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8") print("PASS native-qa-restore") PY ;; close) [[ $# -eq 3 ]] || usage PID="$3" RECORD="$STATE_ROOT/native-$NAME.json" [[ -f "$RECORD" ]] || { echo "native-qa: no runner record for $NAME" >&2; exit 2; } RECORDED="$(python -c 'import json,sys; print(json.load(open(sys.argv[1]))["pid"])' "$RECORD")" [[ "$RECORDED" = "$PID" ]] || { echo "native-qa: PID does not match runner record" >&2; exit 2; } SPAWNED="$(python -c 'import json,sys; print(str(bool(json.load(open(sys.argv[1])).get("spawned_by_runner"))).lower())' "$RECORD")" if [[ "$SPAWNED" = "true" ]] && kill -0 "$PID" 2>/dev/null; then CMDLINE="$(tr '\0' ' ' <"/proc/$PID/cmdline")" [[ "$CMDLINE" == *"$BINARY_NAME"* ]] || { echo "native-qa: refusing to stop unexpected process: $CMDLINE" >&2; exit 2; } kill -TERM "$PID" fi python - "$RECORD" <<'PY' import json, pathlib, time, sys path = pathlib.Path(sys.argv[1]) data = json.loads(path.read_text(encoding="utf-8")) data["closed_at"] = time.time() data["detached_only"] = not bool(data.get("spawned_by_runner")) if data["detached_only"]: data["detached_pid"] = data.get("pid") data["pid"] = None path.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8") PY echo "PASS native-qa-close — $NAME — PID $PID" ;; *) usage ;; esac