Blacksite/scripts/codex/native_qa.sh
Rbanh 53dc1e44d8
Some checks are pending
CI / Format, lint, test, build (push) Waiting to run
feat: add production UI gallery and inspector system
2026-07-18 12:05:26 -04:00

358 lines
14 KiB
Bash
Executable File

#!/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 <scenario-name> [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