/ scripts / check-arch.sh
check-arch.sh
   1  #!/usr/bin/env bash
   2  # Aura Architectural Compliance Checker (trimmed and opinionated)
   3  set -euo pipefail
   4  
   5  # Styling
   6  RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; BLUE='\033[0;34m'; CYAN='\033[0;36m'; BOLD='\033[1m'; NC='\033[0m'
   7  
   8  usage() {
   9    cat <<'EOF'
  10  Aura Architectural Compliance Checker
  11  
  12  Usage: scripts/check-arch.sh [OPTIONS]
  13  
  14  Options (run all when none given):
  15    --layers         Layer boundary and purity checks
  16    --deps           Dependency direction checks
  17    --effects        Effect placement and handler sanity
  18    --guards         Guard-chain bypass heuristics
  19    --invariants     INVARIANTS.md schema validation
  20    --todos          Incomplete code markers
  21    --registration   Handler composition vs direct instantiation
  22    --crypto         Crypto library usage boundaries (ed25519_dalek, OsRng, getrandom)
  23    --concurrency    Concurrency hygiene (block_in_place, unbounded channels)
  24    --reactive       TUI reactive data model (signals as source of truth, no domain data in props)
  25    --ui             UI boundary checks (aura-terminal uses aura_app::ui facade only)
  26    --workflows      aura-app workflow hygiene (runtime access, parsing helpers, signal access)
  27    --serialization  Serialization format enforcement (DAG-CBOR canonical, no bincode)
  28    --style          Rust style guide rules (usize in wire formats, bounded collections, etc.)
  29    --layer N[,M...] Filter output to specific layer numbers (1-8); repeatable
  30    --quick          Run fast checks only (skip todos, placeholders)
  31    -v, --verbose    Show more detail (allowlisted paths, etc.)
  32    -h, --help       Show this help
  33  EOF
  34  }
  35  
  36  RUN_ALL=true
  37  RUN_LAYERS=false
  38  RUN_DEPS=false
  39  RUN_EFFECTS=false
  40  RUN_GUARDS=false
  41  RUN_INVARIANTS=false
  42  RUN_TODOS=false
  43  RUN_REG=false
  44  RUN_CRYPTO=false
  45  RUN_CONCURRENCY=false
  46  RUN_REACTIVE=false
  47  RUN_UI=false
  48  RUN_WORKFLOWS=false
  49  RUN_SERIALIZATION=false
  50  RUN_STYLE=false
  51  RUN_QUICK=false
  52  VERBOSE=false
  53  LAYER_FILTERS=()
  54  
  55  while [[ $# -gt 0 ]]; do
  56    case $1 in
  57      --layers) RUN_ALL=false; RUN_LAYERS=true ;;
  58      --deps) RUN_ALL=false; RUN_DEPS=true ;;
  59      --effects) RUN_ALL=false; RUN_EFFECTS=true ;;
  60      --guards) RUN_ALL=false; RUN_GUARDS=true ;;
  61      --invariants) RUN_ALL=false; RUN_INVARIANTS=true ;;
  62      --todos) RUN_ALL=false; RUN_TODOS=true ;;
  63      --registration) RUN_ALL=false; RUN_REG=true ;;
  64      --crypto) RUN_ALL=false; RUN_CRYPTO=true ;;
  65      --concurrency) RUN_ALL=false; RUN_CONCURRENCY=true ;;
  66      --reactive) RUN_ALL=false; RUN_REACTIVE=true ;;
  67      --ui) RUN_ALL=false; RUN_UI=true ;;
  68      --workflows) RUN_ALL=false; RUN_WORKFLOWS=true ;;
  69      --serialization) RUN_ALL=false; RUN_SERIALIZATION=true ;;
  70      --style) RUN_ALL=false; RUN_STYLE=true ;;
  71      --layer)
  72        if [[ -z "${2-}" ]]; then
  73          echo "--layer requires a layer number (1-8)"; exit 1
  74        fi
  75        IFS=',' read -r -a layers <<< "$2"
  76        for l in "${layers[@]}"; do
  77          LAYER_FILTERS+=("$l")
  78        done
  79        shift
  80        ;;
  81      --quick) RUN_QUICK=true ;;
  82      -v|--verbose) VERBOSE=true ;;
  83      -h|--help) usage; exit 0 ;;
  84      *) echo "Unknown option: $1"; usage; exit 1 ;;
  85    esac
  86    shift
  87  done
  88  
  89  # Quick mode skips slower/noisier checks
  90  if [ "$RUN_QUICK" = true ] && [ "$RUN_ALL" = true ]; then
  91    RUN_LAYERS=true
  92    RUN_DEPS=true
  93    RUN_EFFECTS=true
  94    RUN_GUARDS=true
  95    RUN_INVARIANTS=true
  96    RUN_REG=true
  97    RUN_CRYPTO=true
  98    RUN_CONCURRENCY=true
  99    RUN_REACTIVE=true
 100    RUN_SERIALIZATION=true
 101    RUN_STYLE=true
 102    RUN_WORKFLOWS=true
 103    RUN_TODOS=false  # Skip todos in quick mode
 104    RUN_ALL=false
 105  fi
 106  
 107  VIOLATIONS=0
 108  VIOLATION_DETAILS=()
 109  
 110  violation() { VIOLATIONS=$((VIOLATIONS+1)); VIOLATION_DETAILS+=("$1"); echo -e "${RED}✖${NC} $1"; }
 111  # Warnings are treated as violations to enforce strict compliance
 112  warning() { violation "$1"; }
 113  info() { echo -e "${BLUE}•${NC} $1"; }
 114  
 115  # Extract layer from a file path
 116  get_layer_from_path() {
 117    local path="$1"
 118    local crate
 119    crate=$(echo "$path" | sed 's|^crates/||' | cut -d/ -f1)
 120    layer_of "$crate"
 121  }
 122  
 123  # Sort hits by layer (L1→L8) based on crate path, preserving layer info.
 124  sort_hits_by_layer() {
 125    while IFS= read -r entry; do
 126      [ -z "$entry" ] && continue
 127      path=${entry%%:*}
 128      crate=$(echo "$path" | cut -d/ -f2)
 129      layer=$(layer_of "$crate")
 130      [ "$layer" = "0" ] && layer=99
 131      printf "%02d:%s\n" "$layer" "$entry"
 132    done | sort -t: -k1,1n -k2,2
 133  }
 134  
 135  layer_filter_matches() {
 136    local layer="$1"
 137    # No filter -> always matches
 138    if [ ${#LAYER_FILTERS[@]} -eq 0 ]; then
 139      return 0
 140    fi
 141    for lf in "${LAYER_FILTERS[@]}"; do
 142      if [ "$layer" = "$lf" ]; then
 143        return 0
 144      fi
 145    done
 146    return 1
 147  }
 148  
 149  # Helper to emit numbered violations with consistent formatting and layer ordering.
 150  # Output format: [Ln] label [idx]: path:content
 151  emit_hits() {
 152    local label="$1"; shift
 153    local hits="$1"
 154    if [ -n "$hits" ]; then
 155      local sorted
 156      sorted=$(printf "%s\n" "$hits" | sort_hits_by_layer)
 157      local idx=1
 158      local any=false
 159      while IFS= read -r entry; do
 160        [ -z "$entry" ] && continue
 161        # Extract layer number (first 2 chars) and actual content
 162        local layer_num="${entry:0:2}"
 163        local content="${entry:3}"  # Skip "NN:"
 164        # Convert layer 99 back to "?" for unknown
 165        [ "$layer_num" = "99" ] && layer_num="?"
 166        # Remove leading zero
 167        layer_num="${layer_num#0}"
 168          # Apply layer filter if present
 169        if ! layer_filter_matches "$layer_num"; then
 170          continue
 171        fi
 172        any=true
 173        violation "[L${layer_num}] ${label} [${idx}]: ${content}"
 174        idx=$((idx+1))
 175      done <<< "$sorted"
 176      if [ "$any" = false ]; then
 177        info "${label}: none (filtered)"
 178      fi
 179    else
 180      info "${label}: none"
 181    fi
 182  }
 183  
 184  section() { echo -e "\n${BOLD}${CYAN}$1${NC}"; }
 185  verbose() { [ "$VERBOSE" = true ] && echo -e "${BLUE}  ↳${NC} $1" || true; }
 186  
 187  # Precise allowlists for impure operations
 188  # These specify exact modules/files that legitimately need impure operations
 189  
 190  # Infrastructure effect implementations (Layer 3)
 191  EFFECT_HANDLER_ALLOWLIST="crates/aura-effects/src/"
 192  
 193  # Test infrastructure (Layer 8) - mocks and test harnesses
 194  # Also includes /testing/ directories which are L8-style test infrastructure in other layers
 195  TEST_ALLOWLIST="crates/aura-testkit/|/tests/|/testing/|/examples/|benches/"
 196  
 197  # Simulator (Layer 6/8) - simulation-specific impurity and test infrastructure (handlers, quint ITF loading)
 198  SIMULATOR_ALLOWLIST="crates/aura-simulator/src/"
 199  
 200  # Handler state allowlist (keep empty; add exact paths for intentional state in handlers)
 201  HANDLER_STATE_ALLOWLIST=""
 202  
 203  # Handler bridge allowlist (keep empty; add exact paths for intentional handler bridges)
 204  HANDLER_BRIDGE_ALLOWLIST=""
 205  
 206  # Runtime assembly (Layer 6) - where effects are composed with real impls
 207  # Includes runtime/ subdirectory, runtime_bridge_impl.rs, and builder/ (bootstrapping before effects exist)
 208  RUNTIME_ALLOWLIST="crates/aura-agent/src/runtime/|crates/aura-agent/src/runtime_bridge_impl.rs|crates/aura-agent/src/builder/"
 209  
 210  # App core (Layer 5) - cfg-gated for native builds only (#[cfg(not(target_arch = "wasm32"))])
 211  # signal_sync.rs uses tokio::spawn for background forwarding tasks (native platform feature)
 212  APP_NATIVE_ALLOWLIST="crates/aura-app/src/core/app.rs|crates/aura-app/src/core/signal_sync.rs"
 213  
 214  # CLI entry points (Layer 7) - main.rs and bootstrap handlers where production starts
 215  CLI_ENTRY_ALLOWLIST="crates/aura-terminal/src/main.rs"
 216  # TUI bootstrap handler - needs fs access before effect system exists
 217  TUI_BOOTSTRAP_ALLOWLIST="crates/aura-terminal/src/handlers/tui.rs"
 218  # TUI infrastructure - low-level terminal plumbing (fd redirection, stdio capture)
 219  TUI_INFRA_ALLOWLIST="crates/aura-terminal/src/tui/fullscreen_stdio.rs"
 220  
 221  # Common filter for effect/impure checks
 222  # Usage: filter_common_allowlist "$input" ["extra_pattern"]
 223  filter_common_allowlist() {
 224    local input="$1"
 225    local extra="${2:-}"
 226    local result
 227    # Use -E for extended regex (alternation with |)
 228    # Filter doc comments (///) as they're examples, not actual code
 229    result=$(echo "$input" \
 230      | grep -v "$EFFECT_HANDLER_ALLOWLIST" \
 231      | grep -v "$SIMULATOR_ALLOWLIST" \
 232      | grep -Ev "$TEST_ALLOWLIST" \
 233      | grep -v "///" || true)
 234    if [ -n "$extra" ]; then
 235      result=$(echo "$result" | grep -Ev "$extra" || true)
 236    fi
 237    echo "$result"
 238  }
 239  
 240  # Counts for summary
 241  # NOTE: Keep this script compatible with macOS bash 3.x (no associative arrays).
 242  
 243  check_cargo() {
 244    if command -v cargo >/dev/null 2>&1; then
 245      return 0
 246    fi
 247    # Fallback to user toolchain (common in dev shells where PATH is trimmed)
 248    if [ -x "$HOME/.cargo/bin/cargo" ]; then
 249      export PATH="$HOME/.cargo/bin:$PATH"
 250      return 0
 251    fi
 252    return 1
 253  }
 254  
 255  layer_of() {
 256    case "$1" in
 257      aura-core) echo 1 ;;
 258      aura-journal|aura-authorization|aura-signature|aura-store|aura-transport|aura-mpst|aura-macros) echo 2 ;;
 259      aura-effects|aura-composition) echo 3 ;;
 260      aura-protocol|aura-guards|aura-consensus|aura-amp|aura-anti-entropy) echo 4 ;;
 261      aura-authentication|aura-chat|aura-invitation|aura-recovery|aura-relational|aura-rendezvous|aura-sync|aura-app) echo 5 ;;
 262      aura-agent|aura-simulator) echo 6 ;;
 263      aura-terminal) echo 7 ;;
 264      aura-testkit|aura-quint) echo 8 ;;
 265      *) echo 0 ;;
 266    esac
 267  }
 268  
 269  if [ "$RUN_ALL" = true ] || [ "$RUN_LAYERS" = true ]; then
 270    section "Layer purity — keep aura-core interface-only; move impls to aura-effects (L3) or domain crates (L2); see docs/999_project_structure.md §Layer 1 and docs/001_system_architecture.md §6"
 271    # aura-core should only define traits/types (no impl of Effects)
 272    # Exclude: trait definitions, blanket impls (impl<...), and doc comments
 273    # Blanket impls include: extension traits and Arc<T> wrappers (both allowed exceptions per docs/999)
 274    # Use word boundary \bimpl\b to avoid false positives like "SimpleIntentEffects"
 275    if grep -RE "\bimpl\b.*Effects" crates/aura-core/src 2>/dev/null | grep -v "trait" | grep -v "impl<" | grep -v ":///" >/dev/null; then
 276      violation "aura-core contains effect implementations (should be interface-only)"
 277    else
 278      info "aura-core: interface-only (no effect impls)"
 279    fi
 280  
 281    # Domain crates should not depend on runtime/UI layers
 282    for crate in aura-authentication aura-app aura-chat aura-invitation aura-recovery aura-relational aura-rendezvous aura-sync; do
 283      if [ -d "crates/$crate" ]; then
 284        if grep -A20 "^\[dependencies\]" crates/$crate/Cargo.toml | grep -E "aura-agent|aura-simulator|aura-terminal" >/dev/null; then
 285          violation "$crate depends on runtime/UI layers"
 286        else
 287          info "$crate: no runtime/UI deps"
 288        fi
 289      fi
 290    done
 291  
 292  fi
 293  
 294  if [ "$RUN_ALL" = true ] || [ "$RUN_DEPS" = true ]; then
 295    section "Dependency direction — remove upward deps (Lx→Ly where y>x); follow docs/999_project_structure.md dependency graph"
 296    if check_cargo; then
 297      deps=$(cargo metadata --format-version 1 --no-deps 2>/dev/null | jq -r '.packages[] | select(.name | startswith("aura-")) | [.name, (.dependencies[] | select(.name | startswith("aura-")) | .name)] | @tsv') || deps=""
 298      clean=true
 299      while IFS=$'\t' read -r src dst; do
 300        [ -z "$src" ] && continue
 301        src_layer=$(layer_of "$src"); dst_layer=$(layer_of "$dst")
 302        if [ "$src_layer" -gt 0 ] && [ "$dst_layer" -gt 0 ] && [ "$dst_layer" -gt "$src_layer" ]; then
 303          violation "$src (L$src_layer) depends upward on $dst (L$dst_layer)"
 304          clean=false
 305        fi
 306      done <<< "$deps"
 307    if [ "$clean" = true ]; then info "Dependency direction: clean"; fi
 308    else
 309      violation "cargo unavailable; dependency direction not checked"
 310    fi
 311  
 312    # Layer 4 dependency firewall: prevent upward deps into Layer 6+
 313    section "Layer 4 firewall — disallow dependencies on runtime/UI/testkit layers"
 314    l4_crates=(aura-protocol aura-guards aura-consensus aura-amp aura-anti-entropy)
 315    l4_blocked="aura-agent|aura-simulator|aura-app|aura-terminal|aura-testkit"
 316    for crate in "${l4_crates[@]}"; do
 317      if [ -f "crates/$crate/Cargo.toml" ]; then
 318        if rg -n "^(.*\\[dependencies\\].*|.*\\[dev-dependencies\\].*|.*\\[build-dependencies\\].*|.*$l4_blocked.*)" "crates/$crate/Cargo.toml" | rg -n "$l4_blocked" >/dev/null; then
 319          violation "$crate depends on Layer 6+ ($l4_blocked) — forbidden by firewall"
 320        else
 321          info "$crate: firewall clean"
 322        fi
 323      fi
 324    done
 325  fi
 326  
 327  if [ "$RUN_ALL" = true ] || [ "$RUN_EFFECTS" = true ]; then
 328    section "Effects — infra traits only in aura-core; infra impls in aura-effects; app effects in domain crates; mocks in aura-testkit (docs/106_effect_system_and_runtime.md §1, docs/999_project_structure.md §Effect Trait Classification)"
 329    # Infrastructure effect traits must live in aura-core
 330    infra_traits="CryptoEffects|NetworkEffects|StorageEffects|PhysicalTimeEffects|LogicalClockEffects|OrderClockEffects|TimeAttestationEffects|RandomEffects|ConsoleEffects|ConfigurationEffects|LeakageEffects"
 331    infra_defs=$(find crates/ -name "*.rs" -not -path "*/aura-core/*" -exec grep -El "pub trait ($infra_traits)" {} + 2>/dev/null || true)
 332    if [ -n "$infra_defs" ]; then
 333      violation "Infrastructure effect traits defined outside aura-core:"
 334      echo "$infra_defs"
 335    else
 336      info "Infra effect traits defined only in aura-core"
 337    fi
 338  
 339    # aura-effects should stay stateless (except allowed infra caches: reactive signal registry, query fact cache)
 340    stateful_matches=$(grep -R "Arc<Mutex\|Arc<RwLock\|Rc<RefCell" crates/aura-effects/src 2>/dev/null | grep -v "test" | grep -v "reactive/handler.rs" | grep -v "query/handler.rs" || true)
 341    if [ -n "$stateful_matches" ]; then
 342      violation "aura-effects contains stateful constructs (should be stateless handlers)"
 343      echo "$stateful_matches"
 344    fi
 345  
 346    # Guard for mocks in aura-effects
 347    if grep -R "Mock.*Handler\|InMemory.*Handler" crates/aura-effects/src 2>/dev/null | grep -v "test" >/dev/null; then
 348      violation "Mock/test handlers found in aura-effects (should be in aura-testkit)"
 349    fi
 350  
 351    # Check for infrastructure effect implementations outside aura-effects
 352    # Only flag concrete impl blocks (not type bounds) of infra effects outside aura-effects/testkit
 353    infra_impls=$(rg --no-heading --glob "*.rs" "impl\\s+[^\n{}]*for[^\n{}]*(CryptoEffects|NetworkEffects|StorageEffects|PhysicalTimeEffects|LogicalClockEffects|OrderClockEffects|TimeAttestationEffects|RandomEffects|ConsoleEffects|ConfigurationEffects)" crates \
 354      | grep -v "crates/aura-effects/" \
 355      | grep -v "crates/aura-testkit/" \
 356      | grep -v "crates/aura-core/" \
 357      | grep -v "tests/" || true)
 358    emit_hits "Infrastructure effects implemented outside aura-effects" "$infra_impls"
 359  
 360    # Check for application effects in aura-effects
 361    # Note: LeakageEffects is infrastructure (moved to infra_traits above)
 362    app_effects="JournalEffects|AuthorityEffects|FlowBudgetEffects|AuthorizationEffects|RelationalContextEffects|GuardianEffects|ChoreographicEffects|EffectApiEffects|SyncEffects"
 363    app_impls=$(grep -R "impl.*\($app_effects\)" crates/aura-effects/src 2>/dev/null | grep -v "test" || true)
 364    if [ -n "$app_impls" ]; then
 365      violation "Application effects implemented in aura-effects (should be in domain crates)"
 366    else
 367      info "No application effects implemented in aura-effects"
 368    fi
 369  
 370    # Check for direct OS operations in domain handlers
 371    domain_crates="aura-journal|aura-authorization|aura-signature|aura-store|aura-transport|aura-authentication|aura-recovery|aura-relational"
 372    os_violations=$(find crates/ -path "*/src/*" -name "*.rs" | grep -E "($domain_crates)" | xargs grep -l "std::fs::\|SystemTime::now\|thread_rng()" 2>/dev/null | grep -v "test" || true)
 373    emit_hits "Direct OS operations in domain crates (should use effect injection)" "$os_violations"
 374  
 375    # Check for direct std::fs usage outside handler layers (should use StorageEffects)
 376    # Allowed: effect handler impls (storage.rs), runtime assembly, tests, cfg-gated native code, TUI bootstrap
 377    fs_pattern="std::fs::|std::io::File|std::io::BufReader|std::io::BufWriter"
 378    fs_hits=$(rg --no-heading "$fs_pattern" crates -g "*.rs" || true)
 379    filtered_fs=$(filter_common_allowlist "$fs_hits" "$RUNTIME_ALLOWLIST|$APP_NATIVE_ALLOWLIST|$TUI_BOOTSTRAP_ALLOWLIST|$TUI_INFRA_ALLOWLIST")
 380    # Additional filter: skip lines in files after #[cfg(test)] (inline test modules)
 381    if [ -n "$filtered_fs" ]; then
 382      filtered_fs_final=""
 383      while IFS= read -r line; do
 384        [ -z "$line" ] && continue
 385        file_path="${line%%:*}"
 386        # Skip if file contains #[cfg(test)] and this line is in the test module
 387        if [ -f "$file_path" ] && grep -q "#\[cfg(test)\]" "$file_path" 2>/dev/null; then
 388          match_line_text="${line#*:}"
 389          match_line_num=$(grep -n "$match_line_text" "$file_path" 2>/dev/null | head -1 | cut -d: -f1)
 390          cfg_test_line=$(grep -n "#\[cfg(test)\]" "$file_path" 2>/dev/null | head -1 | cut -d: -f1)
 391          if [ -n "$match_line_num" ] && [ -n "$cfg_test_line" ] && [ "$match_line_num" -gt "$cfg_test_line" ]; then
 392            continue  # Skip - this is in a test module
 393          fi
 394        fi
 395        filtered_fs_final="${filtered_fs_final}${line}"$'\n'
 396      done <<< "$filtered_fs"
 397      filtered_fs="$filtered_fs_final"
 398    fi
 399    emit_hits "Direct std::fs usage (should use StorageEffects)" "$filtered_fs"
 400    verbose "Allowed: aura-effects/src/, aura-simulator/src/handlers/, aura-agent/src/runtime/, aura-app/src/core/app.rs (cfg-gated), aura-terminal/src/handlers/tui.rs (bootstrap), tests/, testing/, #[cfg(test)] modules"
 401  
 402    # Check for direct std::net usage outside handler layers (should use NetworkEffects)
 403    # Allowed: effect handler impls (network.rs), runtime assembly, tests
 404    net_pattern="std::net::|TcpStream|TcpListener|UdpSocket"
 405    net_hits=$(rg --no-heading "$net_pattern" crates -g "*.rs" || true)
 406    filtered_net=$(filter_common_allowlist "$net_hits" "$RUNTIME_ALLOWLIST")
 407    emit_hits "Direct std::net usage (should use NetworkEffects)" "$filtered_net"
 408    verbose "Allowed: aura-effects/src/, aura-simulator/src/handlers/, aura-agent/src/runtime/, tests/"
 409  
 410    section "Runtime coupling — keep foundation/spec crates runtime-agnostic; wrap tokio/async-std behind effects (docs/106_effect_system_and_runtime.md §3.5, docs/001_system_architecture.md §3)"
 411    runtime_pattern="tokio::|async_std::"
 412    runtime_hits=$(rg --no-heading -n "$runtime_pattern" crates -g "*.rs" || true)
 413    # Allowlist: effect handlers, agent runtime, simulator, terminal UI, composition, testkit, app core (native feature), tests
 414    # Layer 6 (runtime) and Layer 7 (UI) are allowed to use tokio directly
 415    # Layer 5 aura-app uses tokio for signal forwarding (cfg-gated for native platforms)
 416    # Note: aura-authorization/storage_authorization.rs uses tokio::sync::RwLock for AuthorizedStorageHandler
 417    # which is a handler wrapper that should eventually move to aura-composition (tracked technical debt)
 418    # Note: aura-core/effects/reactive.rs uses tokio::sync::broadcast for SignalStream<T> which is
 419    # part of the ReactiveEffects trait API. This should be abstracted to a runtime-agnostic stream
 420    # trait in the future (tracked technical debt: abstract SignalStreamReceiver trait)
 421    filtered_runtime=$(echo "$runtime_hits" \
 422      | grep -v "crates/aura-effects/" \
 423      | grep -v "crates/aura-agent/" \
 424      | grep -v "crates/aura-simulator/" \
 425      | grep -v "crates/aura-terminal/" \
 426      | grep -v "crates/aura-composition/" \
 427      | grep -v "crates/aura-testkit/" \
 428      | grep -Ev "$APP_NATIVE_ALLOWLIST" \
 429      | grep -v "crates/aura-authorization/src/storage_authorization.rs" \
 430      | grep -v "crates/aura-core/src/effects/reactive.rs" \
 431      | grep -v "#\\[tokio::test\\]" \
 432      | grep -v "#\\[async_std::test\\]" \
 433      | grep -v "#\\[tokio::main\\]" \
 434      | grep -v "/tests/" \
 435      | grep -v "/examples/" \
 436      | grep -v "test_macros.rs" \
 437      | grep -v "benches/" || true)
 438    # Second pass: filter out lines from files with inline #[cfg(test)] modules
 439    if [ -n "$filtered_runtime" ]; then
 440      filtered_final=""
 441      while IFS= read -r line; do
 442        [ -z "$line" ] && continue
 443        file_path="${line%%:*}"
 444        # Skip if file contains #[cfg(test)] and this is a test module (heuristic)
 445        if [ -f "$file_path" ] && grep -q "#\[cfg(test)\]" "$file_path" 2>/dev/null; then
 446          # Extract line number (format: file:linenum:content)
 447          match_line_num=$(echo "$line" | cut -d: -f2)
 448          cfg_test_line=$(grep -n "#\[cfg(test)\]" "$file_path" 2>/dev/null | head -1 | cut -d: -f1)
 449          if [ -n "$match_line_num" ] && [ -n "$cfg_test_line" ] && [ "$match_line_num" -gt "$cfg_test_line" ]; then
 450            continue  # Skip - this is in a test module
 451          fi
 452        fi
 453        filtered_final="${filtered_final}${line}"$'\n'
 454      done <<< "$filtered_runtime"
 455      filtered_runtime="$filtered_final"
 456    fi
 457    emit_hits "Concrete runtime usage detected outside handler/composition layers (replace tokio/async-std with effect-injected abstractions)" "$filtered_runtime"
 458  
 459    section "Aura-app runtime-agnostic surface — no tokio/async-std in aura-app"
 460    app_runtime_hits=$(rg --no-heading -n "tokio::|async_std::" crates/aura-app/src -g "*.rs" || true)
 461    filtered_app_runtime=$(echo "$app_runtime_hits" \
 462      | grep -v "#\\[tokio::test\\]" \
 463      | grep -v "#\\[async_std::test\\]" \
 464      | grep -v "/tests/" \
 465      | grep -v "/benches/" || true)
 466    emit_hits "tokio/async-std usage in aura-app (should be runtime-agnostic)" "$filtered_app_runtime"
 467  
 468    section "Impure functions — route time/random/fs through effect traits; production handlers in aura-effects or runtime assembly (docs/106_effect_system_and_runtime.md §1.3, .claude/skills/patterns/SKILL.md)"
 469    # Strict flag for direct wall-clock/random usage outside allowed areas
 470    impure_pattern="SystemTime::now|Instant::now|thread_rng\\(|rand::thread_rng|chrono::Utc::now|chrono::Local::now|rand::rngs::OsRng|rand::random"
 471    impure_hits=$(rg --no-heading "$impure_pattern" crates -g "*.rs" || true)
 472    # Allowlist: effect handlers, testkit, simulator, agent runtime, terminal UI, tests, benches
 473    # Terminal UI is allowed to use direct system time for UI measurements/metrics that don't affect protocol behavior
 474    # Note: Lines ending with .unwrap() or containing #[tokio::test] are likely test code
 475    filtered_impure=$(echo "$impure_hits" \
 476      | grep -v "crates/aura-effects/" \
 477      | grep -v "crates/aura-testkit/" \
 478      | grep -v "crates/aura-simulator/" \
 479      | grep -Ev "$RUNTIME_ALLOWLIST" \
 480      | grep -v "crates/aura-terminal/" \
 481      | grep -v "/tests/" \
 482      | grep -v "/benches/" \
 483      | grep -v "tests/performance_regression.rs" \
 484      | grep -v "///" \
 485      | grep -v "//!" \
 486      | grep -v "//" \
 487      | grep -v "\.unwrap()" \
 488      | grep -v "#\[tokio::test\]" \
 489      | grep -v "#\[test\]" || true)
 490    # Second pass: filter out lines from files with inline #[cfg(test)] modules
 491    if [ -n "$filtered_impure" ]; then
 492      filtered_final=""
 493      while IFS= read -r line; do
 494        [ -z "$line" ] && continue
 495        file_path="${line%%:*}"
 496        # Skip if file contains #[cfg(test)] and this is a test module (heuristic)
 497        if [ -f "$file_path" ] && grep -q "#\[cfg(test)\]" "$file_path" 2>/dev/null; then
 498          # Get line number from match and check if it's after #[cfg(test)]
 499          match_line_text="${line#*:}"
 500          match_line_num=$(grep -n "$match_line_text" "$file_path" 2>/dev/null | head -1 | cut -d: -f1)
 501          cfg_test_line=$(grep -n "#\[cfg(test)\]" "$file_path" 2>/dev/null | head -1 | cut -d: -f1)
 502          if [ -n "$match_line_num" ] && [ -n "$cfg_test_line" ] && [ "$match_line_num" -gt "$cfg_test_line" ]; then
 503            continue  # Skip - this is in a test module
 504          fi
 505        fi
 506        filtered_final="${filtered_final}${line}"$'\n'
 507      done <<< "$filtered_impure"
 508      filtered_impure="$filtered_final"
 509    fi
 510    emit_hits "Impure functions detected outside effect implementations/testkit/runtime assembly" "$filtered_impure"
 511  
 512    section "Physical time guardrails — use PhysicalTimeEffects::sleep_ms; keep sleeps simulator-controllable (docs/106_effect_system_and_runtime.md §1.1, .claude/skills/patterns/SKILL.md)"
 513    # Direct tokio::time::sleep instances should go through PhysicalTimeEffects
 514    # Use -n for line numbers so we can filter by test module position
 515    tokio_sleep_hits=$(rg --no-heading -n "tokio::time::sleep" crates -g "*.rs" || true)
 516    # Allowlist: effect handlers (time.rs), simulator, testkit, tests, aura-terminal (L7 UI)
 517    # aura-agent should use PhysicalTimeEffects::sleep_ms for simulator determinism
 518    # Also filter out inline #[cfg(test)] module content
 519    filtered_tokio_sleep=""
 520    if [[ -n "$tokio_sleep_hits" ]]; then
 521      # First pass: basic path filtering
 522      path_filtered=$(echo "$tokio_sleep_hits" \
 523        | grep -v "crates/aura-effects/" \
 524        | grep -v "crates/aura-simulator/" \
 525        | grep -v "crates/aura-testkit/" \
 526        | grep -v "crates/aura-terminal/" \
 527        | grep -v "/tests/" \
 528        | grep -v "/examples/" \
 529        | grep -v "benches/" || true)
 530      # Second pass: filter out matches that are in inline test modules
 531      # Format is file:linenum:content - extract linenum and check against #[cfg(test)] position
 532      if [[ -n "$path_filtered" ]]; then
 533        while IFS= read -r hit; do
 534          [[ -z "$hit" ]] && continue
 535          file=$(echo "$hit" | cut -d: -f1)
 536          linenum=$(echo "$hit" | cut -d: -f2)
 537          # Check if this line is within a #[cfg(test)] module (after the marker)
 538          test_mod_line=$(grep -n '#\[cfg(test)\]' "$file" 2>/dev/null | head -1 | cut -d: -f1 || echo "99999")
 539          if [[ "$linenum" =~ ^[0-9]+$ ]] && [[ "$linenum" -lt "$test_mod_line" ]]; then
 540            # Line is before test module, include it (strip line number for display)
 541            filtered_tokio_sleep+="${file}:$(echo "$hit" | cut -d: -f3-)"$'\n'
 542          elif ! [[ "$linenum" =~ ^[0-9]+$ ]]; then
 543            # No line number format, include as-is
 544            filtered_tokio_sleep+="$hit"$'\n'
 545          fi
 546          # Lines after test_mod_line are in test modules, skip them
 547        done <<< "$path_filtered"
 548        filtered_tokio_sleep="${filtered_tokio_sleep%$'\n'}"
 549      fi
 550    fi
 551    emit_hits "Direct tokio::time::sleep usage (should use PhysicalTimeEffects::sleep_ms)" "$filtered_tokio_sleep"
 552  
 553    # Check for direct sleeps from std/async-std (should use effect-injected time)
 554    sleep_pattern="std::thread::sleep|async_std::task::sleep"
 555    sleep_hits=$(rg --no-heading "$sleep_pattern" crates -g "*.rs" || true)
 556    filtered_sleep=$(echo "$sleep_hits" \
 557      | grep -v "crates/aura-effects/src/time.rs" \
 558      | grep -v "crates/aura-simulator/" \
 559      | grep -v "crates/aura-testkit/" \
 560      | grep -v "/tests/" \
 561      | grep -v "benches/" || true)
 562    emit_hits "Direct sleeps detected (should be effect-injected/simulator-controlled)" "$filtered_sleep"
 563  
 564    section "Sync protocol runtime neutrality — no tokio/async-std in aura-sync protocols"
 565    sync_protocol_runtime=$(rg --no-heading -n "tokio::|async_std::" crates/aura-sync/src/protocols -g "*.rs" || true)
 566    filtered_sync_protocol_runtime=$(echo "$sync_protocol_runtime" \
 567      | grep -v "///" \
 568      | grep -v "//!" \
 569      | grep -v "//" || true)
 570    if [ -n "$filtered_sync_protocol_runtime" ]; then
 571      emit_hits "Runtime-specific usage in aura-sync protocols (replace with effect-injected abstractions)" "$filtered_sync_protocol_runtime"
 572    else
 573      info "aura-sync protocols: no runtime-specific usage"
 574    fi
 575  
 576    section "Simulation control surfaces — inject randomness/IO/spawn via effects so simulator can control (docs/806_simulation_guide.md, .claude/skills/patterns/SKILL.md)"
 577    sim_patterns="rand::random|rand::thread_rng|rand::rngs::OsRng|RngCore::fill_bytes|std::io::stdin|read_line\\(|std::thread::spawn"
 578    sim_hits=$(rg --no-heading "$sim_patterns" crates -g "*.rs" || true)
 579    # TUI_BLOCKON_ALLOWLIST: TUI sync/async bridge helper using std::thread::spawn to avoid
 580    # "Cannot start a runtime from within a runtime" panic. Underlying storage ops go through
 581    # effect handlers (PathFilesystemStorageHandler). See handlers/tui.rs block_on().
 582    TUI_BLOCKON_ALLOWLIST="crates/aura-terminal/src/handlers/tui.rs"
 583    filtered_sim=$(echo "$sim_hits" \
 584      | grep -v "crates/aura-effects/" \
 585      | grep -v "crates/aura-testkit/" \
 586      | grep -v "crates/aura-simulator/" \
 587      | grep -v "crates/aura-agent/src/runtime/" \
 588      | grep -v "$TUI_BLOCKON_ALLOWLIST" \
 589      | grep -v "/tests/" \
 590      | grep -v "///" \
 591      | grep -v "//!" \
 592      | grep -v "//" || true)
 593    emit_hits "Potential non-injected randomness/IO/spawn (should be simulator-controllable; see docs/806_simulation_guide.md and .claude/skills/patterns/SKILL.md)" "$filtered_sim"
 594  
 595    section "Pure interpreter alignment — migrate to GuardSnapshot + pure guard eval + EffectCommand interpreter (docs/106_effect_system_and_runtime.md §8, docs/001_system_architecture.md §2.1-2.3)"
 596    guard_bridge_hits=$(
 597      rg --no-heading "GuardEffectSystem" crates -g "*.rs" || true
 598    )
 599    guard_block_on_hits=$(
 600      rg --no-heading "futures::executor::block_on" crates -g "*.rs" || true
 601    )
 602    sync_output=$(printf "%s\n%s" "$guard_bridge_hits" "$guard_block_on_hits" | sed '/^$/d' | sort -u)
 603    emit_hits "Synchronous guard/effect bridges detected (migrate to pure snapshot + EffectCommand + interpreter; see docs/106_effect_system_and_runtime.md and docs/806_simulation_guide.md)" "$sync_output"
 604  
 605    section "Identifier determinism — avoid entropy-consuming ID creation; use deterministic constructors for tests"
 606    # Reference: .claude/skills/patterns/SKILL.md (Test Determinism Violations section)
 607    # Reference: docs/805_testing_guide.md (Deterministic Identifier Generation section)
 608  
 609    # Check for AuthorityId::new(), ContextId::new(), DeviceId::new() which use system entropy
 610    # Allowed only in: effect handlers (random.rs), runtime assembly, CLI entry point, tests
 611    entropy_id_pattern="AuthorityId::new\\(\\)|ContextId::new\\(\\)|DeviceId::new\\(\\)"
 612    entropy_id_hits=$(rg --no-heading "$entropy_id_pattern" crates -g "*.rs" || true)
 613    filtered_entropy_ids=$(echo "$entropy_id_hits" \
 614      | grep -v "$EFFECT_HANDLER_ALLOWLIST" \
 615      | grep -Ev "$RUNTIME_ALLOWLIST" \
 616      | grep -v "$CLI_ENTRY_ALLOWLIST" \
 617      | grep -Ev "$TEST_ALLOWLIST" || true)
 618    if [ -n "$filtered_entropy_ids" ]; then
 619      # Sort by layer and emit with layer prefix, respecting layer filters
 620      sorted_ids=$(printf "%s\n" "$filtered_entropy_ids" | sort_hits_by_layer)
 621      any=false
 622      while IFS= read -r entry; do
 623        [ -z "$entry" ] && continue
 624        layer_num="${entry:0:2}"
 625        content="${entry:3}"
 626        [ "$layer_num" = "99" ] && layer_num="?"
 627        layer_num="${layer_num#0}"
 628        if ! layer_filter_matches "$layer_num"; then
 629          continue
 630        fi
 631        any=true
 632        violation "[L${layer_num}] Entropy-consuming ID: $content"
 633        echo -e "    ${YELLOW}Fix:${NC} Use XxxId::new_from_entropy([n; 32]) or ContextId::from_uuid(Uuid::from_bytes([n; 16]))"
 634        echo -e "    ${YELLOW}Ref:${NC} .claude/skills/patterns/SKILL.md §Test Determinism Violations"
 635      done <<< "$sorted_ids"
 636      if [ "$any" = false ]; then
 637        info "Entropy-consuming identifiers: none (filtered)"
 638      fi
 639    else
 640      info "Entropy-consuming identifiers (AuthorityId::new, ContextId::new, DeviceId::new): none"
 641    fi
 642  
 643    # Check for Uuid::new_v4() which uses system entropy
 644    # Allowed only in: effect handlers, runtime assembly, CLI entry point, tests
 645    uuid_v4_pattern="Uuid::new_v4|uuid::Uuid::new_v4"
 646    uuid_v4_hits=$(rg --no-heading "$uuid_v4_pattern" crates -g "*.rs" || true)
 647    filtered_uuid_v4=$(echo "$uuid_v4_hits" \
 648      | grep -v "$EFFECT_HANDLER_ALLOWLIST" \
 649      | grep -Ev "$RUNTIME_ALLOWLIST" \
 650      | grep -v "$CLI_ENTRY_ALLOWLIST" \
 651      | grep -Ev "$TEST_ALLOWLIST" || true)
 652    if [ -n "$filtered_uuid_v4" ]; then
 653      # Sort by layer and emit with layer prefix, respecting layer filters
 654      sorted_uuids=$(printf "%s\n" "$filtered_uuid_v4" | sort_hits_by_layer)
 655      any=false
 656      while IFS= read -r entry; do
 657        [ -z "$entry" ] && continue
 658        layer_num="${entry:0:2}"
 659        content="${entry:3}"
 660        [ "$layer_num" = "99" ] && layer_num="?"
 661        layer_num="${layer_num#0}"
 662        if ! layer_filter_matches "$layer_num"; then
 663          continue
 664        fi
 665        any=true
 666        violation "[L${layer_num}] Entropy-consuming UUID: $content"
 667        echo -e "    ${YELLOW}Fix:${NC} Use Uuid::nil() for placeholders or Uuid::from_bytes([n; 16]) for deterministic unique IDs"
 668        echo -e "    ${YELLOW}Ref:${NC} .claude/skills/patterns/SKILL.md §Test Determinism Violations"
 669      done <<< "$sorted_uuids"
 670      if [ "$any" = false ]; then
 671        info "Entropy-consuming UUIDs: none (filtered)"
 672      fi
 673    else
 674      info "Entropy-consuming UUIDs (Uuid::new_v4): none"
 675    fi
 676  
 677    # Check for rand::random and thread_rng outside allowed areas
 678    rand_pattern="rand::random|thread_rng\\(\\)|rand::thread_rng"
 679    rand_hits=$(rg --no-heading "$rand_pattern" crates -g "*.rs" || true)
 680    filtered_rand=$(echo "$rand_hits" \
 681      | grep -v "crates/aura-effects/" \
 682      | grep -v "crates/aura-testkit/" \
 683      | grep -v "crates/aura-simulator/" \
 684      | grep -v "crates/aura-agent/src/runtime/" \
 685      | grep -v "/tests/" \
 686      | grep -v "///" \
 687      | grep -v "//!" || true)
 688    if [ -n "$filtered_rand" ]; then
 689      # Sort by layer and emit with layer prefix, respecting layer filters
 690      sorted_rand=$(printf "%s\n" "$filtered_rand" | sort_hits_by_layer)
 691      any=false
 692      while IFS= read -r entry; do
 693        [ -z "$entry" ] && continue
 694        layer_num="${entry:0:2}"
 695        content="${entry:3}"
 696        [ "$layer_num" = "99" ] && layer_num="?"
 697        layer_num="${layer_num#0}"
 698        if ! layer_filter_matches "$layer_num"; then
 699          continue
 700        fi
 701        any=true
 702        violation "[L${layer_num}] Direct randomness: $content"
 703        echo -e "    ${YELLOW}Fix:${NC} Use RandomEffects trait for production code; use deterministic seeds/bytes for tests"
 704        echo -e "    ${YELLOW}Ref:${NC} .claude/skills/patterns/SKILL.md §Test Determinism Violations, docs/805_testing_guide.md"
 705      done <<< "$sorted_rand"
 706      if [ "$any" = false ]; then
 707        info "Direct randomness: none (filtered)"
 708      fi
 709    else
 710      info "Direct randomness (rand::random, thread_rng): none"
 711    fi
 712  fi
 713  
 714  if [ "$RUN_ALL" = true ] || [ "$RUN_GUARDS" = true ]; then
 715    section "Guard chain — all TransportEffects sends must flow through CapGuard → FlowGuard → JournalCoupler (docs/108_transport_and_information_flow.md, docs/001_system_architecture.md §2.1)"
 716    transport_sends=$(rg --no-heading "TransportEffects::(send|open_channel)" crates -g "*.rs" || true)
 717    guard_allowlist="crates/aura-guards/src/guards|crates/aura-protocol/src/handlers/sessions|crates/aura-agent/src/runtime/effects.rs|crates/aura-agent/src/runtime/effects/choreography.rs|tests/|crates/aura-testkit/"
 718    bypass_hits=$(echo "$transport_sends" | grep -Ev "$guard_allowlist" || true)
 719    emit_hits "Potential guard-chain bypass (TransportEffects send/open outside guard modules)" "$bypass_hits"
 720  fi
 721  
 722  if [ "$RUN_ALL" = true ] || [ "$RUN_INVARIANTS" = true ]; then
 723    section "Invariant docs — INVARIANTS.md must include required headings; model after docs/005_system_invariants.md"
 724    invariant_files=$(find crates -name INVARIANTS.md 2>/dev/null | sort)
 725    if [ -z "$invariant_files" ]; then
 726      violation "Invariant docs: none found"
 727    else
 728      for inv in $invariant_files; do
 729        missing=()
 730        for heading in "Invariant Name" "Enforcement Locus" "Failure Mode" "Detection Method"; do
 731          if ! grep -q "$heading" "$inv"; then
 732            missing+=("$heading")
 733          fi
 734        done
 735        if [ ${#missing[@]} -gt 0 ]; then
 736          violation "Invariant doc missing sections [$(IFS=,; echo "${missing[*]}")]: $inv"
 737        else
 738          info "Invariant doc OK: $inv"
 739        fi
 740      done
 741    fi
 742  fi
 743  
 744  if [ "$RUN_ALL" = true ] || [ "$RUN_REG" = true ]; then
 745    section "Handler composition — instantiate aura-effects via EffectRegistry/aura-composition, not direct new(); see docs/106_effect_system_and_runtime.md §3.3 and docs/999_project_structure.md §Layer 3"
 746    handler_pattern="(aura_effects::.*Handler::new|PhysicalTimeHandler::new|RealRandomHandler::new|FilesystemStorageHandler::new|EncryptedStorageHandler::new|TcpNetworkHandler::new|RealCryptoHandler::new)"
 747    instantiation=$(rg --no-heading "$handler_pattern" crates/aura-protocol/src crates/aura-authentication/src crates/aura-chat/src crates/aura-invitation/src crates/aura-recovery/src crates/aura-relational/src crates/aura-rendezvous/src crates/aura-sync/src -g "*.rs" -g "!tests/**/*" || true)
 748    emit_hits "Direct aura-effects handler instantiation found (prefer EffectRegistry / composition)" "$instantiation"
 749  fi
 750  
 751  if [ "$RUN_ALL" = true ] || [ "$RUN_CRYPTO" = true ]; then
 752    section "Crypto library boundaries — route crypto through aura-core wrappers; keep ed25519_dalek/OsRng/getrandom in allowed locations (work/crypto.md, docs/106_effect_system_and_runtime.md)"
 753  
 754    # Allowed locations for direct crypto library usage:
 755    # - Layer 1: aura-core/src/crypto/* (wrapper implementations)
 756    # - Layer 1: aura-core/src/types/authority.rs (type aliases - known design issue)
 757    # - Layer 3: aura-effects/src/* (production handlers)
 758    # - Layer 8: aura-testkit/* (test infrastructure)
 759    # - Test modules: /tests/, *_test.rs
 760    CRYPTO_ALLOWLIST="crates/aura-core/src/crypto/|crates/aura-core/src/types/authority.rs|crates/aura-effects/src/|crates/aura-testkit/|/tests/|_test\\.rs"
 761  
 762    # Allowed locations for direct randomness (OsRng, getrandom):
 763    # - Layer 3: aura-effects/src/* (production handlers)
 764    # - Layer 8: aura-testkit/* (test infrastructure)
 765    # - Test modules: /tests/, *_test.rs
 766    # - #[cfg(test)] modules (detected by context)
 767    RANDOMNESS_ALLOWLIST="crates/aura-effects/src/|crates/aura-testkit/|/tests/|_test\\.rs"
 768  
 769    # Check for direct ed25519_dalek imports outside allowed locations
 770    ed25519_imports=$(rg --no-heading "use ed25519_dalek" crates -g "*.rs" || true)
 771    filtered_ed25519=$(echo "$ed25519_imports" | grep -Ev "$CRYPTO_ALLOWLIST" || true)
 772    if [ -n "$filtered_ed25519" ]; then
 773      emit_hits "Direct ed25519_dalek import (use aura_core::crypto::ed25519 wrappers instead)" "$filtered_ed25519"
 774      echo -e "    ${YELLOW}Allowed locations:${NC}"
 775      echo -e "      - crates/aura-core/src/crypto/* (L1 wrappers)"
 776      echo -e "      - crates/aura-effects/src/* (L3 handlers)"
 777      echo -e "      - crates/aura-testkit/* (L8 testing)"
 778      echo -e "      - /tests/ directories and *_test.rs files"
 779    else
 780      info "Direct ed25519_dalek imports: none outside allowed locations"
 781    fi
 782  
 783    # Check for direct OsRng usage outside allowed locations
 784    # Filter out comments and #[cfg(test)] code
 785    osrng_usage=$(rg --no-heading "OsRng" crates -g "*.rs" || true)
 786    filtered_osrng=$(echo "$osrng_usage" \
 787      | grep -v "///" \
 788      | grep -v "//!" \
 789      | grep -v "// " \
 790      | grep -Ev "$RANDOMNESS_ALLOWLIST" || true)
 791    # Additional filter: skip lines in files after #[cfg(test)]
 792    if [ -n "$filtered_osrng" ]; then
 793      osrng_final=""
 794      while IFS= read -r line; do
 795        [ -z "$line" ] && continue
 796        file_path="${line%%:*}"
 797        if [ -f "$file_path" ] && grep -q "#\[cfg(test)\]" "$file_path" 2>/dev/null; then
 798          # Get line content and check if it's in test module
 799          match_content="${line#*:}"
 800          match_line_num=$(grep -n "$match_content" "$file_path" 2>/dev/null | head -1 | cut -d: -f1)
 801          cfg_test_line=$(grep -n "#\[cfg(test)\]" "$file_path" 2>/dev/null | head -1 | cut -d: -f1)
 802          if [ -n "$match_line_num" ] && [ -n "$cfg_test_line" ] && [ "$match_line_num" -gt "$cfg_test_line" ]; then
 803            continue  # Skip - in test module
 804          fi
 805        fi
 806        osrng_final="${osrng_final}${line}"$'\n'
 807      done <<< "$filtered_osrng"
 808      filtered_osrng="$osrng_final"
 809    fi
 810    if [ -n "$filtered_osrng" ]; then
 811      emit_hits "Direct OsRng usage (use RandomEffects trait instead)" "$filtered_osrng"
 812      echo -e "    ${YELLOW}Allowed locations:${NC}"
 813      echo -e "      - crates/aura-effects/src/* (L3 handlers)"
 814      echo -e "      - crates/aura-testkit/* (L8 testing)"
 815      echo -e "      - #[cfg(test)] modules"
 816    else
 817      info "Direct OsRng usage: none outside allowed locations"
 818    fi
 819  
 820    # Check for direct getrandom usage outside allowed locations
 821    getrandom_usage=$(rg --no-heading "getrandom::" crates -g "*.rs" || true)
 822    filtered_getrandom=$(echo "$getrandom_usage" \
 823      | grep -v "///" \
 824      | grep -v "//" \
 825      | grep -Ev "$RANDOMNESS_ALLOWLIST" || true)
 826    if [ -n "$filtered_getrandom" ]; then
 827      emit_hits "Direct getrandom usage (use RandomEffects trait instead)" "$filtered_getrandom"
 828      echo -e "    ${YELLOW}Allowed locations:${NC}"
 829      echo -e "      - crates/aura-effects/src/* (L3 handlers)"
 830      echo -e "      - crates/aura-testkit/* (L8 testing)"
 831    else
 832      info "Direct getrandom usage: none outside allowed locations"
 833    fi
 834  fi
 835  
 836  if [ "$RUN_ALL" = true ] || [ "$RUN_CONCURRENCY" = true ]; then
 837    section "Concurrency hygiene — avoid block_in_place/block_on and unbounded channels in production code"
 838  
 839    # Flag blocking bridges in async code.
 840    block_bridge_hits=$(rg --no-heading "tokio::task::block_in_place|Handle::current\\(\\)\\.block_on" crates -g "*.rs" || true)
 841    filtered_block_bridge=$(filter_common_allowlist "$block_bridge_hits")
 842    emit_hits "Blocking async bridge (block_in_place / block_on) found" "$filtered_block_bridge"
 843  
 844    # Flag unbounded channels (prefer bounded or coalescing queues).
 845    unbounded_hits=$(rg --no-heading "mpsc::unbounded_channel\\(|async_channel::unbounded\\(|mpsc::unbounded\\(" crates -g "*.rs" || true)
 846    filtered_unbounded=$(filter_common_allowlist "$unbounded_hits")
 847    emit_hits "Unbounded channel usage (prefer bounded/coalescing)" "$filtered_unbounded"
 848  fi
 849  
 850  if [ "$RUN_ALL" = true ] || [ "$RUN_REACTIVE" = true ]; then
 851    section "Reactive data model — signals are source of truth; no domain data in props; components subscribe to signals (docs/115_cli_tui.md)"
 852  
 853    # Check 1: Props structs with explicit "Domain data" comments
 854    # This catches the current pattern where domain data is explicitly marked in props
 855    domain_data_in_props=$(rg --no-heading -l "// === Domain data" crates/aura-terminal/src/tui/screens -g "*.rs" || true)
 856    if [ -n "$domain_data_in_props" ]; then
 857      # Count unique files with domain data in props
 858      file_count=$(echo "$domain_data_in_props" | wc -l | tr -d ' ')
 859      violation "[L7] Domain data in props: $file_count screen(s) pass domain data as props instead of subscribing to signals"
 860      echo -e "    ${YELLOW}Files:${NC}"
 861      echo "$domain_data_in_props" | while read -r f; do
 862        [ -n "$f" ] && echo "      - $f"
 863      done
 864      echo -e "    ${YELLOW}Fix:${NC} Remove domain data fields from *ScreenProps; subscribe to signals in component"
 865      echo -e "    ${YELLOW}Ref:${NC} docs/115_cli_tui.md §Reactive data model"
 866    else
 867      info "Domain data in props: none (all screens use signal subscriptions)"
 868    fi
 869  
 870    # Check 2: Known domain types in Props structs that should come from signals
 871    # These types are domain data that should be subscribed to, not passed as props
 872    domain_types="Vec<Contact>|Vec<Channel>|Vec<Message>|Vec<Guardian>|Vec<Device>|Vec<Resident>|Vec<BlockSummary>|Vec<PendingRequest>"
 873  
 874    # Find Props structs containing domain types
 875    # Use multiline matching to find struct definitions with these types
 876    domain_type_hits=$(rg --no-heading "$domain_types" crates/aura-terminal/src/tui/screens -g "*screen.rs" || true)
 877    # Filter to only Props structs (lines near "Props" or containing "pub ")
 878    filtered_domain_types=$(echo "$domain_type_hits" | grep -E "pub |ScreenProps" | grep -v "// Subscribe" | grep -v "use_state" || true)
 879  
 880    if [ -n "$filtered_domain_types" ]; then
 881      type_count=$(echo "$filtered_domain_types" | wc -l | tr -d ' ')
 882      if [ "$type_count" -gt 0 ]; then
 883        verbose "Domain types in screen files (may be in Props or local state):"
 884        echo "$filtered_domain_types" | head -5 | while read -r line; do
 885          verbose "  $line"
 886        done
 887      fi
 888    fi
 889  
 890    # Check 3: Screen components that don't subscribe to any signals
 891    # Each screen.rs should have at least one subscribe_signal_with_retry call
 892    screens_dir="crates/aura-terminal/src/tui/screens"
 893    if [ -d "$screens_dir" ]; then
 894      missing_subscriptions=""
 895      for screen_file in $(find "$screens_dir" -name "screen.rs" 2>/dev/null); do
 896        # Check if the file has a signal subscription
 897        if ! grep -q "subscribe_signal_with_retry\|SIGNAL" "$screen_file" 2>/dev/null; then
 898          missing_subscriptions="${missing_subscriptions}${screen_file}"$'\n'
 899        fi
 900      done
 901      if [ -n "$missing_subscriptions" ]; then
 902        emit_hits "Screen without signal subscription (should subscribe to domain signals)" "$missing_subscriptions"
 903      else
 904        info "All screens subscribe to signals"
 905      fi
 906    fi
 907  
 908    verbose "Reactive pattern: Props should only contain view state (focus, selection), callbacks, and configuration"
 909    verbose "Domain data (contacts, messages, guardians, etc.) should come from signal subscriptions"
 910  fi
 911  
 912  if [ "$RUN_ALL" = true ] || [ "$RUN_UI" = true ]; then
 913    section "UI boundary — aura-terminal uses aura_app::ui facade; no direct protocol/journal access"
 914  
 915    direct_app_modules=$(rg --no-heading "aura_app::(workflows|signal_defs|views|runtime_bridge|authorization)" crates/aura-terminal/src -g "*.rs" || true)
 916    filtered_app_modules=$(echo "$direct_app_modules" | grep -v "///" | grep -v "//" || true)
 917    emit_hits "Direct aura_app module access in aura-terminal (use aura_app::ui::* facade)" "$filtered_app_modules"
 918  
 919    view_access_hits=$(rg --no-heading "\\.views\\(" crates/aura-terminal/src -g "*.rs" || true)
 920    emit_hits "Direct ViewState access in aura-terminal (use signals)" "$view_access_hits"
 921  
 922    journal_hits=$(rg --no-heading "FactRegistry|FactReducer|RelationalFact|JournalEffects|commit_.*facts|RuntimeBridge::commit" crates/aura-terminal/src -g "*.rs" || true)
 923    emit_hits "Direct journal/protocol mutation in aura-terminal (use workflows)" "$journal_hits"
 924  
 925    forbidden_crate_hits=$(rg --no-heading "aura_(journal|protocol|consensus|guards|amp|anti_entropy|transport|recovery|sync|invitation|authentication|relational|chat)::" crates/aura-terminal/src -g "*.rs" || true)
 926    emit_hits "Direct protocol/domain crate usage in aura-terminal (use aura_app::ui facade)" "$forbidden_crate_hits"
 927  
 928    section "Terminal time — use algebraic effects (PhysicalTimeEffects), no OS time"
 929  
 930    terminal_time_hits=$(rg --no-heading -n "SystemTime::now|Instant::now|std::time::Instant|std::time::SystemTime|chrono::Utc::now|chrono::Local::now" crates/aura-terminal/src -g "*.rs" || true)
 931    filtered_terminal_time=$(echo "$terminal_time_hits" \
 932      | grep -v "///" \
 933      | grep -v "//!" \
 934      | grep -v "//" || true)
 935    emit_hits "Direct OS time usage in aura-terminal (use PhysicalTimeEffects)" "$filtered_terminal_time"
 936  
 937    section "Terminal business logic — validation and domain state should be in aura_app::workflows"
 938  
 939    # Check for threshold validation logic in terminal (should use workflows::account)
 940    # Filter: threshold vs num_devices/configs.len comparisons (domain validation)
 941    # Exclude: progress() calculations, UI-only checks, tests, and lines using workflow
 942    threshold_validation=$(rg --no-heading "threshold\s*(>|<|==|!=)\s*(num_devices|configs\.len|0)" crates/aura-terminal/src -g "*.rs" || true)
 943    filtered_threshold=""
 944    if [ -n "$threshold_validation" ]; then
 945      while IFS= read -r hit; do
 946        [ -z "$hit" ] && continue
 947        file="${hit%%:*}"
 948        # Check if previous line has UI/division/guard comment
 949        line_content="${hit#*:}"
 950        line_num=$(grep -n "$line_content" "$file" 2>/dev/null | head -1 | cut -d: -f1)
 951        if [ -n "$line_num" ] && [ "$line_num" -gt 1 ]; then
 952          prev_line=$((line_num - 1))
 953          prev_content=$(sed -n "${prev_line}p" "$file" 2>/dev/null)
 954          # Skip if previous line has UI/progress/division comment
 955          if echo "$prev_content" | grep -qiE "// (UI|progress|Division|guard|not domain)"; then
 956            continue
 957          fi
 958        fi
 959        # Skip if line itself has workflow/test markers
 960        if echo "$hit" | grep -qE "(uses workflow|workflows::account|/tests/)"; then
 961          continue
 962        fi
 963        filtered_threshold="${filtered_threshold}${hit}"$'\n'
 964      done <<< "$threshold_validation"
 965    fi
 966    emit_hits "Threshold validation in terminal (use aura_app::ui::workflows::account)" "$filtered_threshold"
 967  
 968    # Check for local domain state (HashSet/HashMap of peers, guardians, etc)
 969    # These should use AppCore signals instead
 970    local_domain_state=$(rg --no-heading "HashSet<.*Id>|HashMap<.*Id," crates/aura-terminal/src/handlers -g "*.rs" || true)
 971    filtered_domain_state=$(echo "$local_domain_state" \
 972      | grep -v "// temporary" \
 973      | grep -v "// local cache" \
 974      | grep -v "/tests/" || true)
 975    emit_hits "Local domain state in terminal handlers (use AppCore signals)" "$filtered_domain_state"
 976  
 977    # Check for guardian count validation in terminal (should be in workflows)
 978    guardian_validation=$(rg --no-heading "guardians?\.(len|count|is_empty)\(\)\s*(>|<|==|>=|<=)" crates/aura-terminal/src -g "*.rs" || true)
 979    filtered_guardian=$(echo "$guardian_validation" \
 980      | grep -v "// uses workflow" \
 981      | grep -v "/tests/" || true)
 982    if [ -n "$filtered_guardian" ]; then
 983      emit_hits "Guardian count validation in terminal (consider moving to workflow)" "$filtered_guardian"
 984    fi
 985  
 986    verbose "Terminal layer should only handle I/O and formatting; all business logic belongs in aura_app::workflows"
 987  fi
 988  
 989  if [ "$RUN_ALL" = true ] || [ "$RUN_WORKFLOWS" = true ]; then
 990    section "Workflow hygiene — use helpers for runtime access, parsing, and signals"
 991  
 992    runtime_string_hits=$(rg --no-heading "Runtime bridge not available" crates/aura-app/src/workflows -g "*.rs" || true)
 993    filtered_runtime_string_hits=$(echo "$runtime_string_hits" \
 994      | grep -v "crates/aura-app/src/workflows/runtime.rs" \
 995      | grep -v '\.contains(' || true)
 996    emit_hits "Direct runtime error strings in workflows (use workflows::runtime::require_runtime for consistent errors + wiring)" "$filtered_runtime_string_hits"
 997  
 998    parse_authority_hits=$(rg --no-heading "parse::<AuthorityId>" crates/aura-app/src/workflows -g "*.rs" || true)
 999    filtered_parse_authority_hits=$(echo "$parse_authority_hits" | grep -v "crates/aura-app/src/workflows/parse.rs" || true)
1000    emit_hits "Direct AuthorityId parsing in workflows (use workflows::parse::parse_authority_id for normalized errors)" "$filtered_parse_authority_hits"
1001  
1002    parse_context_hits=$(rg --no-heading "parse::<ContextId>" crates/aura-app/src/workflows -g "*.rs" || true)
1003    filtered_parse_context_hits=$(echo "$parse_context_hits" | grep -v "crates/aura-app/src/workflows/parse.rs" || true)
1004    emit_hits "Direct ContextId parsing in workflows (use workflows::parse::parse_context_id for normalized errors)" "$filtered_parse_context_hits"
1005  
1006    signal_access_hits=$(rg --no-heading "\\.(read|emit)\\(&\\*.*_SIGNAL" crates/aura-app/src/workflows -g "*.rs" || true)
1007    filtered_signal_access_hits=$(echo "$signal_access_hits" | grep -v "crates/aura-app/src/workflows/signals.rs" || true)
1008    emit_hits "Direct signal access in workflows (use workflows::signals::{read_signal, emit_signal} + signal_defs::*_SIGNAL_NAME constants)" "$filtered_signal_access_hits"
1009  
1010    init_signals_hits=$(rg --no-heading "init_signals\\(" crates/aura-app/src -g "*.rs" || true)
1011    filtered_init_signals_hits=$(echo "$init_signals_hits" \
1012      | grep -v "crates/aura-app/src/core/app.rs" \
1013      | grep -v "init_signals_with_hooks" || true)
1014    emit_hits "Direct init_signals calls (use AppCore::init_signals_with_hooks to attach workflow hooks)" "$filtered_init_signals_hits"
1015  fi
1016  
1017  if [ "$RUN_ALL" = true ] || [ "$RUN_SERIALIZATION" = true ]; then
1018    section "Serialization — use DAG-CBOR (aura_core::util::serialization) for all wire protocols and facts; no bincode anywhere"
1019  
1020    # Check for bincode usage - no allowlist, bincode should not be used anywhere
1021    bincode_hits=$(rg --no-heading "bincode::" crates -g "*.rs" || true)
1022    filtered_bincode=$(echo "$bincode_hits" \
1023      | grep -v "/examples/" \
1024      | grep -v "benches/" || true)
1025  
1026    if [ -n "$filtered_bincode" ]; then
1027      emit_hits "bincode usage (migrate to aura_core::util::serialization)" "$filtered_bincode"
1028      echo -e "    ${YELLOW}Migration:${NC} bincode::serialize → to_vec, bincode::deserialize → from_slice"
1029      echo -e "    ${YELLOW}Canonical import:${NC} use aura_core::util::serialization::{to_vec, from_slice, hash_canonical};"
1030      echo -e "    ${YELLOW}Ref:${NC} work/024.md (serialization migration)"
1031    else
1032      info "bincode usage: none outside testkit"
1033    fi
1034  
1035    # Check that wire protocol files use canonical serialization
1036    # Wire protocol files (wire.rs, protocol messages) should use DAG-CBOR
1037    wire_files=$(find crates -type f \( -name "wire.rs" -o -name "*_wire.rs" \) 2>/dev/null || true)
1038    non_canonical_wire=""
1039    for wire_file in $wire_files; do
1040      # Check if file uses canonical serialization or has no serialization at all
1041      if grep -q "serde_json::to_vec\|serde_json::from_slice\|bincode::" "$wire_file" 2>/dev/null; then
1042        if ! grep -q "aura_core::util::serialization\|crate::util::serialization" "$wire_file" 2>/dev/null; then
1043          non_canonical_wire="${non_canonical_wire}${wire_file}"$'\n'
1044        fi
1045      fi
1046    done
1047    if [ -n "$non_canonical_wire" ]; then
1048      emit_hits "Wire protocol without canonical DAG-CBOR serialization" "$non_canonical_wire"
1049    else
1050      info "Wire protocols: using canonical serialization"
1051    fi
1052  
1053    # Check facts.rs files for proper versioned serialization
1054    facts_files=$(find crates -path "*/src/facts.rs" -type f 2>/dev/null | grep -v aura-core || true)
1055    non_versioned_facts=""
1056    for facts_file in $facts_files; do
1057      # Facts files should have VersionedMessage or use canonical serialization
1058      if grep -q "Serialize\|Deserialize" "$facts_file" 2>/dev/null; then
1059        if ! grep -qE "aura_core::util::serialization|Versioned.*Fact|from_slice|to_vec" "$facts_file" 2>/dev/null; then
1060          non_versioned_facts="${non_versioned_facts}${facts_file}"$'\n'
1061        fi
1062      fi
1063    done
1064    if [ -n "$non_versioned_facts" ]; then
1065      emit_hits "Facts file without versioned DAG-CBOR serialization" "$non_versioned_facts"
1066    else
1067      info "Facts files: using versioned serialization"
1068    fi
1069  
1070    verbose "Canonical serialization: aura_core::util::serialization::{to_vec, from_slice, hash_canonical}"
1071    verbose "Allowed alternatives: serde_json for debug output, config files, dynamic metadata"
1072  fi
1073  
1074  if [ "$RUN_ALL" = true ] || [ "$RUN_EFFECTS" = true ]; then
1075    section "Identifier invariants — deterministic SessionId::new() is test-only"
1076  
1077    session_id_hits=$(rg --no-heading "SessionId::new\\(" crates -g "*.rs" || true)
1078    filtered_session_id=$(echo "$session_id_hits" \
1079      | grep -v "/tests/" \
1080      | grep -v "/benches/" \
1081      | grep -v "/examples/" \
1082      | grep -v "cfg(test)" \
1083      | grep -v "cfg\\(test\\)" || true)
1084  
1085    if [ -n "$filtered_session_id" ]; then
1086      emit_hits "SessionId::new() used outside tests (use SessionId::new_from_entropy / RandomEffects)" "$filtered_session_id"
1087    else
1088      info "SessionId::new(): none outside tests"
1089    fi
1090  fi
1091  
1092  if [ "$RUN_ALL" = true ] || [ "$RUN_EFFECTS" = true ]; then
1093    section "Runtime handler hygiene — handlers are stateless; no bridge modules in aura-agent"
1094  
1095    handler_state_hits=$(rg --no-heading "Arc<.*(RwLock|Mutex)|RwLock<|Mutex<" crates/aura-agent/src/handlers -g "*.rs" || true)
1096    filtered_handler_state="$handler_state_hits"
1097    if [ -n "$HANDLER_STATE_ALLOWLIST" ]; then
1098      filtered_handler_state=$(echo "$filtered_handler_state" | grep -Ev "$HANDLER_STATE_ALLOWLIST" || true)
1099    fi
1100    emit_hits "Stateful handlers detected (move state into runtime/services managers)" "$filtered_handler_state"
1101  
1102    handler_bridge_files=$(rg --files -g "*bridge*.rs" crates/aura-agent/src/handlers 2>/dev/null || true)
1103    if [ -n "$handler_bridge_files" ]; then
1104      filtered_handler_bridges="$handler_bridge_files"
1105      if [ -n "$HANDLER_BRIDGE_ALLOWLIST" ]; then
1106        filtered_handler_bridges=$(echo "$filtered_handler_bridges" | grep -Ev "$HANDLER_BRIDGE_ALLOWLIST" || true)
1107      fi
1108      if [ -n "$filtered_handler_bridges" ]; then
1109        emit_hits "Handler bridge modules present (merge into handler/service)" "$filtered_handler_bridges"
1110      else
1111        info "Handler bridge modules: none outside allowlist"
1112      fi
1113    else
1114      info "Handler bridge modules: none"
1115    fi
1116  fi
1117  
1118  if [ "$RUN_ALL" = true ] || [ "$RUN_STYLE" = true ]; then
1119    section "Rust Style Guide — safety and API rules (work/030.md)"
1120  
1121    # Safety §3: "Prefer explicitly-sized integers, avoid usize in stored formats"
1122    # Find structs with Serialize/Deserialize that contain usize fields
1123    # Use -U for multiline matching to catch struct definitions spanning lines
1124    usize_serialized=$(rg --no-heading -n "usize" crates -g "*.rs" \
1125      | xargs -I{} sh -c 'file="${1%%:*}"; if grep -l "#\[derive.*Serialize" "$file" >/dev/null 2>&1; then echo "$1"; fi' _ {} 2>/dev/null || true)
1126    filtered_usize=$(echo "$usize_serialized" \
1127      | grep -v "/tests/" \
1128      | grep -v "/benches/" \
1129      | grep -v "crates/aura-testkit/" \
1130      | grep -v "// usize ok:" \
1131      | grep -v "fn " \
1132      | grep -v "let " \
1133      | grep -v "for " \
1134      | grep -v "impl " || true)
1135    # Only show field definitions (pub xxx: usize or xxx: usize,)
1136    field_usize=$(echo "$filtered_usize" | grep -E ":\s*usize\s*[,}]|pub\s+\w+:\s*usize" || true)
1137    emit_hits "usize in serialized struct field (use u32/u64 for wire formats; Safety §3)" "$field_usize"
1138    verbose "Add '// usize ok: <reason>' comment to suppress false positives"
1139  
1140    # Safety §2: "Every queue, buffer, batch, map must have a hard upper bound"
1141    # Find Vec<u8> fields in core types (signatures, payloads, ciphertext) without MAX_* constants
1142    unbounded_bytes=$(rg --no-heading -n "pub\s+\w+:\s*Vec<u8>" crates/aura-core/src -g "*.rs" || true)
1143    if [ -n "$unbounded_bytes" ]; then
1144      missing_bounds=""
1145      while IFS= read -r hit; do
1146        [ -z "$hit" ] && continue
1147        file="${hit%%:*}"
1148        # Check if file has a MAX_*_BYTES or MAX_*_SIZE constant
1149        if ! grep -qE "const\s+MAX_.*_(BYTES|SIZE|LEN)" "$file" 2>/dev/null; then
1150          missing_bounds="${missing_bounds}${hit}"$'\n'
1151        fi
1152      done <<< "$unbounded_bytes"
1153      emit_hits "Vec<u8> field without MAX_*_BYTES constant in same file (Safety §2)" "$missing_bounds"
1154    else
1155      info "Vec<u8> bounds: all core types have MAX_* constants"
1156    fi
1157  
1158    # Safety §2: "Encode limits as constants with units in the name"
1159    # Find numeric constants without unit suffixes
1160    constants_no_units=$(rg --no-heading -n "const\s+[A-Z][A-Z0-9_]+:\s*(u\d+|i\d+|usize)\s*=\s*\d+" crates/aura-core/src -g "*.rs" \
1161      | grep -vE "_(MS|BYTES|COUNT|SIZE|MAX|MIN|LEN|LIMIT|DEPTH|HEIGHT|BITS|SECS|NANOS)(\s*:|:)" \
1162      | grep -vE "VERSION|MAGIC|EPOCH|THRESHOLD|FACTOR|RATIO|WIRE_FORMAT|DEFAULT_" \
1163      | grep -v "/tests/" \
1164      | grep -v "/benches/" || true)
1165    if [ -n "$constants_no_units" ]; then
1166      emit_hits "Numeric constant without unit suffix (_MS, _BYTES, _COUNT, etc.; Safety §2)" "$constants_no_units"
1167      verbose "Expected patterns: TIMEOUT_MS, BATCH_SIZE_MAX, MAX_RETRY_COUNT, BUFFER_SIZE_BYTES"
1168    else
1169      info "Constants with units: all numeric constants have unit suffixes"
1170    fi
1171  
1172    # Style by Numbers: "#[must_use] for APIs where dropping the value is likely a bug"
1173    # Find builder methods (with_*) without #[must_use]
1174    builder_methods=$(rg --no-heading -n "pub\s+(const\s+)?fn\s+with_\w+\s*\(" crates/aura-core/src -g "*.rs" || true)
1175    if [ -n "$builder_methods" ]; then
1176      missing_must_use=""
1177      while IFS= read -r hit; do
1178        [ -z "$hit" ] && continue
1179        file="${hit%%:*}"
1180        rest="${hit#*:}"
1181        linenum="${rest%%:*}"
1182        # Check if previous 1-3 lines have #[must_use]
1183        has_must_use=false
1184        for offset in 1 2 3; do
1185          prev_line=$((linenum - offset))
1186          if [ "$prev_line" -gt 0 ]; then
1187            if sed -n "${prev_line}p" "$file" 2>/dev/null | grep -q "#\[must_use\]"; then
1188              has_must_use=true
1189              break
1190            fi
1191          fi
1192        done
1193        if [ "$has_must_use" = false ]; then
1194          missing_must_use="${missing_must_use}${hit}"$'\n'
1195        fi
1196      done <<< "$builder_methods"
1197      emit_hits "Builder method without #[must_use] (Style by Numbers)" "$missing_must_use"
1198    else
1199      info "Builder methods: all have #[must_use]"
1200    fi
1201  
1202    info "Rust style guide checks complete (see also: clippy lints in Cargo.toml)"
1203  fi
1204  
1205  if [ "$RUN_ALL" = true ] || [ "$RUN_TODOS" = true ]; then
1206    section "Production placeholders — replace nil UUIDs/placeholder implementations with real IDs/derivations (see docs/105_identifiers_and_boundaries.md, docs/001_system_architecture.md §1.4)"
1207    # Note: "placeholder" in UI code (input hints, props.placeholder) is intentional and not a violation
1208    # Only flag "placeholder implementation" and Uuid::nil() which indicate incomplete code
1209    placeholder_hits=$(rg --no-heading -i "uuid::nil\\(\\)|placeholder implementation" crates -g "*.rs" \
1210      | grep -v "/tests/" \
1211      | grep -v "/benches/" \
1212      | grep -v "/examples/" || true)
1213    if [ -n "$placeholder_hits" ]; then
1214      formatted=$(while IFS= read -r entry; do
1215        [ -z "$entry" ] && continue
1216        echo "$entry -- Action: derive real identifiers via AuthorityId/ContextId or deterministic key derivation"
1217      done <<< "$placeholder_hits")
1218      emit_hits "Placeholder identity/ID use" "$formatted"
1219    else
1220      info "Placeholder identity/ID use: none"
1221    fi
1222  
1223    section "Deterministic algorithm TODOs — replace vague notes with implemented deterministic paths (docs/108_transport_and_information_flow.md, docs/003_information_flow_contract.md)"
1224    deterministic_hits=$(rg --no-heading -i "deterministic algorithm" crates -g "*.rs" \
1225      | grep -v "/tests/" \
1226      | grep -v "/benches/" \
1227      | grep -v "/examples/" || true)
1228    if [ -n "$deterministic_hits" ]; then
1229      formatted=$(while IFS= read -r entry; do
1230        [ -z "$entry" ] && continue
1231        echo "$entry -- Action: implement deterministic selection/ordering per transport/guard specs; avoid entropy leaks"
1232      done <<< "$deterministic_hits")
1233      emit_hits "Deterministic algorithm stub" "$formatted"
1234    else
1235      info "Deterministic algorithm stubs: none"
1236    fi
1237  
1238    section "Temporary context fallbacks — ensure real context resolution instead of temp contexts (docs/103_relational_contexts.md, docs/001_system_architecture.md §1.4)"
1239    temp_ctx_hits=$(rg --no-heading -i "temporary context|temp context" crates -g "*.rs" \
1240      | grep -v "/tests/" \
1241      | grep -v "/benches/" \
1242      | grep -v "/examples/" || true)
1243    if [ -n "$temp_ctx_hits" ]; then
1244      formatted=$(while IFS= read -r entry; do
1245        [ -z "$entry" ] && continue
1246        echo "$entry -- Action: resolve ContextId via relational/journal state; remove temp context creation to avoid guard bypass"
1247      done <<< "$temp_ctx_hits")
1248      emit_hits "Temporary context fallback" "$formatted"
1249    else
1250      info "Temporary context fallbacks: none"
1251    fi
1252  
1253    section "TODO/FIXME — convert to tracked issues or implement; prioritize architecture/compliance blockers first"
1254    # PLATFORM_BUILDERS_ALLOWLIST: Platform preset builders (android.rs, ios.rs, web.rs) contain
1255    # explicit TODOs for future platform handlers. These are behind feature flags and already
1256    # return proper errors explaining the platform isn't implemented. Not architecture blockers.
1257    PLATFORM_BUILDERS_ALLOWLIST="crates/aura-agent/src/builder/android.rs|crates/aura-agent/src/builder/ios.rs|crates/aura-agent/src/builder/web.rs"
1258    # TUI_FEATURE_ALLOWLIST: L7 UI feature work for callback implementation and modal state
1259    # propagation. These are tracked enhancement items, not architecture violations:
1260    # - shell.rs: channel deletion, contact removal, invitation revocation callbacks
1261    # - state_machine.rs: passing selected channel info to modals
1262    TUI_FEATURE_ALLOWLIST="Implement channel deletion callback|Implement contact removal callback|Implement invitation revocation callback|Pass actual channel"
1263    todo_hits=$(rg --no-heading "TODO|FIXME" crates -g "*.rs" \
1264      | grep -v "/tests/" \
1265      | grep -v "/benches/" \
1266      | grep -v "/examples/" \
1267      | grep -vE "$PLATFORM_BUILDERS_ALLOWLIST" \
1268      | grep -vE "$TUI_FEATURE_ALLOWLIST" || true)
1269    emit_hits "TODO/FIXME" "$todo_hits"
1270  
1271    section "Incomplete markers — replace \"in production\"/WIP text with TODOs or complete implementation per docs/805_development_patterns.md"
1272    incomplete_pattern="in production[^\\n]*(would|should|not)|stub|not implemented|unimplemented|temporary|workaround|hacky|\\bWIP\\b|\\bTBD\\b|prototype|future work|to be implemented"
1273    incomplete_hits=$(rg --no-heading -i "$incomplete_pattern" crates -g "*.rs" || true)
1274    # INTENTIONAL_STUBS_ALLOWLIST: Documented development/testing APIs that are intentionally "stubs"
1275    # - biscuit_capability_stub: Explicit fallback API for Biscuit capability checking when
1276    #   RuntimeBridge isn't available. Documented in dispatcher.rs with clear integration guidance.
1277    #   All related comments (Stub implementation, In production, allowed in stub, etc.) are part
1278    #   of this documented API and not incomplete code.
1279    # - "in production this would be": Accurate description of placeholder behavior in workflows
1280    #   that will be updated when user identity propagation is implemented.
1281    INTENTIONAL_STUBS_ALLOWLIST="biscuit_capability_stub|in production this would be the actual|effects/dispatcher.rs.*[Ss]tub|effects/dispatcher.rs.*[Ii]n production"
1282    # Filter out tests, benches, examples, bin/ directories, and intentional stubs
1283    filtered_incomplete=$(echo "$incomplete_hits" \
1284      | grep -v "/tests/" \
1285      | grep -v "/benches/" \
1286      | grep -v "/examples/" \
1287      | grep -v "/bin/" \
1288      | grep -vE "$INTENTIONAL_STUBS_ALLOWLIST" \
1289      | grep -E "//" || true)
1290    if [ -n "$filtered_incomplete" ]; then
1291      emit_hits "Incomplete/WIP marker" "$filtered_incomplete"
1292    else
1293      info "Incomplete/WIP markers: none"
1294    fi
1295  fi
1296  
1297  echo -e "\n${BOLD}${CYAN}Summary${NC}"
1298  if [ $VIOLATIONS -eq 0 ]; then
1299    echo -e "${GREEN}✔ No violations${NC}"
1300  else
1301    echo -e "${RED}✖ $VIOLATIONS violation(s)${NC}"
1302    if [ "$VERBOSE" = true ] && [ ${#VIOLATION_DETAILS[@]} -gt 0 ]; then
1303      echo -e "\n${BOLD}Violation details:${NC}"
1304      for detail in "${VIOLATION_DETAILS[@]}"; do
1305        echo "  - $detail"
1306      done
1307    fi
1308  fi
1309  
1310  # Show quick mode hint if many violations
1311  if [ $VIOLATIONS -gt 10 ] && [ "$RUN_QUICK" = false ]; then
1312    echo -e "\n${YELLOW}Tip:${NC} Use --quick to skip TODO/placeholder checks for faster iteration"
1313  fi
1314  
1315  exit $([ $VIOLATIONS -eq 0 ] && echo 0 || echo 1)