Skip to content

feat(terraphim): add project-local .terraphim KG + thesaurus for terr… #5

feat(terraphim): add project-local .terraphim KG + thesaurus for terr…

feat(terraphim): add project-local .terraphim KG + thesaurus for terr… #5

Workflow file for this run

name: Fuzz CI
on:
push:
branches: [main]
pull_request:
branches: [main]
schedule:
- cron: "0 2 * * *"
workflow_dispatch:
inputs:
profile:
description: "Fuzz profile to run"
required: false
default: "quick"
type: choice
options:
- quick
- nightly
- all
permissions:
contents: read
env:
CARGO_TERM_COLOR: always
RUST_BACKTRACE: "1"
jobs:
fuzz-quick:
name: "fuzz-quick (${{ matrix.target }})"
if: >-
github.event_name == 'push' ||
github.event_name == 'pull_request' ||
(github.event_name == 'workflow_dispatch' &&
(inputs.profile == 'quick' || inputs.profile == 'all'))
runs-on: ubuntu-latest
timeout-minutes: 45
strategy:
fail-fast: false
max-parallel: 4
matrix:
target:
- fuzz_sse_parser
- fuzz_sse_stream
- fuzz_session_jsonl
- fuzz_session_entry
- fuzz_provider_event
- fuzz_message_deser
- fuzz_message_roundtrip
- fuzz_config
- fuzz_config_load
- fuzz_tool_paths
- fuzz_grep_pattern
- fuzz_edit_match
- fuzz_extension_payload
defaults:
run:
working-directory: pi_agent_rust
shell: bash
steps:
- name: Checkout pi_agent_rust
uses: actions/checkout@v4
with:
path: pi_agent_rust
- name: Install Rust toolchain (nightly)
uses: dtolnay/rust-toolchain@nightly
with:
targets: x86_64-unknown-linux-gnu
- name: Install cargo-fuzz
uses: taiki-e/install-action@v2
with:
tool: cargo-fuzz
- name: Cache cargo artifacts
uses: actions/cache@v4
with:
path: |
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
pi_agent_rust/fuzz/target/
key: fuzz-quick-${{ runner.os }}-${{ matrix.target }}-${{ hashFiles('pi_agent_rust/fuzz/Cargo.toml', 'pi_agent_rust/fuzz/Cargo.lock') }}
restore-keys: |
fuzz-quick-${{ runner.os }}-${{ matrix.target }}-
fuzz-quick-${{ runner.os }}-
- name: Run quick fuzz target
env:
FUZZ_PROFILE: quick
FUZZ_TARGET: ${{ matrix.target }}
FUZZ_SECONDS: "60"
run: |
set -euo pipefail
cd fuzz
mkdir -p "corpus/$FUZZ_TARGET" "artifacts/$FUZZ_TARGET" reports
start_ts="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
start_ns="$(date +%s%N)"
set +e
# Force gnu target: cargo-binstall ships cargo-fuzz as a musl binary,
# so its default host triple is x86_64-unknown-linux-musl. ASan is
# incompatible with statically-linked libc, so we must build for the
# dynamically-linked gnu target instead.
cargo fuzz run --target x86_64-unknown-linux-gnu "$FUZZ_TARGET" -- \
-max_total_time="$FUZZ_SECONDS" \
-artifact_prefix="artifacts/$FUZZ_TARGET/" \
"corpus/$FUZZ_TARGET" \
2>&1 | tee "reports/${FUZZ_TARGET}_${FUZZ_PROFILE}.log"
exit_code=${PIPESTATUS[0]}
set -e
end_ts="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
end_ns="$(date +%s%N)"
duration_ms=$(( (end_ns - start_ns) / 1000000 ))
corpus_entries="$(find "corpus/$FUZZ_TARGET" -maxdepth 1 -type f 2>/dev/null | wc -l | tr -d ' ')"
artifact_entries="$(find "artifacts/$FUZZ_TARGET" -maxdepth 1 -type f 2>/dev/null | wc -l | tr -d ' ')"
status="pass"
if [ "$exit_code" -ne 0 ]; then
status="fail"
fi
cat > "reports/${FUZZ_TARGET}_${FUZZ_PROFILE}_summary.json" <<EOF
{
"schema": "pi.fuzz.run.v1",
"profile": "$FUZZ_PROFILE",
"target": "$FUZZ_TARGET",
"max_total_time_s": $FUZZ_SECONDS,
"status": "$status",
"exit_code": $exit_code,
"start_ts": "$start_ts",
"end_ts": "$end_ts",
"duration_ms": $duration_ms,
"corpus_entries": $corpus_entries,
"artifact_entries": $artifact_entries
}
EOF
if [ "$exit_code" -ne 0 ]; then
exit "$exit_code"
fi
- name: Upload quick fuzz reports
if: always()
uses: actions/upload-artifact@v4
with:
name: fuzz-quick-${{ matrix.target }}-${{ github.run_id }}
path: |
pi_agent_rust/fuzz/reports/${{ matrix.target }}_quick.log
pi_agent_rust/fuzz/reports/${{ matrix.target }}_quick_summary.json
if-no-files-found: warn
retention-days: 14
- name: Upload crash artifacts (quick)
if: failure()
uses: actions/upload-artifact@v4
with:
name: fuzz-crash-quick-${{ matrix.target }}-${{ github.run_id }}
path: pi_agent_rust/fuzz/artifacts/${{ matrix.target }}/
if-no-files-found: ignore
retention-days: 30
fuzz-nightly:
name: "fuzz-nightly (${{ matrix.target }})"
if: >-
github.event_name == 'schedule' ||
(github.event_name == 'workflow_dispatch' &&
(inputs.profile == 'nightly' || inputs.profile == 'all'))
runs-on: ubuntu-latest
timeout-minutes: 120
strategy:
fail-fast: false
max-parallel: 2
matrix:
target:
- fuzz_sse_parser
- fuzz_provider_event
- fuzz_session_jsonl
- fuzz_message_deser
defaults:
run:
working-directory: pi_agent_rust
shell: bash
steps:
- name: Checkout pi_agent_rust
uses: actions/checkout@v4
with:
path: pi_agent_rust
- name: Install Rust toolchain (nightly)
uses: dtolnay/rust-toolchain@nightly
with:
targets: x86_64-unknown-linux-gnu
- name: Install cargo-fuzz
uses: taiki-e/install-action@v2
with:
tool: cargo-fuzz
- name: Cache cargo artifacts
uses: actions/cache@v4
with:
path: |
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
pi_agent_rust/fuzz/target/
key: fuzz-nightly-${{ runner.os }}-${{ matrix.target }}-${{ hashFiles('pi_agent_rust/fuzz/Cargo.toml', 'pi_agent_rust/fuzz/Cargo.lock') }}
restore-keys: |
fuzz-nightly-${{ runner.os }}-${{ matrix.target }}-
fuzz-nightly-${{ runner.os }}-
- name: Restore nightly corpus cache
uses: actions/cache@v4
with:
path: pi_agent_rust/fuzz/corpus/${{ matrix.target }}
key: fuzz-corpus-${{ matrix.target }}-${{ github.run_id }}
restore-keys: |
fuzz-corpus-${{ matrix.target }}-
- name: Run nightly fuzz target
env:
FUZZ_PROFILE: nightly
FUZZ_TARGET: ${{ matrix.target }}
FUZZ_SECONDS: "1800"
run: |
set -euo pipefail
cd fuzz
mkdir -p "corpus/$FUZZ_TARGET" "artifacts/$FUZZ_TARGET" reports
start_ts="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
start_ns="$(date +%s%N)"
set +e
# Force gnu target: cargo-binstall ships cargo-fuzz as a musl binary,
# so its default host triple is x86_64-unknown-linux-musl. ASan is
# incompatible with statically-linked libc, so we must build for the
# dynamically-linked gnu target instead.
cargo fuzz run --target x86_64-unknown-linux-gnu "$FUZZ_TARGET" -- \
-max_total_time="$FUZZ_SECONDS" \
-artifact_prefix="artifacts/$FUZZ_TARGET/" \
"corpus/$FUZZ_TARGET" \
2>&1 | tee "reports/${FUZZ_TARGET}_${FUZZ_PROFILE}.log"
exit_code=${PIPESTATUS[0]}
set -e
end_ts="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
end_ns="$(date +%s%N)"
duration_ms=$(( (end_ns - start_ns) / 1000000 ))
corpus_entries="$(find "corpus/$FUZZ_TARGET" -maxdepth 1 -type f 2>/dev/null | wc -l | tr -d ' ')"
artifact_entries="$(find "artifacts/$FUZZ_TARGET" -maxdepth 1 -type f 2>/dev/null | wc -l | tr -d ' ')"
status="pass"
if [ "$exit_code" -ne 0 ]; then
status="fail"
fi
cat > "reports/${FUZZ_TARGET}_${FUZZ_PROFILE}_summary.json" <<EOF
{
"schema": "pi.fuzz.run.v1",
"profile": "$FUZZ_PROFILE",
"target": "$FUZZ_TARGET",
"max_total_time_s": $FUZZ_SECONDS,
"status": "$status",
"exit_code": $exit_code,
"start_ts": "$start_ts",
"end_ts": "$end_ts",
"duration_ms": $duration_ms,
"corpus_entries": $corpus_entries,
"artifact_entries": $artifact_entries
}
EOF
if [ "$exit_code" -ne 0 ]; then
exit "$exit_code"
fi
- name: Upload nightly fuzz reports
if: always()
uses: actions/upload-artifact@v4
with:
name: fuzz-nightly-${{ matrix.target }}-${{ github.run_id }}
path: |
pi_agent_rust/fuzz/reports/${{ matrix.target }}_nightly.log
pi_agent_rust/fuzz/reports/${{ matrix.target }}_nightly_summary.json
if-no-files-found: warn
retention-days: 30
- name: Upload crash artifacts (nightly)
if: failure()
uses: actions/upload-artifact@v4
with:
name: fuzz-crash-nightly-${{ matrix.target }}-${{ github.run_id }}
path: pi_agent_rust/fuzz/artifacts/${{ matrix.target }}/
if-no-files-found: ignore
retention-days: 30
- name: Upload corpus snapshot (nightly)
if: always()
uses: actions/upload-artifact@v4
with:
name: fuzz-corpus-${{ matrix.target }}-${{ github.run_id }}
path: pi_agent_rust/fuzz/corpus/${{ matrix.target }}/
if-no-files-found: ignore
retention-days: 14
fuzz-coverage-dashboard:
name: fuzz-coverage-dashboard
if: >-
github.event_name == 'schedule' ||
(github.event_name == 'workflow_dispatch' &&
(inputs.profile == 'nightly' || inputs.profile == 'all'))
runs-on: ubuntu-latest
timeout-minutes: 60
defaults:
run:
working-directory: pi_agent_rust
shell: bash
env:
FUZZ_TARGET: fuzz_sse_parser
FUZZ_COVERAGE_SECONDS: "120"
steps:
- name: Checkout pi_agent_rust
uses: actions/checkout@v4
with:
path: pi_agent_rust
- name: Install Rust toolchain (nightly)
uses: dtolnay/rust-toolchain@nightly
with:
components: llvm-tools-preview
targets: x86_64-unknown-linux-gnu
- name: Install cargo-fuzz
uses: taiki-e/install-action@v2
with:
tool: cargo-fuzz
- name: Cache cargo artifacts
uses: actions/cache@v4
with:
path: |
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
pi_agent_rust/fuzz/target/
key: fuzz-coverage-${{ runner.os }}-${{ hashFiles('pi_agent_rust/fuzz/Cargo.toml', 'pi_agent_rust/fuzz/Cargo.lock') }}
restore-keys: |
fuzz-coverage-${{ runner.os }}-
fuzz-nightly-${{ runner.os }}-
- name: Build fuzz coverage dashboard
run: |
set -euo pipefail
cd fuzz
mkdir -p "corpus/${FUZZ_TARGET}" "coverage/dashboard/html" reports
start_ts="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
start_ns="$(date +%s%N)"
set +e
# Force gnu target — see fuzz-quick job comment.
cargo fuzz coverage --target x86_64-unknown-linux-gnu "${FUZZ_TARGET}" -- \
-max_total_time="${FUZZ_COVERAGE_SECONDS}" \
"corpus/${FUZZ_TARGET}" \
2>&1 | tee "reports/${FUZZ_TARGET}_coverage.log"
coverage_exit=${PIPESTATUS[0]}
set -e
end_ts="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
end_ns="$(date +%s%N)"
duration_ms=$(( (end_ns - start_ns) / 1000000 ))
if [ "${coverage_exit}" -ne 0 ]; then
echo "coverage run failed for ${FUZZ_TARGET} (exit=${coverage_exit})" >&2
exit "${coverage_exit}"
fi
host_triple="$(rustc -vV | awk '/host:/ {print $2}')"
llvm_tools_bin="$(rustc --print sysroot)/lib/rustlib/${host_triple}/bin"
llvm_cov="${llvm_tools_bin}/llvm-cov"
llvm_profdata="${llvm_tools_bin}/llvm-profdata"
if [ ! -x "${llvm_cov}" ] || [ ! -x "${llvm_profdata}" ]; then
echo "missing llvm tools in ${llvm_tools_bin}" >&2
exit 1
fi
profdata="coverage/${FUZZ_TARGET}/coverage.profdata"
binary_path="$(find target -type f -path '*coverage*' -name "${FUZZ_TARGET}" | head -n 1 || true)"
if [ -z "${binary_path}" ] || [ ! -f "${profdata}" ]; then
echo "coverage artifacts missing (binary='${binary_path}', profdata='${profdata}')" >&2
exit 1
fi
raw_summary_json="coverage/dashboard/${FUZZ_TARGET}_raw_summary.json"
dashboard_json="coverage/dashboard/${FUZZ_TARGET}_dashboard.json"
dashboard_md="coverage/dashboard/${FUZZ_TARGET}_dashboard.md"
report_txt="coverage/dashboard/${FUZZ_TARGET}_report.txt"
html_dir="coverage/dashboard/html/${FUZZ_TARGET}"
"${llvm_cov}" export \
--format=text \
--summary-only \
--instr-profile="${profdata}" \
"${binary_path}" > "${raw_summary_json}"
"${llvm_cov}" report \
--instr-profile="${profdata}" \
"${binary_path}" > "${report_txt}"
"${llvm_cov}" show \
--format=html \
--output-dir="${html_dir}" \
--instr-profile="${profdata}" \
"${binary_path}"
jq \
--arg schema "pi.fuzz.coverage.dashboard.v1" \
--arg target "${FUZZ_TARGET}" \
--arg started "${start_ts}" \
--arg finished "${end_ts}" \
--argjson duration_ms "${duration_ms}" \
--arg run_id "${GITHUB_RUN_ID}" \
'{
schema: $schema,
target: $target,
run_id: $run_id,
started_at: $started,
finished_at: $finished,
duration_ms: $duration_ms,
totals: {
lines: (.data[0].totals.lines // {}),
branches: (.data[0].totals.branches // {}),
functions: (.data[0].totals.functions // {}),
regions: (.data[0].totals.regions // {})
},
files_lowest_line_coverage: (
(.data[0].files // [])
| map({
file: .filename,
lines: (.summary.lines // {}),
branches: (.summary.branches // {})
})
| sort_by((.lines.percent // 0))
| .[:25]
)
}' "${raw_summary_json}" > "${dashboard_json}"
jq -r '
"## Fuzz Coverage Dashboard",
"",
"Target: `\(.target)`",
"",
"| Metric | Covered | Count | Percent |",
"|---|---:|---:|---:|",
"| Lines | \(.totals.lines.covered // 0) | \(.totals.lines.count // 0) | \((.totals.lines.percent // 0)|tostring)% |",
"| Branches | \(.totals.branches.covered // 0) | \(.totals.branches.count // 0) | \((.totals.branches.percent // 0)|tostring)% |",
"| Functions | \(.totals.functions.covered // 0) | \(.totals.functions.count // 0) | \((.totals.functions.percent // 0)|tostring)% |",
"| Regions | \(.totals.regions.covered // 0) | \(.totals.regions.count // 0) | \((.totals.regions.percent // 0)|tostring)% |",
"",
"### Lowest line coverage files (top 25)",
"",
"| File | Lines % | Branches % |",
"|---|---:|---:|",
(.files_lowest_line_coverage[] |
"| `\(.file)` | \((.lines.percent // 0)|tostring)% | \((.branches.percent // 0)|tostring)% |")
' "${dashboard_json}" > "${dashboard_md}"
cat "${dashboard_md}" >> "${GITHUB_STEP_SUMMARY}"
- name: Upload fuzz coverage dashboard artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: fuzz-coverage-dashboard-${{ env.FUZZ_TARGET }}-${{ github.run_id }}
path: |
pi_agent_rust/fuzz/reports/${{ env.FUZZ_TARGET }}_coverage.log
pi_agent_rust/fuzz/coverage/${{ env.FUZZ_TARGET }}/
pi_agent_rust/fuzz/coverage/dashboard/${{ env.FUZZ_TARGET }}_raw_summary.json
pi_agent_rust/fuzz/coverage/dashboard/${{ env.FUZZ_TARGET }}_dashboard.json
pi_agent_rust/fuzz/coverage/dashboard/${{ env.FUZZ_TARGET }}_dashboard.md
pi_agent_rust/fuzz/coverage/dashboard/${{ env.FUZZ_TARGET }}_report.txt
pi_agent_rust/fuzz/coverage/dashboard/html/${{ env.FUZZ_TARGET }}/
if-no-files-found: warn
retention-days: 30