#!/usr/bin/env bash # G1P smoke — vectord persistence across restart. # # Validates: # - Create index + add 50 vectors → Save fires (storaged shows # _vectors/persist_demo.json AND _vectors/persist_demo.hnsw) # - Search w-001 → recall=1 with distance ≈ 0 # - Kill vectord → restart → initial Refresh enumerates persisted # indexes, Loads each back into the registry # - Same search post-restart → still recall=1 (state survived) # - DELETE → both files removed from storaged → restart → index gone # # Requires storaged + vectord both up. # # Usage: ./scripts/g1p_smoke.sh set -euo pipefail cd "$(dirname "$0")/.." export PATH="$PATH:/usr/local/go/bin" echo "[g1p-smoke] building storaged + vectord + gateway..." go build -o bin/ ./cmd/storaged ./cmd/vectord ./cmd/gateway pkill -f "bin/(storaged|vectord|gateway)" 2>/dev/null || true sleep 0.3 PIDS=() TMP="$(mktemp -d)" cleanup() { echo "[g1p-smoke] cleanup" for p in "${PIDS[@]}"; do [ -n "$p" ] && kill "$p" 2>/dev/null || true; done rm -rf "$TMP" } trap cleanup EXIT INT TERM poll_health() { local port="$1" deadline=$(($(date +%s) + 5)) while [ "$(date +%s)" -lt "$deadline" ]; do if curl -sS --max-time 1 "http://127.0.0.1:$port/health" >/dev/null 2>&1; then return 0; fi sleep 0.05 done return 1 } NAME="persist_demo" DIM=8 echo "[g1p-smoke] launching storaged..." ./bin/storaged > /tmp/storaged.log 2>&1 & PIDS+=($!) poll_health 3211 || { echo "storaged failed"; tail /tmp/storaged.log; exit 1; } # Clean any prior persisted state for this index name. curl -sS -o /dev/null -X DELETE "http://127.0.0.1:3211/storage/delete/_vectors/$NAME.lhv1" || true launch_vectord() { ./bin/vectord > /tmp/vectord.log 2>&1 & VECTORD_PID=$! PIDS+=($VECTORD_PID) poll_health 3215 || { echo "vectord failed"; tail /tmp/vectord.log; return 1; } } launch_gateway() { ./bin/gateway > /tmp/gateway.log 2>&1 & PIDS+=($!) poll_health 3110 || { echo "gateway failed"; tail /tmp/gateway.log; return 1; } } echo "[g1p-smoke] launching vectord (round 1) → gateway..." launch_vectord launch_gateway FAILED=0 echo "[g1p-smoke] create index + add 50 vectors:" HTTP="$(curl -sS -o /dev/null -w '%{http_code}' -X POST http://127.0.0.1:3110/v1/vectors/index \ -H 'Content-Type: application/json' \ -d "{\"name\":\"$NAME\",\"dimension\":$DIM,\"distance\":\"cosine\"}")" if [ "$HTTP" != "201" ]; then echo " ✗ create → $HTTP"; FAILED=1; fi # Build a 50-vector batch. python3 - < "$TMP/batch.json" import json items = [] for i in range(50): v = [0.0] * $DIM v[i % $DIM] = 1.0 v[(i+1) % $DIM] = (i % 13) * 0.01 items.append({"id": f"w-{i:03d}", "vector": v, "metadata": {"row": i}}) print(json.dumps({"items": items})) EOF LEN="$(curl -sS -X POST "http://127.0.0.1:3110/v1/vectors/index/$NAME/add" \ -H 'Content-Type: application/json' \ -d @"$TMP/batch.json" | jq -r '.length')" if [ "$LEN" = "50" ]; then echo " ✓ added 50 → length=50"; else echo " ✗ add → length=$LEN"; FAILED=1; fi echo "[g1p-smoke] verify storaged has the persistence file:" FILE_KEY="_vectors/$NAME.lhv1" FILE_HTTP="$(curl -sS -o /dev/null -w '%{http_code}' "http://127.0.0.1:3211/storage/get/$FILE_KEY")" if [ "$FILE_HTTP" = "200" ]; then echo " ✓ $FILE_KEY present in storaged" else echo " ✗ file=$FILE_HTTP"; FAILED=1 fi echo "[g1p-smoke] search pre-restart:" python3 - < "$TMP/q.json" import json i = 1 v = [0.0] * $DIM v[i % $DIM] = 1.0 v[(i+1) % $DIM] = (i % 13) * 0.01 print(json.dumps({"vector": v, "k": 1})) EOF TOP_PRE="$(curl -sS -X POST "http://127.0.0.1:3110/v1/vectors/index/$NAME/search" \ -H 'Content-Type: application/json' -d @"$TMP/q.json" | jq -r '.results[0].id')" if [ "$TOP_PRE" = "w-001" ]; then echo " ✓ pre-restart top hit = w-001"; else echo " ✗ top=$TOP_PRE"; FAILED=1; fi echo "[g1p-smoke] kill + restart vectord (rehydrate path):" kill $VECTORD_PID 2>/dev/null || true wait $VECTORD_PID 2>/dev/null || true sleep 0.3 launch_vectord sleep 0.2 # let initial Refresh complete (no API to query rehydration done) echo "[g1p-smoke] vectord rehydrated index list shows $NAME:" NAMES_HTTP="$(curl -sS http://127.0.0.1:3110/v1/vectors/index | jq -r '.names | length')" if [ "$NAMES_HTTP" = "1" ]; then echo " ✓ list count=1 after restart"; else echo " ✗ count=$NAMES_HTTP"; FAILED=1; fi INFO_LEN="$(curl -sS "http://127.0.0.1:3110/v1/vectors/index/$NAME" | jq -r '.length')" if [ "$INFO_LEN" = "50" ]; then echo " ✓ length=50 after restart (state survived)"; else echo " ✗ length=$INFO_LEN"; FAILED=1; fi echo "[g1p-smoke] search post-restart:" TOP_POST="$(curl -sS -X POST "http://127.0.0.1:3110/v1/vectors/index/$NAME/search" \ -H 'Content-Type: application/json' -d @"$TMP/q.json" | jq -r '.results[0].id')" DIST_POST="$(curl -sS -X POST "http://127.0.0.1:3110/v1/vectors/index/$NAME/search" \ -H 'Content-Type: application/json' -d @"$TMP/q.json" | jq -r '.results[0].distance')" if [ "$TOP_POST" = "w-001" ]; then echo " ✓ post-restart top hit = w-001 (dist=$DIST_POST)" else echo " ✗ post-restart top=$TOP_POST"; FAILED=1 fi echo "[g1p-smoke] DELETE then restart → index gone:" curl -sS -o /dev/null -X DELETE "http://127.0.0.1:3110/v1/vectors/index/$NAME" FILE_HTTP="$(curl -sS -o /dev/null -w '%{http_code}' "http://127.0.0.1:3211/storage/get/$FILE_KEY")" if [ "$FILE_HTTP" = "404" ]; then echo " ✓ persistence file removed from storaged" else echo " ✗ file=$FILE_HTTP after delete"; FAILED=1 fi kill $VECTORD_PID 2>/dev/null || true wait $VECTORD_PID 2>/dev/null || true sleep 0.3 launch_vectord sleep 0.2 NAMES_HTTP="$(curl -sS http://127.0.0.1:3110/v1/vectors/index | jq -r '.names | length')" if [ "$NAMES_HTTP" = "0" ]; then echo " ✓ post-delete restart list count=0"; else echo " ✗ count=$NAMES_HTTP"; FAILED=1; fi if [ "$FAILED" -eq 0 ]; then echo "[g1p-smoke] G1P acceptance gate: PASSED" exit 0 else echo "[g1p-smoke] G1P acceptance gate: FAILED" exit 1 fi