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)