mirror of
https://github.com/garrytan/gstack.git
synced 2026-05-07 05:56:41 +02:00
feat: telemetry data integrity — source tagging, UUID fingerprint, duration guards
- Add source field (live/test/dev) to telemetry pipeline: --source flag in gstack-telemetry-log, GSTACK_TELEMETRY_SOURCE env fallback, pass-through in telemetry-sync, source=eq.live filter on all dashboard queries - Replace SHA-256 installation_id with UUID install_fingerprint for all tiers (not just community). Expand-contract migration: ADD new column + trigger to copy installation_id, preserving backward compat with old clients - Fix duration bug: persist _TEL_START to file via $PPID (stable across bash blocks), cap durations at 86400s, reject negative values - Ungate update-check pings from telemetry=off — sends only version + OS + random UUID. Generate .install-id in update-check for telemetry=off users - Migration 003: source columns, install_fingerprint, duration CHECK constraint, indexes, recreated views with source filter, growth funnel (first-seen based), materialized views for daily installs + version adoption - E2E test isolation: session-runner sets GSTACK_TELEMETRY_SOURCE=test - 8 new telemetry tests (source field, duration caps, fingerprint persistence) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
+28
-15
@@ -35,6 +35,7 @@ ERROR_CLASS=""
|
||||
ERROR_MESSAGE=""
|
||||
FAILED_STEP=""
|
||||
EVENT_TYPE="skill_run"
|
||||
SOURCE=""
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
@@ -47,10 +48,14 @@ while [ $# -gt 0 ]; do
|
||||
--error-message) ERROR_MESSAGE="$2"; shift 2 ;;
|
||||
--failed-step) FAILED_STEP="$2"; shift 2 ;;
|
||||
--event-type) EVENT_TYPE="$2"; shift 2 ;;
|
||||
--source) SOURCE="$2"; shift 2 ;;
|
||||
*) shift ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Source: flag > env > default 'live'
|
||||
SOURCE="${SOURCE:-${GSTACK_TELEMETRY_SOURCE:-live}}"
|
||||
|
||||
# ─── Read telemetry tier ─────────────────────────────────────
|
||||
TIER="$("$CONFIG_CMD" get telemetry 2>/dev/null || true)"
|
||||
TIER="${TIER:-off}"
|
||||
@@ -109,19 +114,19 @@ if [ -d "$STATE_DIR/sessions" ]; then
|
||||
[ -n "$_SC" ] && [ "$_SC" -gt 0 ] 2>/dev/null && SESSIONS="$_SC"
|
||||
fi
|
||||
|
||||
# Generate installation_id for community tier
|
||||
INSTALL_ID=""
|
||||
if [ "$TIER" = "community" ]; then
|
||||
HOST="$(hostname 2>/dev/null || echo "unknown")"
|
||||
USER="$(whoami 2>/dev/null || echo "unknown")"
|
||||
if command -v shasum >/dev/null 2>&1; then
|
||||
INSTALL_ID="$(printf '%s-%s' "$HOST" "$USER" | shasum -a 256 | awk '{print $1}')"
|
||||
elif command -v sha256sum >/dev/null 2>&1; then
|
||||
INSTALL_ID="$(printf '%s-%s' "$HOST" "$USER" | sha256sum | awk '{print $1}')"
|
||||
elif command -v openssl >/dev/null 2>&1; then
|
||||
INSTALL_ID="$(printf '%s-%s' "$HOST" "$USER" | openssl dgst -sha256 | awk '{print $NF}')"
|
||||
# Generate/read persistent UUID fingerprint (all tiers, not just community)
|
||||
INSTALL_FP=""
|
||||
FP_FILE="$STATE_DIR/.install-id"
|
||||
if [ -f "$FP_FILE" ]; then
|
||||
INSTALL_FP="$(cat "$FP_FILE" 2>/dev/null | tr -d '[:space:]')"
|
||||
fi
|
||||
if [ -z "$INSTALL_FP" ]; then
|
||||
INSTALL_FP="$(uuidgen 2>/dev/null || cat /proc/sys/kernel/random/uuid 2>/dev/null || python3 -c 'import uuid; print(uuid.uuid4())' 2>/dev/null || echo "")"
|
||||
INSTALL_FP="$(echo "$INSTALL_FP" | tr '[:upper:]' '[:lower:]')" # normalize case
|
||||
if [ -n "$INSTALL_FP" ]; then
|
||||
mkdir -p "$STATE_DIR"
|
||||
echo "$INSTALL_FP" > "$FP_FILE"
|
||||
fi
|
||||
# If no SHA-256 command available, install_id stays empty
|
||||
fi
|
||||
|
||||
# Local-only fields (never sent remotely)
|
||||
@@ -145,20 +150,28 @@ ERR_MSG_FIELD="null"
|
||||
STEP_FIELD="null"
|
||||
[ -n "$FAILED_STEP" ] && STEP_FIELD="\"$(echo "$FAILED_STEP" | head -c 30)\""
|
||||
|
||||
# Cap unreasonable durations
|
||||
if [ -n "$DURATION" ] && [ "$DURATION" -gt 86400 ] 2>/dev/null; then
|
||||
DURATION="" # null if > 24h
|
||||
fi
|
||||
if [ -n "$DURATION" ] && [ "$DURATION" -lt 0 ] 2>/dev/null; then
|
||||
DURATION="" # null if negative
|
||||
fi
|
||||
|
||||
DUR_FIELD="null"
|
||||
[ -n "$DURATION" ] && DUR_FIELD="$DURATION"
|
||||
|
||||
INSTALL_FIELD="null"
|
||||
[ -n "$INSTALL_ID" ] && INSTALL_FIELD="\"$INSTALL_ID\""
|
||||
[ -n "$INSTALL_FP" ] && INSTALL_FIELD="\"$INSTALL_FP\""
|
||||
|
||||
BROWSE_BOOL="false"
|
||||
[ "$USED_BROWSE" = "true" ] && BROWSE_BOOL="true"
|
||||
|
||||
printf '{"v":1,"ts":"%s","event_type":"%s","skill":"%s","session_id":"%s","gstack_version":"%s","os":"%s","arch":"%s","duration_s":%s,"outcome":"%s","error_class":%s,"error_message":%s,"failed_step":%s,"used_browse":%s,"sessions":%s,"installation_id":%s,"_repo_slug":"%s","_branch":"%s"}\n' \
|
||||
printf '{"v":1,"ts":"%s","event_type":"%s","skill":"%s","session_id":"%s","gstack_version":"%s","os":"%s","arch":"%s","duration_s":%s,"outcome":"%s","error_class":%s,"error_message":%s,"failed_step":%s,"used_browse":%s,"sessions":%s,"install_fingerprint":%s,"source":"%s","_repo_slug":"%s","_branch":"%s"}\n' \
|
||||
"$TS" "$EVENT_TYPE" "$SKILL" "$SESSION_ID" "$GSTACK_VERSION" "$OS" "$ARCH" \
|
||||
"$DUR_FIELD" "$OUTCOME" "$ERR_FIELD" "$ERR_MSG_FIELD" "$STEP_FIELD" \
|
||||
"$BROWSE_BOOL" "${SESSIONS:-1}" \
|
||||
"$INSTALL_FIELD" "$REPO_SLUG" "$BRANCH" >> "$JSONL_FILE" 2>/dev/null || true
|
||||
"$INSTALL_FIELD" "$SOURCE" "$REPO_SLUG" "$BRANCH" >> "$JSONL_FILE" 2>/dev/null || true
|
||||
|
||||
# ─── Trigger sync if tier is not off ─────────────────────────
|
||||
SYNC_CMD="$GSTACK_DIR/bin/gstack-telemetry-sync"
|
||||
|
||||
Reference in New Issue
Block a user