Admit observation uncertainty without workspace authority #847
Workflow file for this run
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
| # SPDX-License-Identifier: Apache-2.0 | |
| # © James Ross Ω FLYING•ROBOTS <https://github.com/flyingrobots> | |
| name: det-gates | |
| on: | |
| pull_request: | |
| push: | |
| branches: [main] | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: det-gates-${{ github.head_ref || github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| classify-changes: | |
| name: classify-changes | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| outputs: | |
| max_class: ${{ steps.classify.outputs.max_class }} | |
| run_g1: ${{ steps.classify.outputs.run_g1 }} | |
| run_g2: ${{ steps.classify.outputs.run_g2 }} | |
| run_g3: ${{ steps.classify.outputs.run_g3 }} | |
| run_g4: ${{ steps.classify.outputs.run_g4 }} | |
| run_none: ${{ steps.classify.outputs.run_none }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Detect changed files | |
| id: changed | |
| env: | |
| BASE_REF: ${{ github.base_ref }} | |
| EVENT_NAME: ${{ github.event_name }} | |
| run: | | |
| if [ "$EVENT_NAME" = "pull_request" ]; then | |
| git fetch origin "$BASE_REF" --depth=1 | |
| git diff --name-only "origin/$BASE_REF...HEAD" > changed.txt | |
| else | |
| git diff --name-only HEAD~1..HEAD > changed.txt || true | |
| fi | |
| if [ "$EVENT_NAME" = "push" ] && [ ! -s changed.txt ]; then | |
| echo "Warning: empty changelist on push, treating as full run" >&2 | |
| echo "det-policy.yaml" > changed.txt | |
| fi | |
| echo "Changed files:" | |
| cat changed.txt || true | |
| - name: Convert policy to JSON | |
| run: | | |
| yq -o=json det-policy.yaml > det-policy.json | |
| - name: Classify path impact from det-policy.yaml | |
| id: classify | |
| run: | | |
| node ./scripts/classify_changes.cjs det-policy.json changed.txt >> "$GITHUB_OUTPUT" | |
| determinism-linux: | |
| name: G1 determinism (linux) | |
| needs: classify-changes | |
| if: needs.classify-changes.outputs.run_g1 == 'true' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Setup Rust | |
| uses: dtolnay/rust-toolchain@stable | |
| - name: Run parity tests (linux) | |
| run: | | |
| cargo test -p echo-scene-port test_float_parity_with_js -- --nocapture 2>&1 | tee det-linux.log | |
| grep -q " 0 passed" det-linux.log && echo "FATAL: zero tests matched filter" && exit 1 || true | |
| - name: Trig oracle golden vectors (linux) | |
| run: | | |
| cargo test -p warp-math --test trig_golden_vectors -- trig_oracle_matches_golden_vectors --nocapture 2>&1 | tee trig-linux.log | |
| cargo test -p warp-math --test deterministic_sin_cos_tests -- --nocapture 2>&1 | tee -a trig-linux.log | |
| - name: Run DIND suite (linux) | |
| run: | | |
| node scripts/dind-run-suite.mjs --mode run | tee dind-linux.log | |
| - name: Create digest table | |
| env: | |
| COMMIT_SHA: ${{ github.sha }} | |
| RUN_ID: ${{ github.run_id }} | |
| run: | | |
| mkdir -p artifacts | |
| echo "target,commit,run_id,digest" > artifacts/digest-table.csv | |
| echo "linux,${COMMIT_SHA},${RUN_ID},$(sha256sum dind-report.json | cut -d' ' -f1)" >> artifacts/digest-table.csv | |
| - name: Upload artifacts | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: det-linux-artifacts | |
| path: | | |
| det-linux.log | |
| trig-linux.log | |
| dind-linux.log | |
| dind-report.json | |
| artifacts/digest-table.csv | |
| determinism-macos: | |
| name: G1 determinism (macos) | |
| needs: classify-changes | |
| if: needs.classify-changes.outputs.run_g1 == 'true' | |
| runs-on: macos-latest | |
| timeout-minutes: 15 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Setup Rust | |
| uses: dtolnay/rust-toolchain@stable | |
| - name: Run parity tests (macos) | |
| run: | | |
| cargo test -p echo-scene-port test_float_parity_with_js -- --nocapture 2>&1 | tee det-macos.log | |
| grep -q " 0 passed" det-macos.log && echo "FATAL: zero tests matched filter" && exit 1 || true | |
| - name: Trig oracle golden vectors (macos) | |
| run: | | |
| cargo test -p warp-math --test trig_golden_vectors -- trig_oracle_matches_golden_vectors --nocapture 2>&1 | tee trig-macos.log | |
| cargo test -p warp-math --test deterministic_sin_cos_tests -- --nocapture 2>&1 | tee -a trig-macos.log | |
| - name: Run DIND suite (macos) | |
| run: | | |
| node scripts/dind-run-suite.mjs --mode run | tee dind-macos.log | |
| - name: Create digest table | |
| env: | |
| COMMIT_SHA: ${{ github.sha }} | |
| RUN_ID: ${{ github.run_id }} | |
| run: | | |
| mkdir -p artifacts | |
| echo "target,commit,run_id,digest" > artifacts/digest-table.csv | |
| echo "macos,${COMMIT_SHA},${RUN_ID},$(shasum -a 256 dind-report.json | cut -d' ' -f1)" >> artifacts/digest-table.csv | |
| - name: Upload artifacts | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: det-macos-artifacts | |
| path: | | |
| det-macos.log | |
| trig-macos.log | |
| dind-macos.log | |
| dind-report.json | |
| artifacts/digest-table.csv | |
| static-inspection: | |
| name: Static nondeterminism inspection | |
| needs: classify-changes | |
| if: needs.classify-changes.outputs.run_g1 == 'true' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Install ripgrep | |
| run: command -v rg >/dev/null || (sudo apt-get update && sudo apt-get install -y ripgrep) | |
| - name: Compute DETERMINISM_PATHS from policy | |
| id: det_paths | |
| run: | | |
| PATHS=$(yq -o=json det-policy.yaml | jq -r ' | |
| .crates | to_entries[] | | |
| select(.value.class == "DET_CRITICAL") | | |
| .value.paths[]' | | |
| grep '^crates/' | sed 's|/\*\*$||' | sort -u | tr '\n' ' ') | |
| echo "paths=$PATHS" >> "$GITHUB_OUTPUT" | |
| - name: Run determinism check | |
| id: det_check | |
| env: | |
| DETERMINISM_PATHS: ${{ steps.det_paths.outputs.paths }} | |
| run: | | |
| ./scripts/ban-nondeterminism.sh | tee static-inspection.log | |
| - name: Upload inspection artifacts | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: static-inspection | |
| path: static-inspection.log | |
| decoder-security: | |
| name: G2 decoder security tests | |
| needs: classify-changes | |
| if: needs.classify-changes.outputs.run_g2 == 'true' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Setup Rust | |
| uses: dtolnay/rust-toolchain@stable | |
| - name: Run codec tests | |
| run: | | |
| cargo test -p echo-scene-codec --lib cbor::tests -- --nocapture 2>&1 | tee sec-tests.log | |
| grep -q " 0 passed" sec-tests.log && echo "FATAL: zero tests matched filter" && exit 1 || true | |
| - name: Upload security artifacts | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: sec-artifacts | |
| path: | | |
| sec-tests.log | |
| perf-regression: | |
| name: G3 perf regression (criterion) | |
| needs: classify-changes | |
| if: needs.classify-changes.outputs.run_g3 == 'true' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Setup Rust | |
| uses: dtolnay/rust-toolchain@stable | |
| - name: Run benchmarks | |
| run: | | |
| cargo bench -p warp-benches --bench materialization_hotpath -- --output-format bencher | tee perf.log | |
| - name: Check regression against baseline | |
| run: | | |
| node scripts/check_perf_regression.cjs perf-baseline.json perf.log --threshold 15 | |
| - name: Upload perf artifacts | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: perf-artifacts | |
| path: | | |
| perf.log | |
| perf-report.json | |
| build-repro-candidate: | |
| name: G4 build candidate ${{ matrix.candidate }} | |
| needs: classify-changes | |
| if: needs.classify-changes.outputs.run_g4 == 'true' | |
| runs-on: ubuntu-24.04 | |
| container: | |
| image: docker.io/library/rust@sha256:3914072ca0c3b8aad871db9169a651ccfce30cf58303e5d6f2db16d1d8a7e58f | |
| options: --platform linux/amd64 | |
| timeout-minutes: 20 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| candidate: [1, 2] | |
| steps: | |
| - name: Checkout exact candidate source | |
| uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ github.event.pull_request.head.sha || github.sha }} | |
| - name: Install wasm target | |
| run: rustup target add wasm32-unknown-unknown | |
| - name: Build isolated candidate | |
| env: | |
| CANDIDATE: ${{ matrix.candidate }} | |
| GIT_CONFIG_COUNT: 1 | |
| GIT_CONFIG_KEY_0: safe.directory | |
| GIT_CONFIG_VALUE_0: ${{ github.workspace }} | |
| run: | | |
| repo_root="$(pwd -P)" | |
| export RUSTFLAGS="--remap-path-prefix=${repo_root}=/workspace" | |
| cargo build --release --target wasm32-unknown-unknown -p warp-wasm --features engine | |
| sha256sum target/wasm32-unknown-unknown/release/warp_wasm.wasm > "hash${CANDIDATE}.txt" | |
| cp target/wasm32-unknown-unknown/release/warp_wasm.wasm "build${CANDIDATE}.wasm" | |
| cargo xtask provider-lowerer-component designated-build \ | |
| --target-dir target/provider-lowerer-repro \ | |
| --output target/lowerer.echo-dpo.component.wasm | |
| sha256sum target/lowerer.echo-dpo.component.wasm > "lowerer-hash${CANDIDATE}.txt" | |
| cp target/lowerer.echo-dpo.component.wasm "build${CANDIDATE}.lowerer.component.wasm" | |
| cargo xtask provider-verifier-component designated-build \ | |
| --target-dir target/provider-verifier-repro \ | |
| --output target/verifier.echo-dpo.component.wasm | |
| sha256sum target/verifier.echo-dpo.component.wasm > "verifier-hash${CANDIDATE}.txt" | |
| cp target/verifier.echo-dpo.component.wasm "build${CANDIDATE}.verifier.component.wasm" | |
| - name: Upload isolated candidate | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: build-repro-candidate-${{ matrix.candidate }} | |
| overwrite: true | |
| path: | | |
| hash${{ matrix.candidate }}.txt | |
| build${{ matrix.candidate }}.wasm | |
| lowerer-hash${{ matrix.candidate }}.txt | |
| build${{ matrix.candidate }}.lowerer.component.wasm | |
| verifier-hash${{ matrix.candidate }}.txt | |
| build${{ matrix.candidate }}.verifier.component.wasm | |
| build-repro: | |
| name: G4 independent build comparison | |
| needs: | |
| - classify-changes | |
| - build-repro-candidate | |
| if: needs.classify-changes.outputs.run_g4 == 'true' && needs.build-repro-candidate.result == 'success' | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 20 | |
| steps: | |
| - name: Checkout exact candidate source | |
| uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ github.event.pull_request.head.sha || github.sha }} | |
| - name: Setup Rust for portable promotion | |
| uses: dtolnay/rust-toolchain@1.90.0 | |
| - name: Download candidate 1 | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: build-repro-candidate-1 | |
| path: candidate1 | |
| - name: Download candidate 2 | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: build-repro-candidate-2 | |
| path: candidate2 | |
| - name: Assemble exact evidence names | |
| run: | | |
| cp candidate1/hash1.txt hash1.txt | |
| cp candidate2/hash2.txt hash2.txt | |
| cp candidate1/build1.wasm build1.wasm | |
| cp candidate2/build2.wasm build2.wasm | |
| cp candidate1/lowerer-hash1.txt lowerer-hash1.txt | |
| cp candidate2/lowerer-hash2.txt lowerer-hash2.txt | |
| cp candidate1/build1.lowerer.component.wasm build1.lowerer.component.wasm | |
| cp candidate2/build2.lowerer.component.wasm build2.lowerer.component.wasm | |
| cp candidate1/verifier-hash1.txt verifier-hash1.txt | |
| cp candidate2/verifier-hash2.txt verifier-hash2.txt | |
| cp candidate1/build1.verifier.component.wasm build1.verifier.component.wasm | |
| cp candidate2/build2.verifier.component.wasm build2.verifier.component.wasm | |
| - name: Compare, promote, and check exact bytes | |
| run: | | |
| diff hash1.txt hash2.txt || (echo "Reproducibility failure: Hashes differ!" && exit 1) | |
| diff lowerer-hash1.txt lowerer-hash2.txt || (echo "Lowerer reproducibility failure: Hashes differ!" && exit 1) | |
| cmp build1.wasm build2.wasm | |
| cmp build1.lowerer.component.wasm build2.lowerer.component.wasm | |
| cargo xtask provider-lowerer-component promote \ | |
| --candidate-a build1.lowerer.component.wasm \ | |
| --candidate-b build2.lowerer.component.wasm \ | |
| --output target/lowerer.echo-dpo.promoted.component.wasm \ | |
| --write | |
| cmp build1.lowerer.component.wasm target/lowerer.echo-dpo.promoted.component.wasm | |
| cmp build1.lowerer.component.wasm schemas/edict-provider/components/v1/lowerer.echo-dpo.component.wasm | |
| cmp build2.lowerer.component.wasm schemas/edict-provider/components/v1/lowerer.echo-dpo.component.wasm | |
| diff verifier-hash1.txt verifier-hash2.txt || (echo "Verifier reproducibility failure: Hashes differ!" && exit 1) | |
| cmp build1.verifier.component.wasm build2.verifier.component.wasm | |
| cargo xtask provider-verifier-component promote \ | |
| --candidate-a build1.verifier.component.wasm \ | |
| --candidate-b build2.verifier.component.wasm \ | |
| --output target/verifier.echo-dpo.promoted.component.wasm \ | |
| --write | |
| cmp build1.verifier.component.wasm target/verifier.echo-dpo.promoted.component.wasm | |
| cmp build1.verifier.component.wasm schemas/edict-provider/components/v1/verifier.echo-dpo.component.wasm | |
| cmp build2.verifier.component.wasm schemas/edict-provider/components/v1/verifier.echo-dpo.component.wasm | |
| echo "Hashes match: $(cat hash1.txt)" | |
| echo "Lowerer hashes match: $(cat lowerer-hash1.txt)" | |
| echo "Verifier hashes match: $(cat verifier-hash1.txt)" | |
| - name: Upload build artifacts | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: build-repro-artifacts | |
| overwrite: true | |
| path: | | |
| hash1.txt | |
| hash2.txt | |
| build1.wasm | |
| build2.wasm | |
| lowerer-hash1.txt | |
| lowerer-hash2.txt | |
| build1.lowerer.component.wasm | |
| build2.lowerer.component.wasm | |
| verifier-hash1.txt | |
| verifier-hash2.txt | |
| build1.verifier.component.wasm | |
| build2.verifier.component.wasm | |
| validate-evidence: | |
| name: Evidence artifact presence | |
| needs: | |
| - classify-changes | |
| - determinism-linux | |
| - determinism-macos | |
| - static-inspection | |
| - decoder-security | |
| - perf-regression | |
| - build-repro | |
| if: always() && needs.classify-changes.result == 'success' && needs.classify-changes.outputs.run_none != 'true' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Download all artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| path: gathered-artifacts | |
| - name: Verify upstream results and exact artifact payloads | |
| env: | |
| RUN_G1: ${{ needs.classify-changes.outputs.run_g1 }} | |
| RUN_G2: ${{ needs.classify-changes.outputs.run_g2 }} | |
| RUN_G3: ${{ needs.classify-changes.outputs.run_g3 }} | |
| RUN_G4: ${{ needs.classify-changes.outputs.run_g4 }} | |
| DECODER_RESULT: ${{ needs.decoder-security.result }} | |
| PERF_RESULT: ${{ needs.perf-regression.result }} | |
| REPRO_RESULT: ${{ needs.build-repro.result }} | |
| LINUX_RESULT: ${{ needs.determinism-linux.result }} | |
| MACOS_RESULT: ${{ needs.determinism-macos.result }} | |
| INSPECTION_RESULT: ${{ needs.static-inspection.result }} | |
| run: | | |
| ls -R gathered-artifacts | |
| required_artifacts=() | |
| if [ "$RUN_G1" = "true" ]; then | |
| [ "$LINUX_RESULT" = "success" ] || { echo "determinism-linux: $LINUX_RESULT"; exit 1; } | |
| [ "$MACOS_RESULT" = "success" ] || { echo "determinism-macos: $MACOS_RESULT"; exit 1; } | |
| [ "$INSPECTION_RESULT" = "success" ] || { echo "static-inspection: $INSPECTION_RESULT"; exit 1; } | |
| required_artifacts+=( | |
| gathered-artifacts/det-linux-artifacts/det-linux.log | |
| gathered-artifacts/det-linux-artifacts/trig-linux.log | |
| gathered-artifacts/det-linux-artifacts/dind-linux.log | |
| gathered-artifacts/det-linux-artifacts/dind-report.json | |
| gathered-artifacts/det-linux-artifacts/artifacts/digest-table.csv | |
| gathered-artifacts/det-macos-artifacts/det-macos.log | |
| gathered-artifacts/det-macos-artifacts/trig-macos.log | |
| gathered-artifacts/det-macos-artifacts/dind-macos.log | |
| gathered-artifacts/det-macos-artifacts/dind-report.json | |
| gathered-artifacts/det-macos-artifacts/artifacts/digest-table.csv | |
| gathered-artifacts/static-inspection/static-inspection.log | |
| ) | |
| fi | |
| if [ "$RUN_G2" = "true" ]; then | |
| [ "$DECODER_RESULT" = "success" ] || { echo "decoder-security: $DECODER_RESULT"; exit 1; } | |
| required_artifacts+=( | |
| gathered-artifacts/sec-artifacts/sec-tests.log | |
| ) | |
| fi | |
| if [ "$RUN_G3" = "true" ]; then | |
| [ "$PERF_RESULT" = "success" ] || { echo "perf-regression: $PERF_RESULT"; exit 1; } | |
| required_artifacts+=( | |
| gathered-artifacts/perf-artifacts/perf.log | |
| gathered-artifacts/perf-artifacts/perf-report.json | |
| ) | |
| fi | |
| if [ "$RUN_G4" = "true" ]; then | |
| [ "$REPRO_RESULT" = "success" ] || { echo "build-repro: $REPRO_RESULT"; exit 1; } | |
| required_artifacts+=( | |
| gathered-artifacts/build-repro-artifacts/hash1.txt | |
| gathered-artifacts/build-repro-artifacts/hash2.txt | |
| gathered-artifacts/build-repro-artifacts/build1.wasm | |
| gathered-artifacts/build-repro-artifacts/build2.wasm | |
| gathered-artifacts/build-repro-artifacts/lowerer-hash1.txt | |
| gathered-artifacts/build-repro-artifacts/lowerer-hash2.txt | |
| gathered-artifacts/build-repro-artifacts/build1.lowerer.component.wasm | |
| gathered-artifacts/build-repro-artifacts/build2.lowerer.component.wasm | |
| gathered-artifacts/build-repro-artifacts/verifier-hash1.txt | |
| gathered-artifacts/build-repro-artifacts/verifier-hash2.txt | |
| gathered-artifacts/build-repro-artifacts/build1.verifier.component.wasm | |
| gathered-artifacts/build-repro-artifacts/build2.verifier.component.wasm | |
| ) | |
| fi | |
| for artifact in "${required_artifacts[@]}"; do | |
| [ -s "$artifact" ] || { echo "Missing or empty artifact: $artifact"; exit 1; } | |
| done |