FastLanes mixed-schema retry #4
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: FastLanes mixed-schema retry | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| numeric_rows: | |
| description: Numeric control row count | |
| required: true | |
| default: "1000000" | |
| type: string | |
| timeout_seconds: | |
| description: Timeout per generated case | |
| required: true | |
| default: "120" | |
| type: string | |
| case_scope: | |
| description: Run the full matrix or only the mixed 13-column reproducer | |
| required: true | |
| default: full | |
| type: choice | |
| options: | |
| - full | |
| - mixed | |
| permissions: | |
| contents: read | |
| env: | |
| FASTLANES_SOURCE_COMMIT: f0edc1020a538f1f8098640fce8347c9ac247a0d | |
| RUN_DIR: runs/fastlanes-linux-${{ github.run_id }} | |
| ARCHIVE: outputs/release/data-format-lab-fastlanes-linux-x86_64-${{ github.run_id }}.tar.zst | |
| jobs: | |
| retry: | |
| name: FastLanes official Python round trip | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 30 | |
| steps: | |
| - name: Check out public commit | |
| id: checkout | |
| uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 | |
| - name: Initialize evidence directory | |
| if: always() | |
| run: | | |
| set -euo pipefail | |
| mkdir -p "$RUN_DIR" | |
| printf 'platform=linux-x86_64\nrun_id=%s\n' "$GITHUB_RUN_ID" \ | |
| > "$RUN_DIR/context.txt" | |
| - name: Install Nix | |
| uses: DeterminateSystems/nix-installer-action@ef8a148080ab6020fd15196c2084a2eea5ff2d25 # v22 | |
| - name: Synchronize frozen lab environment | |
| run: nix develop --command uv sync --frozen | |
| - name: Check out pinned FastLanes source | |
| id: source | |
| continue-on-error: true | |
| run: | | |
| set -euo pipefail | |
| mkdir -p native | |
| git clone --filter=blob:none --no-tags \ | |
| https://github.com/cwida/FastLanes.git native/fastlanes | |
| git -C native/fastlanes checkout --detach "$FASTLANES_SOURCE_COMMIT" | |
| git -C native/fastlanes submodule update --init --recursive | |
| git -C native/fastlanes rev-parse HEAD > "$RUN_DIR/source-commit.txt" | |
| - name: Build official Python extension | |
| id: build | |
| if: always() | |
| continue-on-error: true | |
| run: | | |
| set -euo pipefail | |
| mkdir -p "$RUN_DIR" | |
| set +e | |
| ( | |
| set -euo pipefail | |
| test -f native/fastlanes/CMakeLists.txt | |
| nix develop --command python -m venv native/fastlanes/.venv | |
| nix develop --command native/fastlanes/.venv/bin/python -m pip install \ | |
| --disable-pip-version-check --no-cache-dir \ | |
| scikit-build-core==0.11.6 \ | |
| cmake==4.4.0 \ | |
| ninja==1.13.0 \ | |
| pybind11==2.12.1 \ | |
| setuptools-scm==8.3.1 | |
| CMAKE_BUILD_PARALLEL_LEVEL=4 \ | |
| nix develop --command native/fastlanes/.venv/bin/python -m pip install \ | |
| --no-build-isolation -e native/fastlanes | |
| native/fastlanes/.venv/bin/python -c \ | |
| 'import pyfastlanes; print(pyfastlanes.get_version())' | |
| ) > "$RUN_DIR/build.log" 2>&1 | |
| status=$? | |
| set -e | |
| printf '%s\n' "$status" > "$RUN_DIR/build-status.txt" | |
| exit "$status" | |
| - name: Record reproducibility metadata | |
| if: always() | |
| run: | | |
| set -euo pipefail | |
| { | |
| printf 'source_commit=%s\n' "$FASTLANES_SOURCE_COMMIT" | |
| printf 'requested_numeric_rows=%s\n' '${{ inputs.numeric_rows }}' | |
| printf 'requested_timeout_seconds=%s\n' '${{ inputs.timeout_seconds }}' | |
| printf 'requested_case_scope=%s\n' '${{ inputs.case_scope }}' | |
| printf 'host_python='; python3 --version | |
| printf 'python='; nix develop --command python --version | |
| if test -x native/fastlanes/.venv/bin/python; then | |
| printf 'fastlanes_python='; native/fastlanes/.venv/bin/python --version | |
| fi | |
| printf 'clang='; nix develop --command clang --version | |
| printf 'cmake='; nix develop --command cmake --version | |
| printf 'ninja='; nix develop --command ninja --version | |
| printf 'uname='; uname -a | |
| } > "$RUN_DIR/environment.txt" 2>&1 || true | |
| - name: Run fresh-process FastLanes claims | |
| id: measure | |
| if: steps.build.outcome == 'success' | |
| continue-on-error: true | |
| env: | |
| NUMERIC_ROWS: ${{ inputs.numeric_rows }} | |
| CASE_TIMEOUT_SECONDS: ${{ inputs.timeout_seconds }} | |
| CASE_SCOPE: ${{ inputs.case_scope }} | |
| run: | | |
| set -euo pipefail | |
| status=0 | |
| case "$CASE_TIMEOUT_SECONDS" in | |
| ''|*[!0-9]*) | |
| printf '%s\n' "timeout_seconds must be an integer" \ | |
| > "$RUN_DIR/measure-error.txt" | |
| status=2 | |
| ;; | |
| *) | |
| if test "$CASE_TIMEOUT_SECONDS" -lt 1 || \ | |
| test "$CASE_TIMEOUT_SECONDS" -gt 120; then | |
| printf '%s\n' "timeout_seconds must be between 1 and 120" \ | |
| > "$RUN_DIR/measure-error.txt" | |
| status=2 | |
| fi | |
| ;; | |
| esac | |
| if test "$status" -eq 0; then | |
| set +e | |
| PYTHONPATH="$GITHUB_WORKSPACE/src" \ | |
| native/fastlanes/.venv/bin/python - "$RUN_DIR/results.json" \ | |
| > "$RUN_DIR/measure.log" 2>&1 <<'PY' | |
| import json | |
| import os | |
| import sys | |
| from pathlib import Path | |
| from format_bench.claims.fastlanes import run_fastlanes_claim | |
| output = Path(sys.argv[1]) | |
| evidence = run_fastlanes_claim( | |
| output.parent / "claims" / "fastlanes-official", | |
| numeric_rows=int(os.environ["NUMERIC_ROWS"]), | |
| timeout_seconds=float(os.environ["CASE_TIMEOUT_SECONDS"]), | |
| case_scope=os.environ["CASE_SCOPE"], | |
| ) | |
| payload = { | |
| "schema_version": "1", | |
| "contract_version": "1", | |
| "lane": "claims", | |
| "platform": "linux-x86_64", | |
| "source_commit": "f0edc1020a538f1f8098640fce8347c9ac247a0d", | |
| "evidence": evidence, | |
| } | |
| output.write_text(json.dumps(payload, indent=2, sort_keys=True) + "\n") | |
| PY | |
| status=$? | |
| set -e | |
| fi | |
| printf '%s\n' "$status" > "$RUN_DIR/measure-status.txt" | |
| exit "$status" | |
| - name: Preserve unavailable or failed evidence | |
| if: always() | |
| run: | | |
| set -euo pipefail | |
| if test -s "$RUN_DIR/results.json"; then | |
| exit 0 | |
| fi | |
| python3 - "$RUN_DIR/results.json" \ | |
| '${{ steps.source.outcome }}' '${{ steps.build.outcome }}' \ | |
| '${{ steps.measure.outcome }}' "$RUN_DIR/measure-error.txt" \ | |
| "$RUN_DIR/measure.log" <<'PY' | |
| import json | |
| import sys | |
| from pathlib import Path | |
| output = Path(sys.argv[1]) | |
| source, build, measure = sys.argv[2:5] | |
| error_path = Path(sys.argv[5]) | |
| log_path = Path(sys.argv[6]) | |
| available = source == "success" and build == "success" | |
| if error_path.is_file(): | |
| error = error_path.read_text().strip() | |
| elif log_path.is_file(): | |
| error = log_path.read_text(errors="replace").strip()[-1000:] | |
| else: | |
| error = "none" | |
| output.write_text( | |
| json.dumps( | |
| { | |
| "schema_version": "1", | |
| "contract_version": "1", | |
| "lane": "claims", | |
| "platform": "linux-x86_64", | |
| "source_commit": "f0edc1020a538f1f8098640fce8347c9ac247a0d", | |
| "status": "UNSUPPORTED" if not available else "FAILED", | |
| "failure_reason": ( | |
| f"source={source}; build={build}; measure={measure}; error={error}" | |
| ), | |
| }, | |
| indent=2, | |
| sort_keys=True, | |
| ) | |
| + "\n" | |
| ) | |
| PY | |
| - name: Bind case inputs and artifacts to hashes | |
| if: always() | |
| run: | | |
| set -euo pipefail | |
| if test ! -s "$RUN_DIR/results.json" || \ | |
| test ! -d "$RUN_DIR/claims/fastlanes-official"; then | |
| exit 0 | |
| fi | |
| python3 - "$RUN_DIR/results.json" \ | |
| "$RUN_DIR/claims/fastlanes-official" <<'PY' | |
| import hashlib | |
| import json | |
| import sys | |
| from pathlib import Path | |
| results_path = Path(sys.argv[1]) | |
| root = Path(sys.argv[2]) | |
| def digest(path: Path) -> str | None: | |
| if not path.is_file(): | |
| return None | |
| value = hashlib.sha256() | |
| with path.open("rb") as handle: | |
| for chunk in iter(lambda: handle.read(1024 * 1024), b""): | |
| value.update(chunk) | |
| return value.hexdigest() | |
| case_hashes = {} | |
| for case_dir in sorted(path for path in root.iterdir() if path.is_dir()): | |
| case_hashes[case_dir.name] = { | |
| "input/data.csv": digest(case_dir / "input/data.csv"), | |
| "input/schema.json": digest(case_dir / "input/schema.json"), | |
| "data.fls": digest(case_dir / "data.fls"), | |
| "decoded.csv": digest(case_dir / "decoded.csv"), | |
| } | |
| payload = json.loads(results_path.read_text()) | |
| payload["case_hash_algorithm"] = "sha256" | |
| payload["case_hashes"] = case_hashes | |
| results_path.write_text(json.dumps(payload, indent=2, sort_keys=True) + "\n") | |
| PY | |
| - name: Write concise evidence report | |
| if: always() | |
| run: | | |
| set -euo pipefail | |
| python3 - "$RUN_DIR/results.json" "$RUN_DIR/report.md" <<'PY' | |
| import json | |
| import sys | |
| from pathlib import Path | |
| results = json.loads(Path(sys.argv[1]).read_text()) | |
| lines = [ | |
| "# FastLanes Linux x86_64 retry", | |
| "", | |
| f"- Source commit: `{results['source_commit']}`", | |
| f"- Platform: `{results['platform']}`", | |
| ] | |
| evidence = results.get("evidence") | |
| if evidence: | |
| lines.extend(["", f"- Status: `{evidence.get('status', 'UNKNOWN')}`"]) | |
| lines.append(f"- Summary: {evidence.get('summary', 'not available')}") | |
| else: | |
| lines.extend(["", f"- Status: `{results.get('status', 'UNKNOWN')}`"]) | |
| lines.append(f"- Failure: {results.get('failure_reason', 'not available')}") | |
| Path(sys.argv[2]).write_text("\n".join(lines) + "\n") | |
| PY | |
| - name: Package evidence | |
| if: always() | |
| run: | | |
| set -euo pipefail | |
| mkdir -p "$(dirname "$ARCHIVE")" | |
| tar --zstd --sort=name --mtime='UTC 2026-07-17' \ | |
| -cf "$ARCHIVE" -C "$RUN_DIR" . | |
| sha256sum "$ARCHIVE" > "$ARCHIVE.sha256" | |
| - name: Upload FastLanes evidence | |
| if: always() | |
| uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 | |
| with: | |
| name: data-format-lab-fastlanes-linux-x86_64-${{ github.run_id }} | |
| path: | | |
| ${{ env.RUN_DIR }} | |
| ${{ env.ARCHIVE }} | |
| ${{ env.ARCHIVE }}.sha256 | |
| retention-days: 14 | |
| if-no-files-found: error | |
| - name: Preserve retry failure status | |
| if: always() | |
| run: | | |
| set -euo pipefail | |
| test '${{ steps.checkout.outcome }}' = success | |
| test '${{ steps.source.outcome }}' = success | |
| test '${{ steps.build.outcome }}' = success | |
| python3 - "$RUN_DIR/results.json" <<'PY' | |
| import json | |
| import sys | |
| from pathlib import Path | |
| results = json.loads(Path(sys.argv[1]).read_text()) | |
| evidence = results.get("evidence") | |
| if evidence is None: | |
| raise SystemExit( | |
| "FastLanes claim " | |
| f"{results.get('status', 'UNKNOWN')}: " | |
| f"{results.get('failure_reason', 'no failure reason recorded')}" | |
| ) | |
| if evidence.get("status") != "MEASURED": | |
| raise SystemExit(f"FastLanes claim status: {evidence.get('status')}") | |
| mixed = evidence.get("mixed", {}) | |
| if mixed.get("outcome") != "ROUNDTRIP_EQUAL": | |
| raise SystemExit(f"mixed 13-column outcome: {mixed.get('outcome')}") | |
| PY |