#!/usr/bin/env bash # Boundary tripwire: flag domain-taxonomy leaks in the Svelte frontend. # # The project rule (CLAUDE.md, docs/architecture/02-svelte-frontend.md) is that # the frontend is presentation-only and the Rust backend owns domain logic — # including Jellyfin's item-type *taxonomy* (what the category "Music" means as a # set of item types). See docs/specs/scoped-search-boundary.md for the incident # that motivated this check. # # ⚠️ This is a TRIPWIRE, NOT A PROOF. A grep cannot distinguish taxonomy-as-policy # (a leak) from taxonomy-as-display (legitimate: "is this a music card?"). It # targets the one machine-detectable signature of the leak class — a *query* that # names a multi-type category — and defers everything subtler to the human # spec-review checklist (docs/specs/SPEC-REVIEW-CHECKLIST.md). A clean run here # does not mean the boundary is respected; it means the crudest violation isn't # present. # # What it flags: an `includeItemTypes: [ ... , ... ]` array literal with two or # more types — i.e. the frontend deciding that a *category* maps to a *set* of # Jellyfin types, which is domain knowledge the backend should own. Single-type # query arrays (`includeItemTypes: ["Movie"]`) are a page saying "I show movies" # and are allowed. Type *inspection* (`item.type === "Audio"`) is display logic # and is not matched. # # Escaping a genuine exception: add the file+reason to the ALLOWLIST below. set -euo pipefail cd "$(dirname "$0")/.." # Files permitted to contain a multi-type includeItemTypes query, with the reason. # Keep this SHORT. A growing allowlist means the boundary is eroding — that is a # signal to push taxonomy into Rust, not to keep appending here. ALLOWLIST=( # "Things a person appeared in" is arguably taxonomy, but it is a fixed # two-type filmography query with no category-configuration behind it. Tracked # as acceptable pending any person-scope work; revisit if it grows. "src/lib/components/library/PersonDetailView.svelte" ) is_allowed() { local file="$1" for allowed in "${ALLOWLIST[@]}"; do [[ "$file" == "$allowed" ]] && return 0 done return 1 } # Multi-element includeItemTypes array: `includeItemTypes: [ , ... ]`. # The comma inside the brackets is what makes it multi-type. PATTERN='includeItemTypes:[[:space:]]*\[[^]]*,[^]]*\]' echo "🔎 Checking frontend for domain-taxonomy leaks (multi-type query arrays)…" # Collect hits, excluding tests and the allowlist. violations="" while IFS= read -r line; do [[ -z "$line" ]] && continue file="${line%%:*}" case "$file" in *.test.*) continue ;; esac if is_allowed "$file"; then echo " ⏭️ allowlisted: $line" continue fi violations+="$line"$'\n' done < <(grep -rInE "$PATTERN" src/ 2>/dev/null || true) if [[ -n "$violations" ]]; then echo "" echo "❌ Frontend boundary violation: a multi-type includeItemTypes query defines" echo " a category in the presentation layer. That taxonomy belongs in Rust —" echo " send an opaque scope and let the backend expand it to item types." echo " See docs/specs/scoped-search-boundary.md and CLAUDE.md." echo "" echo "$violations" | sed 's/^/ /' echo " If this is a genuine exception, add the file + reason to ALLOWLIST in" echo " scripts/check-frontend-boundary.sh — but prefer moving it to Rust." exit 1 fi echo "✅ No multi-type taxonomy queries in the frontend." echo " (Reminder: this is a tripwire, not a proof — the spec-review checklist is" echo " the real gate for subtler leaks.)"