Skip to content

Vectors and Validation #34

Vectors and Validation

Vectors and Validation #34

name: Vectors and Validation
on:
push:
branches:
- main
pull_request:
branches:
- main
workflow_dispatch:
schedule:
- cron: '17 3 * * *'
env:
VECTOR_ARTIFACT_NAME: vectors-${{ github.run_number }}-${{ github.sha }}
REPORT_ARTIFACT_PREFIX: validation-reports-${{ github.run_number }}-${{ github.sha }}
LIBI2PD_COMMIT: 3b06529f97b273f2c130ae987a208f35c2031973
permissions:
contents: read
jobs:
# Generate vectors once from pinned Java I2P, then fan out to validators.
generate-vectors:
runs-on: ubuntu-latest
outputs:
reference_version: ${{ steps.vector-metadata.outputs.reference_version }}
vector_artifact_name: ${{ steps.artifact-name.outputs.value }}
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Set up JDK for vector generator
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '21'
# Cache key includes shell scripts + Java source + run OS so reference jars/classes are reusable and deterministic.
- name: Cache Java reference jars and compiled classes
uses: actions/cache@v4
with:
path: |
.cache/reference
.cache/build
key: ${{ runner.os }}-vectors-java-${{ hashFiles('scripts/fetch-reference.sh', 'scripts/generate-vectors.sh', 'src/VectorGenerator.java') }}
- name: Generate vectors
run: |
set -euo pipefail
mkdir -p "$RUNNER_TEMP/vectors"
./scripts/generate-vectors.sh "$RUNNER_TEMP/vectors"
- name: Capture vector metadata
id: vector-metadata
run: |
python3 - <<'PY'
import json
import os
from pathlib import Path
runner_temp = Path(os.environ["RUNNER_TEMP"])
p = runner_temp / "vectors" / "crypto.json"
data = json.loads(p.read_text(encoding="utf-8"))
metadata = data.get("metadata", {})
version = metadata.get("reference_version", "unknown")
impl = metadata.get("reference_implementation", "unknown")
artifacts = metadata.get("reference_artifacts", [])
out = runner_temp / "vector-metadata.json"
out.write_text(json.dumps({
"reference_version": version,
"reference_implementation": impl,
"reference_artifacts": artifacts,
}, indent=2) + "\n", encoding="utf-8")
with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as fh:
fh.write(f"reference_version={version}\n")
PY
- name: Emit vector artifact name
id: artifact-name
run: echo "value=${VECTOR_ARTIFACT_NAME}" >> "$GITHUB_OUTPUT"
- name: Upload generated vectors artifact
uses: actions/upload-artifact@v4
with:
name: ${{ env.VECTOR_ARTIFACT_NAME }}
path: |
${{ runner.temp }}/vectors
${{ runner.temp }}/vector-metadata.json
if-no-files-found: error
retention-days: 14
# Required gate: pinned libi2pd validation must pass for the run to be green.
validate-libi2pd:
runs-on: ubuntu-latest
needs: generate-vectors
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Download shared vectors artifact
uses: actions/download-artifact@v4
with:
name: ${{ needs.generate-vectors.outputs.vector_artifact_name }}
path: ${{ runner.temp }}/artifacts
- name: Install C++/libi2pd build dependencies
run: |
set -euo pipefail
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
build-essential \
cmake \
pkg-config \
git \
libssl-dev \
zlib1g-dev \
libboost-all-dev \
nlohmann-json3-dev
# Cache key is pinned by libi2pd commit and harness CMake/source files.
- name: Cache libi2pd source/build tree
uses: actions/cache@v4
with:
path: |
${{ runner.temp }}/i2pd-src
${{ runner.temp }}/i2pd-build
key: ${{ runner.os }}-libi2pd-${{ env.LIBI2PD_COMMIT }}-${{ hashFiles('harnesses/libi2pd/CMakeLists.txt', 'harnesses/libi2pd/src/main.cpp') }}
- name: Build pinned libi2pd
run: |
set -euo pipefail
if [ ! -d "$RUNNER_TEMP/i2pd-src/.git" ]; then
git clone https://github.com/PurpleI2P/i2pd.git "$RUNNER_TEMP/i2pd-src"
fi
cd "$RUNNER_TEMP/i2pd-src"
git fetch --all --tags
git checkout "${LIBI2PD_COMMIT}"
cmake -S build -B "$RUNNER_TEMP/i2pd-build" -DCMAKE_BUILD_TYPE=Release
cmake --build "$RUNNER_TEMP/i2pd-build" -j"$(nproc)"
LIB_PATH="$(find "$RUNNER_TEMP/i2pd-build" -maxdepth 3 -type f \( -name 'libi2pd.so' -o -name 'libi2pd.a' \) | head -n1)"
if [ -z "$LIB_PATH" ]; then
echo "Unable to locate built libi2pd library" >&2
exit 1
fi
echo "LIBI2PD_LIBRARY_PATH=$LIB_PATH" >> "$GITHUB_ENV"
- name: Build libi2pd validation harness
run: |
set -euo pipefail
cmake -S harnesses/libi2pd -B harnesses/libi2pd/build-ci \
-DLIBI2PD_ROOT="$RUNNER_TEMP/i2pd-src" \
-DLIBI2PD_LIBRARY_HINT="$LIBI2PD_LIBRARY_PATH" \
-DNLOHMANN_JSON_INCLUDE_DIR=/usr/include
cmake --build harnesses/libi2pd/build-ci -j"$(nproc)"
- name: Run libi2pd validation
run: |
set -euo pipefail
mkdir -p "$RUNNER_TEMP/reports"
harnesses/libi2pd/build-ci/libi2pd_vector_harness \
--vectors "$RUNNER_TEMP/artifacts/vectors" \
--report "$RUNNER_TEMP/reports/libi2pd-report.json" \
--libi2pd-commit "${LIBI2PD_COMMIT}"
- name: Upload libi2pd report artifact
uses: actions/upload-artifact@v4
with:
name: ${{ env.REPORT_ARTIFACT_PREFIX }}-libi2pd
path: ${{ runner.temp }}/reports/libi2pd-report.json
if-no-files-found: error
retention-days: 14
# Non-blocking by policy: FAIL is reported, but this in-development implementation does not gate green/red.
validate-go-i2p:
runs-on: ubuntu-latest
needs: generate-vectors
continue-on-error: true
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Download shared vectors artifact
uses: actions/download-artifact@v4
with:
name: ${{ needs.generate-vectors.outputs.vector_artifact_name }}
path: ${{ runner.temp }}/artifacts
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: harnesses/go-i2p/go.mod
cache-dependency-path: harnesses/go-i2p/go.sum
# setup-go caches GOMODCACHE and build cache keyed by go.sum + toolchain.
- name: Refresh go-i2p to tip of main and tidy
working-directory: harnesses/go-i2p
run: |
set -euo pipefail
go get -u github.com/go-i2p/go-i2p@main
go mod tidy
- name: Build and test go-i2p harness
working-directory: harnesses/go-i2p
run: |
set -euo pipefail
go test ./...
- name: Run go-i2p validation
working-directory: harnesses/go-i2p
run: |
set -euo pipefail
mkdir -p "$RUNNER_TEMP/reports"
go run . \
--vectors "$RUNNER_TEMP/artifacts/vectors" \
--report "$RUNNER_TEMP/reports/go-i2p-report.json"
- name: Upload go-i2p report artifact
if: always()
uses: actions/upload-artifact@v4
with:
name: ${{ env.REPORT_ARTIFACT_PREFIX }}-go-i2p
path: ${{ runner.temp }}/reports/go-i2p-report.json
if-no-files-found: warn
retention-days: 14
# Non-blocking by policy: FAIL is reported, but this in-development implementation does not gate green/red.
validate-emissary:
runs-on: ubuntu-latest
needs: generate-vectors
continue-on-error: true
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Download shared vectors artifact
uses: actions/download-artifact@v4
with:
name: ${{ needs.generate-vectors.outputs.vector_artifact_name }}
path: ${{ runner.temp }}/artifacts
- name: Set up Rust stable toolchain
uses: dtolnay/rust-toolchain@stable
# Cache key includes Cargo.lock and Rust target to reuse crates, git checkouts, and target/ objects.
- name: Cache Cargo registry/git/target
uses: Swatinem/rust-cache@v2
with:
workspaces: harnesses/emissary -> target
key: ${{ runner.os }}-emissary-${{ hashFiles('harnesses/emissary/Cargo.lock', 'harnesses/emissary/Cargo.toml') }}
- name: Point emissary dependency at tip branch
working-directory: harnesses/emissary
run: |
set -euo pipefail
BRANCH="main"
if ! git ls-remote --exit-code --heads https://github.com/eepnet/emissary.git main >/dev/null 2>&1; then
BRANCH="master"
fi
sed -i -E "s/branch = \"[^\"]+\"/branch = \"${BRANCH}\"/" Cargo.toml
echo "Using emissary branch: ${BRANCH}"
- name: Refresh emissary and build harness
working-directory: harnesses/emissary
run: |
set -euo pipefail
cargo update -p emissary-core
cargo build --release
- name: Run emissary validation
working-directory: harnesses/emissary
run: |
set -euo pipefail
mkdir -p "$RUNNER_TEMP/reports"
cargo run --release -- \
--vectors "$RUNNER_TEMP/artifacts/vectors" \
--report "$RUNNER_TEMP/reports/emissary-report.json"
- name: Upload emissary report artifact
if: always()
uses: actions/upload-artifact@v4
with:
name: ${{ env.REPORT_ARTIFACT_PREFIX }}-emissary
path: ${{ runner.temp }}/reports/emissary-report.json
if-no-files-found: warn
retention-days: 14
report:
runs-on: ubuntu-latest
needs:
- generate-vectors
- validate-libi2pd
- validate-go-i2p
- validate-emissary
if: always()
permissions:
contents: read
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Download vector artifact
uses: actions/download-artifact@v4
with:
name: ${{ needs.generate-vectors.outputs.vector_artifact_name }}
path: ${{ runner.temp }}/artifacts
- name: Download validator report artifacts
uses: actions/download-artifact@v4
with:
pattern: ${{ env.REPORT_ARTIFACT_PREFIX }}-*
path: ${{ runner.temp }}/reports-artifacts
merge-multiple: true
- name: Generate consolidated markdown, HTML, and summary table
run: |
set -euo pipefail
mkdir -p "$RUNNER_TEMP/site"
python3 validation/scripts/consolidate_report.py \
--vectors-metadata "$RUNNER_TEMP/artifacts/vector-metadata.json" \
--libi2pd "$RUNNER_TEMP/reports-artifacts/libi2pd-report.json" \
--go-i2p "$RUNNER_TEMP/reports-artifacts/go-i2p-report.json" \
--emissary "$RUNNER_TEMP/reports-artifacts/emissary-report.json" \
--run-number "${GITHUB_RUN_NUMBER}" \
--run-id "${GITHUB_RUN_ID}" \
--run-url "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" \
--timestamp "$(date -u +'%Y-%m-%dT%H:%M:%SZ')" \
--output-markdown "$RUNNER_TEMP/site/index.md" \
--output-html "$RUNNER_TEMP/site/index.html" \
--output-summary "$RUNNER_TEMP/site/step-summary.md"
- name: Publish summary to run page
run: cat "$RUNNER_TEMP/site/step-summary.md" >> "$GITHUB_STEP_SUMMARY"
- name: Upload consolidated report artifact
uses: actions/upload-artifact@v4
with:
name: ${{ env.REPORT_ARTIFACT_PREFIX }}-consolidated
path: |
${{ runner.temp }}/site/index.md
${{ runner.temp }}/site/index.html
${{ runner.temp }}/site/step-summary.md
if-no-files-found: error
retention-days: 30
- name: Prepare GitHub Pages
if: |
github.event_name == 'schedule' ||
(github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && github.event_name != 'pull_request')
uses: actions/configure-pages@v5
- name: Upload Pages artifact
if: |
github.event_name == 'schedule' ||
(github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && github.event_name != 'pull_request')
uses: actions/upload-pages-artifact@v3
with:
path: ${{ runner.temp }}/site
- name: Deploy to GitHub Pages
if: |
github.event_name == 'schedule' ||
(github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && github.event_name != 'pull_request')
id: deployment
uses: actions/deploy-pages@v4
# Final required gate: only generation + pinned libi2pd are mandatory pass conditions.
final-gate:
runs-on: ubuntu-latest
needs:
- generate-vectors
- validate-libi2pd
- report
if: always()
steps:
- name: Enforce required gate outcomes
run: |
set -euo pipefail
if [ "${{ needs.generate-vectors.result }}" != "success" ]; then
echo "generate-vectors is required and did not succeed"
exit 1
fi
if [ "${{ needs.validate-libi2pd.result }}" != "success" ]; then
echo "validate-libi2pd is required and did not succeed"
exit 1
fi
if [ "${{ needs.report.result }}" != "success" ]; then
echo "report job failed"
exit 1
fi
echo "Required gate checks passed."