fix(indexer): defer final index-run metadata update on targeted watch… #2102
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # .github/workflows/ci.yml | |
| # Continuous Integration: lint, test, audit, and build verification | |
| name: CI | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| branches: [main] | |
| workflow_dispatch: | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| permissions: | |
| contents: read | |
| env: | |
| CARGO_TERM_COLOR: always | |
| RUST_BACKTRACE: 1 | |
| RUST_LOG: debug | |
| jobs: | |
| # No-mock policy audit | |
| no-mock-audit: | |
| name: No-Mock Policy Audit | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| steps: | |
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | |
| - name: Run repository artifact hygiene check | |
| run: ./scripts/validate_ci.sh --artifact-hygiene-only | |
| - name: Install ripgrep | |
| run: sudo apt-get update && sudo apt-get install -y ripgrep | |
| - name: Run no-mock audit | |
| id: audit | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| mkdir -p test-results | |
| ALLOWLIST_FILE="tests/policies/no_mock_allowlist.json" | |
| AUDIT_REPORT="test-results/no_mock_ci_audit.md" | |
| echo "# No-Mock CI Audit" > "$AUDIT_REPORT" | |
| echo "" >> "$AUDIT_REPORT" | |
| echo "**Run:** $(date -u +%Y-%m-%dT%H:%M:%SZ)" >> "$AUDIT_REPORT" | |
| echo "**Commit:** ${{ github.sha }}" >> "$AUDIT_REPORT" | |
| echo "" >> "$AUDIT_REPORT" | |
| # Search for mock/fake/stub patterns | |
| VIOLATIONS=$(mktemp) | |
| rg -n "(Mock[A-Z][a-z]|Fake[A-Z][a-z]|Stub[A-Z][a-z]|mock_|fake_|stub_)" \ | |
| --glob '!**/node_modules/**' \ | |
| --glob '!target/**' \ | |
| --glob '!.git/**' \ | |
| --glob '!tests/fixtures/**' \ | |
| --glob '!test-results/**' \ | |
| --glob '!*.md' \ | |
| --glob '!*.json' \ | |
| src/ tests/ 2>/dev/null > "$VIOLATIONS" || true | |
| VIOLATION_COUNT=$(wc -l < "$VIOLATIONS" | tr -d ' ') | |
| if [ "$VIOLATION_COUNT" -eq 0 ]; then | |
| echo "## Status: ✅ PASS" >> "$AUDIT_REPORT" | |
| echo "" >> "$AUDIT_REPORT" | |
| echo "No mock/fake/stub patterns found." >> "$AUDIT_REPORT" | |
| echo "status=pass" >> "$GITHUB_OUTPUT" | |
| rm -f "$VIOLATIONS" | |
| exit 0 | |
| fi | |
| echo "Found $VIOLATION_COUNT pattern(s), checking allowlist..." | |
| if [ ! -f "$ALLOWLIST_FILE" ]; then | |
| echo "## Status: ❌ FAIL" >> "$AUDIT_REPORT" | |
| echo "" >> "$AUDIT_REPORT" | |
| echo "Allowlist file not found: $ALLOWLIST_FILE" >> "$AUDIT_REPORT" | |
| echo "status=fail" >> "$GITHUB_OUTPUT" | |
| rm -f "$VIOLATIONS" | |
| exit 1 | |
| fi | |
| ALLOWLIST_ENTRIES=$(jq -r '.entries[] | "\(.path):\(.pattern)"' "$ALLOWLIST_FILE" 2>/dev/null || echo "") | |
| UNALLOWED_COUNT=0 | |
| UNALLOWED_LIST="" | |
| while IFS= read -r line; do | |
| FILE=$(echo "$line" | cut -d: -f1) | |
| PATTERN=$(echo "$line" | grep -oiE "(Mock[A-Z][a-zA-Z]*|Fake[A-Z][a-zA-Z]*|Stub[A-Z][a-zA-Z]*|mock_[a-z_]+|fake_[a-z_]+|stub_[a-z_]+)" | head -1) | |
| ALLOWED=false | |
| for entry in $ALLOWLIST_ENTRIES; do | |
| ENTRY_PATH=$(echo "$entry" | cut -d: -f1) | |
| ENTRY_PATTERN=$(echo "$entry" | cut -d: -f2) | |
| if [[ "$FILE" == *"$ENTRY_PATH"* ]] && [[ "$PATTERN" == *"$ENTRY_PATTERN"* || "$ENTRY_PATTERN" == *"$PATTERN"* ]]; then | |
| ALLOWED=true | |
| break | |
| fi | |
| done | |
| if [ "$ALLOWED" = false ]; then | |
| UNALLOWED_COUNT=$((UNALLOWED_COUNT + 1)) | |
| UNALLOWED_LIST="${UNALLOWED_LIST}\n- \`${line}\`" | |
| fi | |
| done < "$VIOLATIONS" | |
| rm -f "$VIOLATIONS" | |
| if [ "$UNALLOWED_COUNT" -gt 0 ]; then | |
| echo "## Status: ❌ FAIL" >> "$AUDIT_REPORT" | |
| echo "" >> "$AUDIT_REPORT" | |
| echo "**Unapproved patterns:** $UNALLOWED_COUNT" >> "$AUDIT_REPORT" | |
| echo "" >> "$AUDIT_REPORT" | |
| echo "### Violations" >> "$AUDIT_REPORT" | |
| echo -e "$UNALLOWED_LIST" >> "$AUDIT_REPORT" | |
| echo "" >> "$AUDIT_REPORT" | |
| echo "### How to Fix" >> "$AUDIT_REPORT" | |
| echo "" >> "$AUDIT_REPORT" | |
| echo "1. Replace mock/fake/stub with real fixtures (preferred)" >> "$AUDIT_REPORT" | |
| echo "2. OR add to \`tests/policies/no_mock_allowlist.json\` with:" >> "$AUDIT_REPORT" | |
| echo " - \`rationale\`: Why this exception is necessary" >> "$AUDIT_REPORT" | |
| echo " - \`review_date\`: 6-month review date" >> "$AUDIT_REPORT" | |
| echo " - \`permanent: true\` only for true platform boundaries" >> "$AUDIT_REPORT" | |
| echo "" >> "$AUDIT_REPORT" | |
| echo "See TESTING.md 'No-Mock Policy' for details." >> "$AUDIT_REPORT" | |
| echo "status=fail" >> "$GITHUB_OUTPUT" | |
| exit 1 | |
| fi | |
| echo "## Status: ✅ PASS" >> "$AUDIT_REPORT" | |
| echo "" >> "$AUDIT_REPORT" | |
| echo "**Total patterns found:** $VIOLATION_COUNT" >> "$AUDIT_REPORT" | |
| echo "**All patterns allowlisted:** Yes" >> "$AUDIT_REPORT" | |
| echo "" >> "$AUDIT_REPORT" | |
| echo "### Allowlist Summary" >> "$AUDIT_REPORT" | |
| echo "" >> "$AUDIT_REPORT" | |
| jq -r '.entries[] | "- `\(.path)`: \(.pattern)` — \(.rationale)"' "$ALLOWLIST_FILE" >> "$AUDIT_REPORT" | |
| echo "status=pass" >> "$GITHUB_OUTPUT" | |
| - name: Upload audit report | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 | |
| if: always() | |
| with: | |
| name: no-mock-audit | |
| path: test-results/no_mock_ci_audit.md | |
| retention-days: 30 | |
| # Rust linting and formatting | |
| lint: | |
| name: Lint | |
| needs: [no-mock-audit] | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 20 | |
| steps: | |
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | |
| - name: Clone sibling dependencies | |
| shell: bash | |
| run: | | |
| git clone --depth 1 https://github.com/Dicklesworthstone/asupersync.git ../asupersync | |
| git clone --depth 1 https://github.com/Dicklesworthstone/frankensqlite.git ../frankensqlite | |
| git clone --depth 1 https://github.com/Dicklesworthstone/franken_agent_detection.git ../franken_agent_detection | |
| git clone --depth 1 https://github.com/Dicklesworthstone/frankensearch.git ../frankensearch | |
| - name: Install Rust nightly | |
| uses: dtolnay/rust-toolchain@881ba7bf39a41cda34ac9e123fb41b44ed08232f # nightly | |
| with: | |
| components: rustfmt, clippy | |
| - name: Cache cargo | |
| uses: Swatinem/rust-cache@ad397744b0d591a723ab90405b7247fac0e6b8db # v2 | |
| - name: Check formatting | |
| run: cargo fmt --all -- --check | |
| - name: Run clippy | |
| # Explicit feature list excludes `strict-path-dep-validation`, which is | |
| # intentionally opt-in and requires specific sibling-repo git revisions | |
| # (see build.rs CONTRACTS). `--all-features` would activate it and make | |
| # the build fail because CI clones siblings at HEAD, not pinned revs. | |
| run: cargo clippy --all-targets --features "qr encryption backtrace" -- -D warnings | |
| # Rust unit tests | |
| test-rust: | |
| name: Rust Tests (${{ matrix.os }}) | |
| runs-on: ${{ matrix.os }} | |
| timeout-minutes: 45 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-latest, macos-latest, windows-latest] | |
| steps: | |
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | |
| - name: Clone sibling dependencies | |
| shell: bash | |
| run: | | |
| git clone --depth 1 https://github.com/Dicklesworthstone/asupersync.git ../asupersync | |
| git clone --depth 1 https://github.com/Dicklesworthstone/frankensqlite.git ../frankensqlite | |
| git clone --depth 1 https://github.com/Dicklesworthstone/franken_agent_detection.git ../franken_agent_detection | |
| git clone --depth 1 https://github.com/Dicklesworthstone/frankensearch.git ../frankensearch | |
| - name: Install Rust nightly | |
| uses: dtolnay/rust-toolchain@881ba7bf39a41cda34ac9e123fb41b44ed08232f # nightly | |
| - name: Cache cargo | |
| uses: Swatinem/rust-cache@ad397744b0d591a723ab90405b7247fac0e6b8db # v2 | |
| - name: Run tests | |
| # Explicit feature list excludes `strict-path-dep-validation` — see the | |
| # Lint job for rationale. | |
| run: cargo test --features "qr encryption backtrace" --verbose -- --nocapture | |
| env: | |
| RUST_LOG: debug | |
| - name: Run doc tests | |
| run: cargo test --doc | |
| - name: Run Rust E2E tests with JSONL logging | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| mapfile -t tests < <(git ls-files 'tests/e2e_*.rs' | sed 's#^tests/##; s#\\.rs$##') | |
| if [[ "${#tests[@]}" -eq 0 ]]; then | |
| echo "No e2e_* tests found; skipping." | |
| exit 0 | |
| fi | |
| args=() | |
| for t in "${tests[@]}"; do | |
| args+=(--test "$t") | |
| done | |
| # Explicit feature list excludes `strict-path-dep-validation` — see | |
| # the Lint job for rationale. | |
| E2E_LOG=1 cargo test --features "qr encryption backtrace" --verbose "${args[@]}" -- --nocapture | |
| - name: Validate E2E JSONL logs | |
| if: always() | |
| shell: bash | |
| run: | | |
| if [[ -d "test-results/e2e" ]] && ls test-results/e2e/*.jsonl 1>/dev/null 2>&1; then | |
| ./scripts/validate-e2e-jsonl.sh test-results/e2e/*.jsonl | |
| else | |
| echo "No E2E JSONL logs found to validate" | |
| fi | |
| - name: Upload E2E JSONL logs | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 | |
| if: always() | |
| with: | |
| name: e2e-jsonl-${{ matrix.os }} | |
| path: test-results/e2e/*.jsonl | |
| if-no-files-found: ignore | |
| retention-days: 14 | |
| ssh-sync-docker: | |
| name: SSH Sync Docker Tests | |
| needs: [no-mock-audit] | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 60 | |
| steps: | |
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | |
| - name: Clone sibling dependencies | |
| shell: bash | |
| run: | | |
| git clone --depth 1 https://github.com/Dicklesworthstone/asupersync.git ../asupersync | |
| git clone --depth 1 https://github.com/Dicklesworthstone/frankensqlite.git ../frankensqlite | |
| git clone --depth 1 https://github.com/Dicklesworthstone/franken_agent_detection.git ../franken_agent_detection | |
| git clone --depth 1 https://github.com/Dicklesworthstone/frankensearch.git ../frankensearch | |
| - name: Install SSH sync tools | |
| shell: bash | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y openssh-client rsync | |
| docker version | |
| docker info | |
| - name: Install Rust nightly | |
| uses: dtolnay/rust-toolchain@881ba7bf39a41cda34ac9e123fb41b44ed08232f # nightly | |
| - name: Cache cargo | |
| uses: Swatinem/rust-cache@ad397744b0d591a723ab90405b7247fac0e6b8db # v2 | |
| - name: Run SSH sync integration tests | |
| run: cargo test --features "qr encryption backtrace" --test ssh_sync_integration -- --ignored --test-threads=1 --nocapture | |
| env: | |
| RUST_LOG: debug | |
| - name: Run SSH sources E2E tests | |
| run: E2E_LOG=1 cargo test --features "qr encryption backtrace" --test e2e_ssh_sources -- --ignored --test-threads=1 --nocapture | |
| env: | |
| RUST_LOG: debug | |
| - name: Validate SSH E2E JSONL logs | |
| if: always() | |
| shell: bash | |
| run: | | |
| if [[ -d "test-results/e2e" ]] && ls test-results/e2e/*.jsonl 1>/dev/null 2>&1; then | |
| ./scripts/validate-e2e-jsonl.sh test-results/e2e/*.jsonl | |
| else | |
| echo "No SSH E2E JSONL logs found to validate" | |
| fi | |
| - name: Upload SSH E2E logs | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 | |
| if: always() | |
| with: | |
| name: ssh-e2e-jsonl | |
| path: test-results/e2e/*.jsonl | |
| if-no-files-found: ignore | |
| retention-days: 14 | |
| e2e-orchestrated: | |
| name: E2E Orchestrator (Rust + Shell) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| steps: | |
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | |
| - name: Clone sibling dependencies | |
| shell: bash | |
| run: | | |
| git clone --depth 1 https://github.com/Dicklesworthstone/asupersync.git ../asupersync | |
| git clone --depth 1 https://github.com/Dicklesworthstone/frankensqlite.git ../frankensqlite | |
| git clone --depth 1 https://github.com/Dicklesworthstone/franken_agent_detection.git ../franken_agent_detection | |
| git clone --depth 1 https://github.com/Dicklesworthstone/frankensearch.git ../frankensearch | |
| - name: Install Rust nightly | |
| uses: dtolnay/rust-toolchain@881ba7bf39a41cda34ac9e123fb41b44ed08232f # nightly | |
| - name: Cache cargo | |
| uses: Swatinem/rust-cache@ad397744b0d591a723ab90405b7247fac0e6b8db # v2 | |
| - name: Run orchestrated E2E runner (Rust + Shell) | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| RUN_PLAYWRIGHT=0 E2E_LOG=1 ./scripts/tests/run_all.sh | |
| - name: Validate E2E JSONL logs | |
| if: always() | |
| shell: bash | |
| run: | | |
| if [[ -d "test-results/e2e" ]] && ls test-results/e2e/*.jsonl 1>/dev/null 2>&1; then | |
| ./scripts/validate-e2e-jsonl.sh test-results/e2e/*.jsonl | |
| else | |
| echo "No E2E JSONL logs found to validate" | |
| fi | |
| - name: Show E2E summary | |
| if: always() | |
| shell: bash | |
| run: | | |
| if [[ -f "test-results/e2e/summary.md" ]]; then | |
| cat test-results/e2e/summary.md | |
| else | |
| echo "No summary.md found" | |
| fi | |
| - name: Upload orchestrated E2E logs | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 | |
| if: always() | |
| with: | |
| name: e2e-orchestrated-logs | |
| path: | | |
| test-results/e2e/combined.jsonl | |
| test-results/e2e/summary.md | |
| test-results/e2e/*.jsonl | |
| if-no-files-found: ignore | |
| retention-days: 14 | |
| # TUI E2E matrix: themes × degradation × breakpoints (2dccg.11.4) | |
| e2e-tui-matrix: | |
| name: TUI E2E Matrix | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| steps: | |
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | |
| - name: Clone sibling dependencies | |
| shell: bash | |
| run: | | |
| git clone --depth 1 https://github.com/Dicklesworthstone/asupersync.git ../asupersync | |
| git clone --depth 1 https://github.com/Dicklesworthstone/frankensqlite.git ../frankensqlite | |
| git clone --depth 1 https://github.com/Dicklesworthstone/franken_agent_detection.git ../franken_agent_detection | |
| git clone --depth 1 https://github.com/Dicklesworthstone/frankensearch.git ../frankensearch | |
| - name: Install Rust nightly | |
| uses: dtolnay/rust-toolchain@881ba7bf39a41cda34ac9e123fb41b44ed08232f # nightly | |
| - name: Cache cargo | |
| uses: Swatinem/rust-cache@ad397744b0d591a723ab90405b7247fac0e6b8db # v2 | |
| - name: Run TUI stress and E2E scenario tests | |
| run: | | |
| set -euo pipefail | |
| cargo test --lib 'stress_' -- --nocapture 2>&1 | tee test-results-stress.txt | |
| cargo test --lib 'e2e_scenario' -- --nocapture 2>&1 | tee test-results-e2e.txt | |
| cargo test --lib 'cross_theme_degradation' -- --nocapture 2>&1 | tee test-results-matrix.txt | |
| cargo test --lib 'rendering_token_affordance' -- --nocapture 2>&1 | tee test-results-affordance.txt | |
| cargo test --lib 'density_' -- --nocapture 2>&1 | tee test-results-density.txt | |
| env: | |
| RUST_BACKTRACE: 1 | |
| - name: Generate TUI matrix summary | |
| if: always() | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| mkdir -p test-results/tui-matrix | |
| { | |
| echo "# TUI E2E Matrix Summary" | |
| echo "" | |
| echo "**Generated:** $(date -u +%Y-%m-%dT%H:%M:%SZ)" | |
| echo "**Commit:** ${{ github.sha }}" | |
| echo "" | |
| echo "## Test Categories" | |
| echo "" | |
| for f in test-results-*.txt; do | |
| category=$(echo "$f" | sed 's/test-results-//; s/\.txt//') | |
| passed=$(grep -c "^test .* ok$" "$f" 2>/dev/null || echo 0) | |
| failed=$(grep -c "^test .* FAILED$" "$f" 2>/dev/null || echo 0) | |
| echo "- **${category}**: ${passed} passed, ${failed} failed" | |
| done | |
| } > test-results/tui-matrix/summary.md | |
| cat test-results/tui-matrix/summary.md | |
| - name: Upload TUI matrix artifacts | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 | |
| if: always() | |
| with: | |
| name: tui-e2e-matrix | |
| path: | | |
| test-results-*.txt | |
| test-results/tui-matrix/summary.md | |
| retention-days: 14 | |
| # Crypto test vectors | |
| crypto-vectors: | |
| name: Crypto Test Vectors | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 20 | |
| steps: | |
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | |
| - name: Clone sibling dependencies | |
| shell: bash | |
| run: | | |
| git clone --depth 1 https://github.com/Dicklesworthstone/asupersync.git ../asupersync | |
| git clone --depth 1 https://github.com/Dicklesworthstone/frankensqlite.git ../frankensqlite | |
| git clone --depth 1 https://github.com/Dicklesworthstone/franken_agent_detection.git ../franken_agent_detection | |
| git clone --depth 1 https://github.com/Dicklesworthstone/frankensearch.git ../frankensearch | |
| - name: Install Rust nightly | |
| uses: dtolnay/rust-toolchain@881ba7bf39a41cda34ac9e123fb41b44ed08232f # nightly | |
| - name: Cache cargo | |
| uses: Swatinem/rust-cache@ad397744b0d591a723ab90405b7247fac0e6b8db # v2 | |
| - name: Run crypto vector tests | |
| run: cargo test --test crypto_vectors -- --nocapture | |
| env: | |
| RUST_LOG: debug | |
| # Security audit | |
| security: | |
| name: Security Audit | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| steps: | |
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | |
| - name: Clone sibling dependencies | |
| shell: bash | |
| run: | | |
| git clone --depth 1 https://github.com/Dicklesworthstone/asupersync.git ../asupersync | |
| git clone --depth 1 https://github.com/Dicklesworthstone/frankensqlite.git ../frankensqlite | |
| git clone --depth 1 https://github.com/Dicklesworthstone/franken_agent_detection.git ../franken_agent_detection | |
| git clone --depth 1 https://github.com/Dicklesworthstone/frankensearch.git ../frankensearch | |
| - name: Install Rust nightly | |
| uses: dtolnay/rust-toolchain@881ba7bf39a41cda34ac9e123fb41b44ed08232f # nightly | |
| - name: Cache cargo | |
| uses: Swatinem/rust-cache@ad397744b0d591a723ab90405b7247fac0e6b8db # v2 | |
| - name: Install cargo-audit | |
| uses: taiki-e/install-action@878643b9fbcb563eeb35c8d9abe2ea9c84cb55bb # cargo-audit | |
| - name: Run cargo audit | |
| run: cargo audit | |
| # Build artifacts (verification only, not for release) | |
| build: | |
| name: Build (${{ matrix.target }}) | |
| needs: [lint, test-rust, ssh-sync-docker, crypto-vectors, security] | |
| runs-on: ${{ matrix.os }} | |
| timeout-minutes: 30 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - os: ubuntu-latest | |
| target: x86_64-unknown-linux-gnu | |
| - os: macos-15-intel | |
| target: x86_64-apple-darwin | |
| - os: macos-14 | |
| target: aarch64-apple-darwin | |
| - os: windows-latest | |
| target: x86_64-pc-windows-msvc | |
| steps: | |
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | |
| - name: Clone sibling dependencies | |
| shell: bash | |
| run: | | |
| git clone --depth 1 https://github.com/Dicklesworthstone/asupersync.git ../asupersync | |
| git clone --depth 1 https://github.com/Dicklesworthstone/frankensqlite.git ../frankensqlite | |
| git clone --depth 1 https://github.com/Dicklesworthstone/franken_agent_detection.git ../franken_agent_detection | |
| git clone --depth 1 https://github.com/Dicklesworthstone/frankensearch.git ../frankensearch | |
| - name: Install Rust nightly | |
| uses: dtolnay/rust-toolchain@881ba7bf39a41cda34ac9e123fb41b44ed08232f # nightly | |
| with: | |
| targets: ${{ matrix.target }} | |
| - name: Cache cargo | |
| uses: Swatinem/rust-cache@ad397744b0d591a723ab90405b7247fac0e6b8db # v2 | |
| - name: Build release | |
| run: cargo build --release --target ${{ matrix.target }} | |
| - name: Upload artifact | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 | |
| with: | |
| name: cass-${{ matrix.target }} | |
| path: target/${{ matrix.target }}/release/cass* | |
| e2e-log-summary: | |
| name: E2E Log Summary | |
| needs: [test-rust] | |
| runs-on: ubuntu-latest | |
| if: always() | |
| timeout-minutes: 10 | |
| permissions: | |
| contents: read | |
| pull-requests: write | |
| steps: | |
| - name: Download E2E log artifacts | |
| uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4 | |
| continue-on-error: true | |
| with: | |
| pattern: 'e2e-jsonl-*' | |
| path: artifacts | |
| merge-multiple: true | |
| - name: Aggregate E2E JSONL logs | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| mkdir -p artifacts test-results/e2e | |
| find artifacts -type f -name "*.jsonl" -print0 | sort -z | xargs -0 cat > test-results/e2e/combined.jsonl || true | |
| - name: Generate E2E summary report | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| python3 - <<'PY' | |
| import json | |
| from datetime import datetime, timezone | |
| from pathlib import Path | |
| combined_path = Path("test-results/e2e/combined.jsonl") | |
| summary_path = Path("test-results/e2e/summary.md") | |
| summary_path.parent.mkdir(parents=True, exist_ok=True) | |
| total = passed = failed = skipped = flaky = 0 | |
| durations = {} | |
| failures = [] | |
| if combined_path.exists(): | |
| for line in combined_path.read_text(encoding="utf-8").splitlines(): | |
| line = line.strip() | |
| if not line: | |
| continue | |
| try: | |
| event = json.loads(line) | |
| except json.JSONDecodeError: | |
| continue | |
| if event.get("event") != "test_end": | |
| continue | |
| runner = event.get("runner", "unknown") | |
| result = event.get("result", {}) | |
| status = result.get("status", "unknown") | |
| duration_ms = result.get("duration_ms", 0) | |
| durations[runner] = durations.get(runner, 0) + int(duration_ms or 0) | |
| total += 1 | |
| if status == "pass": | |
| passed += 1 | |
| elif status == "skip": | |
| skipped += 1 | |
| else: | |
| failed += 1 | |
| retries = result.get("retries") | |
| if status == "pass" and retries and int(retries) > 0: | |
| flaky += 1 | |
| if status == "fail": | |
| test = event.get("test", {}) | |
| error = event.get("error", {}) | |
| failures.append({ | |
| "runner": runner, | |
| "suite": test.get("suite", "unknown"), | |
| "name": test.get("name", "unknown"), | |
| "file": test.get("file"), | |
| "line": test.get("line"), | |
| "message": error.get("message", "unknown error"), | |
| }) | |
| now = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S UTC") | |
| lines = [ | |
| "# E2E Log Summary (CI)", | |
| "", | |
| f"**Generated:** {now}", | |
| f"**Combined Log:** {combined_path.as_posix()}", | |
| "", | |
| "## Totals", | |
| "", | |
| f"- **Total Tests:** {total}", | |
| f"- **Passed:** {passed}", | |
| f"- **Failed:** {failed}", | |
| f"- **Skipped:** {skipped}", | |
| f"- **Flaky (passed on retry):** {flaky}", | |
| "", | |
| "## Duration by Runner", | |
| "", | |
| "| Runner | Duration (ms) |", | |
| "|--------|---------------|", | |
| ] | |
| if durations: | |
| for runner, duration in sorted(durations.items()): | |
| lines.append(f"| {runner} | {duration} |") | |
| else: | |
| lines.append("| (none) | 0 |") | |
| lines.append("") | |
| lines.append("## Failed Tests") | |
| lines.append("") | |
| if failures: | |
| for f in failures: | |
| location = "" | |
| if f.get("file"): | |
| if f.get("line"): | |
| location = f"{f['file']}:{f['line']}" | |
| else: | |
| location = f"{f['file']}" | |
| detail = f"{f['runner']} :: {f['suite']} :: {f['name']}" | |
| if location: | |
| detail += f" ({location})" | |
| detail += f" — {f['message']}" | |
| lines.append(f"- {detail}") | |
| else: | |
| lines.append("- None") | |
| summary_path.write_text("\n".join(lines) + "\n", encoding="utf-8") | |
| print(f"Wrote {summary_path}") | |
| PY | |
| - name: Upload aggregated E2E logs | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 | |
| if: always() | |
| with: | |
| name: e2e-log-summary | |
| path: | | |
| test-results/e2e/combined.jsonl | |
| test-results/e2e/summary.md | |
| retention-days: 14 | |
| if-no-files-found: ignore | |
| - name: Comment summary on PR | |
| if: github.event_name == 'pull_request' | |
| uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7 | |
| with: | |
| script: | | |
| const fs = require('fs'); | |
| const marker = '<!-- cass-e2e-summary -->'; | |
| const rustStart = '<!-- cass-e2e-rust:start -->'; | |
| const rustEnd = '<!-- cass-e2e-rust:end -->'; | |
| let summary = fs.readFileSync('test-results/e2e/summary.md', 'utf8'); | |
| if (summary.startsWith('# ')) { | |
| summary = summary.replace(/^#\s+.*$/m, '## Rust E2E Summary'); | |
| } else if (!summary.startsWith('## ')) { | |
| summary = `## Rust E2E Summary\n\n${summary}`; | |
| } | |
| const rustSection = `${rustStart}\n${summary.trim()}\n${rustEnd}`; | |
| const { owner, repo } = context.repo; | |
| const issue_number = context.issue.number; | |
| const { data: comments } = await github.rest.issues.listComments({ | |
| owner, | |
| repo, | |
| issue_number, | |
| per_page: 100, | |
| }); | |
| const existing = comments.find(comment => comment.body.includes(marker)); | |
| const upsertSection = (body, section) => { | |
| if (body.includes(rustStart) && body.includes(rustEnd)) { | |
| const regex = new RegExp(`${rustStart}[\\s\\S]*?${rustEnd}`, 'm'); | |
| return body.replace(regex, section); | |
| } | |
| return `${body.trim()}\n\n${section}`; | |
| }; | |
| let body = existing ? existing.body : marker; | |
| if (!body.includes(marker)) { | |
| body = `${marker}\n${body}`; | |
| } | |
| body = upsertSection(body, rustSection); | |
| if (existing) { | |
| await github.rest.issues.updateComment({ | |
| owner, | |
| repo, | |
| comment_id: existing.id, | |
| body, | |
| }); | |
| } else { | |
| await github.rest.issues.createComment({ | |
| owner, | |
| repo, | |
| issue_number, | |
| body, | |
| }); | |
| } |