Vectors and Validation #34
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
| 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." |