Closes the code-side v0.1 ship gate. All quality gates green: cargo fmt/clippy/test (~327 tests), npm check (0/0), vitest 13/13, scripts/dogfood-rebrand-drill.sh 8/8. Phase F — first-run onboarding promoted to v0.1 - FirstRunPage with skip-to-main + failure recovery + event recording - Six onboarding commands (record/list/has-completed + lumotia_events) - Storage migration v17 (onboarding_events + lumotia_events tables) UI hardening (in-scope items from v0.1-ui-hardening.md) - StatusPill + PostCaptureCard components, 21st preview entry - Sidebar recording-as-sacred-state (opacity + aria-disabled, reduced-motion) - Settings 6-section regroup + Help section + Activation log + Privacy toggle - Error-state copy sweep (DictationPage + SettingsPage, plain-language) - Global :focus-visible rule, textarea outlines restored - Ctrl+K / Ctrl+, / Escape bindings in +layout LLM resilience - rule_based_extract_tasks (regex-free imperative-verb extractor) + extract_tasks_with_fallback wrapper — task extraction never returns zero - tokio::time::timeout(120s) wraps cleanup/tags/tasks commands Release artefacts - LICENSE (canonical AGPL-3.0), CHANGELOG (Keep-a-Changelog format) - v0.1-release-notes, privacy-and-ai-use, install-warnings, tester-onboarding-kit, tester-acceptance-runbook, code-signing-setup, apple-silicon-rb08-runbook, virtual-audio-setup, v0.1-contrast-audit - Workspace versioning + AGPL spdx; npm exact-pin (10 ranges removed) - AppImage SHA-256 sidecar in build.yml - README v0.1 section + Reporting-issues; canonical repo slug Closure pass — items moved from human-required to code-complete - KI-02 Linux idle inhibit: zbus 5 → org.freedesktop.login1.Manager.Inhibit - KI-03 Windows sleep prevention: SetThreadExecutionState(ES_CONTINUOUS|...) - acquire/release_idle_inhibit Tauri commands, wired in DictationPage - Diagnostic-bundle frontend wire-up (Settings → Help button) - WCAG-AA contrast fix via .btn-filled-text utility (no token changes) - 8 destructive-action sites wrapped in plain-language confirm() guards - KNOWN-ISSUES.md + v0.1-known-limitations.md updated (KI-02/03 fixed) Scripts - pre-tag-verify.sh, tag-day.sh, smoke-linux + driver - parse-diagnostic-bundle.sh, parse-activation-log.py Per-item audit trail: docs/release/v0.1-completion-status.md Remaining: W-01…W-08 (signing certs, hardware probes, smoke matrix, tester recruitment) — see docs/release/v0.1-known-limitations.md.
154 lines
6.4 KiB
Bash
Executable File
154 lines
6.4 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
# parse-diagnostic-bundle.sh — summarise a Lumotia diagnostic bundle zip.
|
||
# Usage: ./scripts/parse-diagnostic-bundle.sh path/to/tester-bundle.zip
|
||
# Requires: unzip (required), jq (preferred; graceful grep fallback).
|
||
# Privacy: never prints transcript text, audio content, or .db content.
|
||
set -euo pipefail
|
||
|
||
BOLD='\033[1m'; GREEN='\033[0;32m'; RED='\033[0;31m'; YELLOW='\033[0;33m'; RESET='\033[0m'
|
||
pass() { printf "${GREEN} ✓${RESET} %s\n" "$*"; }
|
||
fail() { printf "${RED} ×${RESET} %s\n" "$*"; OVERALL_FAIL=1; }
|
||
warn() { printf "${YELLOW} !${RESET} %s\n" "$*"; }
|
||
info() { printf " %s\n" "$*"; }
|
||
header(){ printf "\n${BOLD}%s${RESET}\n" "$*"; }
|
||
OVERALL_FAIL=0
|
||
|
||
# Argument + file validation
|
||
[[ $# -lt 1 ]] && { echo "Usage: $0 path/to/tester-bundle.zip" >&2; exit 1; }
|
||
BUNDLE="$1"; BUNDLE_NAME="$(basename "$BUNDLE")"
|
||
[[ ! -f "$BUNDLE" ]] && { echo "ERROR: file not found: $BUNDLE" >&2; exit 2; }
|
||
if ! file "$BUNDLE" 2>/dev/null | grep -qi 'zip'; then
|
||
(dd if="$BUNDLE" bs=2 count=2 2>/dev/null | grep -q 'PK') \
|
||
|| { echo "ERROR: $BUNDLE does not appear to be a zip archive." >&2; exit 2; }
|
||
fi
|
||
|
||
# jq check
|
||
HAS_JQ=0
|
||
command -v jq &>/dev/null && HAS_JQ=1 \
|
||
|| echo "WARNING: jq not found — falling back to grep-based extraction. Install jq for best results." >&2
|
||
|
||
# Extract
|
||
TMPDIR_WORK="$(mktemp -d)"; trap 'rm -rf "$TMPDIR_WORK"' EXIT
|
||
unzip -q "$BUNDLE" -d "$TMPDIR_WORK" 2>/dev/null \
|
||
|| { echo "ERROR: failed to extract $BUNDLE" >&2; exit 3; }
|
||
|
||
# Header
|
||
TITLE="LUMOTIA DIAGNOSTIC BUNDLE SUMMARY: $BUNDLE_NAME"
|
||
printf "\n${BOLD}%s${RESET}\n" "$TITLE"
|
||
printf '%0.s=' $(seq 1 ${#TITLE}); printf '\n'
|
||
|
||
# metadata.json → version + timestamps
|
||
META="$TMPDIR_WORK/metadata.json"; GEN_AT=""; VERSION=""
|
||
if [[ -f "$META" ]]; then
|
||
if [[ $HAS_JQ -eq 1 ]]; then
|
||
GEN_AT="$(jq -r '.generated_at // empty' "$META" 2>/dev/null)"
|
||
VERSION="$(jq -r '.lumotia_version // empty' "$META" 2>/dev/null)"
|
||
else
|
||
GEN_AT="$(grep -o '"generated_at":[^,}]*' "$META" | grep -o '[0-9]*' | head -1)"
|
||
VERSION="$(grep -o '"lumotia_version":"[^"]*"' "$META" | cut -d'"' -f4)"
|
||
fi
|
||
fi
|
||
GEN_HUMAN=""
|
||
if [[ -n "$GEN_AT" && "$GEN_AT" =~ ^[0-9]+$ ]]; then
|
||
GEN_HUMAN="$(date -u -d "@$GEN_AT" '+%Y-%m-%dT%H:%M:%SZ' 2>/dev/null \
|
||
|| date -u -r "$GEN_AT" '+%Y-%m-%dT%H:%M:%SZ' 2>/dev/null \
|
||
|| echo "$GEN_AT (epoch)")"
|
||
fi
|
||
info "Generated: ${GEN_HUMAN:-unknown}"
|
||
info "Lumotia version: ${VERSION:-unknown}"
|
||
|
||
# system_info.txt → platform
|
||
SYSINFO="$TMPDIR_WORK/system_info.txt"
|
||
if [[ -f "$SYSINFO" ]]; then
|
||
OS_LINE="$(grep -i '^OS:' "$SYSINFO" | head -1 | sed 's/^OS: *//')"
|
||
ARCH_LINE="$(grep -i '^Arch:' "$SYSINFO" | head -1 | sed 's/^Arch: *//')"
|
||
info "Platform: ${OS_LINE:-unknown} / ${ARCH_LINE:-unknown}"
|
||
fi
|
||
|
||
# Content inventory
|
||
header "CONTENT INVENTORY:"
|
||
check_path() {
|
||
[[ -e "$TMPDIR_WORK/$2" ]] && pass "$1" || warn "$1 (not present)"
|
||
}
|
||
check_path "system_info.txt" "system_info.txt"
|
||
check_path "preferences-redacted.json" "preferences-redacted.json"
|
||
check_path "metadata.json" "metadata.json"
|
||
|
||
LOG_DIR="$TMPDIR_WORK/logs"; LOG_COUNT=0
|
||
if [[ -d "$LOG_DIR" ]]; then
|
||
LOG_COUNT="$(find "$LOG_DIR" -type f | wc -l)"
|
||
pass "logs/ ($LOG_COUNT files, $(du -sh "$LOG_DIR" 2>/dev/null | cut -f1))"
|
||
else warn "logs/ (directory not present)"; fi
|
||
|
||
CRASH_DIR="$TMPDIR_WORK/crashes"
|
||
if [[ -d "$CRASH_DIR" ]]; then
|
||
CRASH_COUNT="$(find "$CRASH_DIR" -type f | wc -l)"
|
||
[[ "$CRASH_COUNT" -eq 0 ]] \
|
||
&& pass "crashes/ (0 files) — no crash dumps, good" \
|
||
|| warn "crashes/ ($CRASH_COUNT files) — crash dumps present, review carefully"
|
||
else pass "crashes/ (directory absent) — no crash dumps, good"; fi
|
||
|
||
# Redaction check — bundle MUST NEVER contain these
|
||
header "REDACTION CHECK (bundle MUST NEVER include these):"
|
||
DENY_CLEAN=1
|
||
check_absent() {
|
||
local hits; hits="$(find "$TMPDIR_WORK" -type f | { grep -iE "$2" 2>/dev/null || true; })"
|
||
[[ -z "$hits" ]] && pass "$1" \
|
||
|| { fail "$1 — FOUND: $(printf '%s' "$hits" | head -3 | tr '\n' ' ')"; DENY_CLEAN=0; }
|
||
}
|
||
check_absent "no .wav files" '\.wav$'
|
||
check_absent "no .mp3/.opus/.ogg/.flac files" '\.(mp3|opus|ogg|flac)$'
|
||
check_absent "no transcripts/ paths" '/transcripts/'
|
||
check_absent "no captures/ paths" '/captures/'
|
||
check_absent "no .db files" '\.(db|db-wal|db-shm)$'
|
||
check_absent "no .env files" '(^|/)\.env(\.|$)'
|
||
if [[ $DENY_CLEAN -eq 1 ]]; then
|
||
printf "\n${GREEN} PASS: bundle passed redaction check.${RESET}\n"
|
||
else
|
||
printf "\n${RED} FAIL: bundle contains DENIED content — do not share this bundle.${RESET}\n"
|
||
OVERALL_FAIL=1
|
||
fi
|
||
|
||
# Log analysis
|
||
header "LOG ANALYSIS (last 7 days):"
|
||
if [[ -d "$LOG_DIR" && "$LOG_COUNT" -gt 0 ]]; then
|
||
ALL_LOGS="$(find "$LOG_DIR" -type f -exec cat {} \;)"
|
||
ERR_COUNT="$(printf '%s' "$ALL_LOGS" | { grep -iE '(ERROR|ERRO|\bERR\b)' 2>/dev/null || true; } | wc -l)"
|
||
WARN_COUNT="$(printf '%s' "$ALL_LOGS" | { grep -iE '(WARN|WARNING)' 2>/dev/null || true; } | wc -l)"
|
||
info "Errors: $ERR_COUNT"
|
||
info "Warnings: $WARN_COUNT"
|
||
if [[ "$ERR_COUNT" -gt 0 ]]; then
|
||
info ""; info "TOP 3 ERROR PATTERNS:"
|
||
printf '%s' "$ALL_LOGS" \
|
||
| { grep -iE '(ERROR|ERRO|\bERR\b)' || true; } \
|
||
| sed 's/^[0-9TZ:. -]*//' | sort | uniq -c | sort -rn | head -3 \
|
||
| while IFS= read -r line; do info " × $line"; done
|
||
fi
|
||
else warn "No log files found."; fi
|
||
|
||
# Preferences (non-secret values)
|
||
PREFS="$TMPDIR_WORK/preferences-redacted.json"
|
||
header "PREFERENCES (redacted secrets):"
|
||
if [[ ! -f "$PREFS" ]]; then
|
||
warn "preferences-redacted.json not found"
|
||
elif [[ $HAS_JQ -eq 1 ]]; then
|
||
jq -r 'paths(scalars) as $p | getpath($p)
|
||
| select(. != "[redacted]" and . != null and . != "")
|
||
| "\($p | join(".")): \(.)"' "$PREFS" 2>/dev/null | head -30 \
|
||
| while IFS= read -r line; do info " $line"; done
|
||
REDACTED_COUNT="$(jq '[.. | strings | select(. == "[redacted]")] | length' "$PREFS" 2>/dev/null || echo '?')"
|
||
info " ($REDACTED_COUNT field(s) redacted by bundler)"
|
||
else
|
||
{ grep -oE '"[^"]+": *("[^"]*"|[0-9.]+|true|false)' "$PREFS" || true; } \
|
||
| grep -v '\[redacted\]' | head -30 | while IFS= read -r line; do info " $line"; done
|
||
fi
|
||
|
||
# Verdict
|
||
header "VERDICT:"
|
||
if [[ $OVERALL_FAIL -eq 0 ]]; then
|
||
printf "${GREEN} PASS: bundle looks healthy. Safe to inspect for bug triage.${RESET}\n\n"
|
||
else
|
||
printf "${RED} FAIL: one or more checks failed — review items marked × above.${RESET}\n\n"
|
||
exit 1
|
||
fi
|