diff --git a/bin/gstack-community-dashboard b/bin/gstack-community-dashboard index 7d9d48c2..1f469283 100755 --- a/bin/gstack-community-dashboard +++ b/bin/gstack-community-dashboard @@ -59,10 +59,11 @@ echo "────────────────────────" # Parse top_skills array from JSON SKILLS="$(echo "$DATA" | grep -o '"top_skills":\[[^]]*\]' || echo "")" if [ -n "$SKILLS" ] && [ "$SKILLS" != '"top_skills":[]' ]; then - echo "$SKILLS" | grep -o '"skill":"[^"]*","count":[0-9]*' | while read -r ENTRY; do - SKILL="$(echo "$ENTRY" | grep -o '"skill":"[^"]*"' | awk -F'"' '{print $4}')" - COUNT="$(echo "$ENTRY" | grep -o '"count":[0-9]*' | grep -o '[0-9]*')" - printf " /%-20s %s runs\n" "$SKILL" "$COUNT" + # Parse each object — handle any key order (JSONB doesn't preserve order) + echo "$SKILLS" | grep -o '{[^}]*}' | while read -r OBJ; do + SKILL="$(echo "$OBJ" | grep -o '"skill":"[^"]*"' | awk -F'"' '{print $4}')" + COUNT="$(echo "$OBJ" | grep -o '"count":[0-9]*' | grep -o '[0-9]*')" + [ -n "$SKILL" ] && [ -n "$COUNT" ] && printf " /%-20s %s runs\n" "$SKILL" "$COUNT" done else echo " No data yet" @@ -75,10 +76,10 @@ echo "──────────────────" CRASHES="$(echo "$DATA" | grep -o '"crashes":\[[^]]*\]' || echo "")" if [ -n "$CRASHES" ] && [ "$CRASHES" != '"crashes":[]' ]; then - echo "$CRASHES" | grep -o '"error_class":"[^"]*"[^}]*"total_occurrences":[0-9]*' | head -5 | while read -r ENTRY; do - ERR="$(echo "$ENTRY" | grep -o '"error_class":"[^"]*"' | awk -F'"' '{print $4}')" - C="$(echo "$ENTRY" | grep -o '"total_occurrences":[0-9]*' | grep -o '[0-9]*')" - printf " %-30s %s occurrences\n" "$ERR" "${C:-?}" + echo "$CRASHES" | grep -o '{[^}]*}' | head -5 | while read -r OBJ; do + ERR="$(echo "$OBJ" | grep -o '"error_class":"[^"]*"' | awk -F'"' '{print $4}')" + C="$(echo "$OBJ" | grep -o '"total_occurrences":[0-9]*' | grep -o '[0-9]*')" + [ -n "$ERR" ] && printf " %-30s %s occurrences\n" "$ERR" "${C:-?}" done else echo " No crashes reported" @@ -91,10 +92,10 @@ echo "──────────────────────── VERSIONS="$(echo "$DATA" | grep -o '"versions":\[[^]]*\]' || echo "")" if [ -n "$VERSIONS" ] && [ "$VERSIONS" != '"versions":[]' ]; then - echo "$VERSIONS" | grep -o '"version":"[^"]*","count":[0-9]*' | head -5 | while read -r ENTRY; do - VER="$(echo "$ENTRY" | grep -o '"version":"[^"]*"' | awk -F'"' '{print $4}')" - COUNT="$(echo "$ENTRY" | grep -o '"count":[0-9]*' | grep -o '[0-9]*')" - printf " v%-15s %s events\n" "$VER" "$COUNT" + echo "$VERSIONS" | grep -o '{[^}]*}' | head -5 | while read -r OBJ; do + VER="$(echo "$OBJ" | grep -o '"version":"[^"]*"' | awk -F'"' '{print $4}')" + COUNT="$(echo "$OBJ" | grep -o '"count":[0-9]*' | grep -o '[0-9]*')" + [ -n "$VER" ] && [ -n "$COUNT" ] && printf " v%-15s %s events\n" "$VER" "$COUNT" done else echo " No data yet" diff --git a/supabase/functions/community-pulse/index.ts b/supabase/functions/community-pulse/index.ts index 4ba39e21..acf2fdb7 100644 --- a/supabase/functions/community-pulse/index.ts +++ b/supabase/functions/community-pulse/index.ts @@ -85,12 +85,10 @@ Deno.serve(async () => { // Version distribution (last 7 days) const versionCounts: Record = {}; - for (const row of skillRows ?? []) { - // skillRows doesn't have version — query separately - } const { data: versionRows } = await supabase .from("telemetry_events") .select("gstack_version") + .eq("event_type", "skill_run") .gte("event_timestamp", weekAgo) .limit(1000); diff --git a/supabase/verify-rls.sh b/supabase/verify-rls.sh index 00b0669c..68438687 100755 --- a/supabase/verify-rls.sh +++ b/supabase/verify-rls.sh @@ -23,6 +23,7 @@ check() { local args=(-sf -o /dev/null -w '%{http_code}' --max-time 10 -H "apikey: ${KEY}" + -H "Authorization: Bearer ${KEY}" -H "Content-Type: application/json") if [ "$method" = "GET" ]; then @@ -33,24 +34,29 @@ check() { HTTP="$(curl "${args[@]}" -X PATCH "${URL}/rest/v1/${path}" -d "$data" 2>/dev/null || echo "000")" fi - # Success = anything that is NOT a 200/201 with data - # 403, 401, or empty 200 (=[]) all count as "denied" + # Only 401/403 prove RLS denial. 200 (even empty) means access is granted. + # 5xx means something errored but access wasn't denied by policy. case "$HTTP" in + 401|403) + echo " PASS $desc (HTTP $HTTP, denied by RLS)" + PASS=$(( PASS + 1 )) + ;; 200) - # For GETs, check if response is empty array - BODY="$(curl -sf --max-time 10 "${URL}/rest/v1/${path}" -H "apikey: ${KEY}" -H "Content-Type: application/json" 2>/dev/null || echo "")" - if [ "$BODY" = "[]" ] || [ -z "$BODY" ]; then - echo " PASS $desc (HTTP $HTTP, empty)" - PASS=$(( PASS + 1 )) + # 200 means the request was accepted — check if data was returned + if [ "$method" = "GET" ]; then + BODY="$(curl -sf --max-time 10 "${URL}/rest/v1/${path}" -H "apikey: ${KEY}" -H "Authorization: Bearer ${KEY}" -H "Content-Type: application/json" 2>/dev/null || echo "")" + if [ "$BODY" = "[]" ] || [ -z "$BODY" ]; then + echo " WARN $desc (HTTP $HTTP, empty — may be RLS or empty table, verify manually)" + FAIL=$(( FAIL + 1 )) + else + echo " FAIL $desc (HTTP $HTTP, got data)" + FAIL=$(( FAIL + 1 )) + fi else - echo " FAIL $desc (HTTP $HTTP, got data)" + echo " FAIL $desc (HTTP $HTTP, write accepted)" FAIL=$(( FAIL + 1 )) fi ;; - 401|403|404|406) - echo " PASS $desc (HTTP $HTTP, denied)" - PASS=$(( PASS + 1 )) - ;; 201) echo " FAIL $desc (HTTP $HTTP, write succeeded!)" FAIL=$(( FAIL + 1 )) @@ -60,8 +66,9 @@ check() { FAIL=$(( FAIL + 1 )) ;; *) - echo " PASS $desc (HTTP $HTTP)" - PASS=$(( PASS + 1 )) + # 404, 406, 500, etc. — access not definitively denied by RLS + echo " WARN $desc (HTTP $HTTP — not a clean RLS denial)" + FAIL=$(( FAIL + 1 )) ;; esac }