diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..60f3fb1 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,9 @@ +# Preserve source fingerprints and generated text references on every checkout. +* text=auto eol=lf +*.png binary +*.jpg binary +*.jpeg binary +*.gif binary +*.zip binary +*.whl binary +*.gz binary diff --git a/.github/workflows/documentation-artifact.yml b/.github/workflows/documentation-artifact.yml index 6a0e732..20055d1 100644 --- a/.github/workflows/documentation-artifact.yml +++ b/.github/workflows/documentation-artifact.yml @@ -1,26 +1,40 @@ -name: Documentation preview artifact +name: Documentation validation on: + pull_request: workflow_dispatch: + workflow_call: + inputs: + pages_artifact: + description: Upload the validated site for the master-only Pages deploy + type: boolean + default: false permissions: contents: read jobs: build: - if: github.ref == 'refs/heads/master' runs-on: ubuntu-latest + timeout-minutes: 10 steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 - - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 + persist-credentials: false + - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: '3.12' - run: python -m pip install -r requirements-docs.txt - run: python -m pip install -e . + - run: python tools/check_architecture_docs.py --examples --getting-started --api-reference --tutorials --showcase --website - run: python -m mkdocs build --strict - run: python tools/check_site.py --site-dir site - - run: python tools/check_architecture_docs.py --examples --getting-started --api-reference --tutorials --showcase --website - - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 with: name: documentation-preview path: site + if-no-files-found: error retention-days: 7 + - name: Upload checked Pages artifact + if: inputs.pages_artifact && github.repository == 'AlexMarinescu/pyGameMath' && github.ref == 'refs/heads/master' + uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0 + with: + path: site diff --git a/.github/workflows/github-release.yml b/.github/workflows/github-release.yml new file mode 100644 index 0000000..879e23b --- /dev/null +++ b/.github/workflows/github-release.yml @@ -0,0 +1,88 @@ +name: GitHub release preparation +on: + workflow_dispatch: + inputs: + commit_sha: + description: Full final reviewed master commit SHA + required: true + type: string + validation_run_id: + description: Successful manual candidate-validation run for that exact commit + required: true + type: string + mode: + description: Publication requires Phase 5D authorization and repository enablement + type: choice + options: [validate, publish] + default: validate + owner_authorization: + description: Leave empty for validation; explicit publication requires publish v1.0.0 + type: string + default: '' +permissions: + contents: read +concurrency: + group: gem-github-release + cancel-in-progress: false +jobs: + validate: + if: github.repository == 'AlexMarinescu/pyGameMath' && github.ref == 'refs/heads/master' && github.event_name == 'workflow_dispatch' + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: + contents: read + actions: read + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + fetch-depth: 0 + persist-credentials: false + - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + with: + python-version: '3.12' + - uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 + with: + name: gem-candidate-${{ inputs.validation_run_id }} + run-id: ${{ inputs.validation_run_id }} + github-token: ${{ github.token }} + path: ${{ runner.temp }}/gem-candidate + digest-mismatch: error + - name: Verify exact previously installed candidate assets without rebuilding + run: python tools/github_release.py --directory "${{ runner.temp }}/gem-candidate" --commit "$CANDIDATE_COMMIT" --run-id "$VALIDATION_RUN" --mode validate --output github-release-validation.json + env: + CANDIDATE_COMMIT: ${{ inputs.commit_sha }} + VALIDATION_RUN: ${{ inputs.validation_run_id }} + GITHUB_TOKEN: ${{ github.token }} + publish: + if: inputs.mode == 'publish' && vars.GEM_RELEASE_ENABLED == 'true' && inputs.owner_authorization == 'publish v1.0.0' + needs: validate + environment: gem-release-review + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: + contents: write + actions: read + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + fetch-depth: 0 + persist-credentials: false + - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + with: + python-version: '3.12' + - uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 + with: + name: gem-candidate-${{ inputs.validation_run_id }} + run-id: ${{ inputs.validation_run_id }} + github-token: ${{ github.token }} + path: ${{ runner.temp }}/gem-candidate + digest-mismatch: error + - name: Publish only after explicit owner authorization and protected approval + run: python tools/github_release.py --directory "${{ runner.temp }}/gem-candidate" --commit "$CANDIDATE_COMMIT" --run-id "$VALIDATION_RUN" --mode publish --output github-release-publication.json + env: + CANDIDATE_COMMIT: ${{ inputs.commit_sha }} + VALIDATION_RUN: ${{ inputs.validation_run_id }} + RELEASE_ENABLED: ${{ vars.GEM_RELEASE_ENABLED }} + OWNER_AUTHORIZATION: ${{ inputs.owner_authorization }} + GITHUB_TOKEN: ${{ github.token }} + GH_TOKEN: ${{ github.token }} diff --git a/.github/workflows/package-validation.yml b/.github/workflows/package-validation.yml index 056b791..87b6b1c 100644 --- a/.github/workflows/package-validation.yml +++ b/.github/workflows/package-validation.yml @@ -1,32 +1,63 @@ name: Package validation on: pull_request: + push: + branches: [master] workflow_dispatch: + workflow_call: + inputs: + source_ref: + description: Exact candidate SHA supplied by manual release preflight + type: string + default: '' permissions: contents: read +concurrency: + group: package-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} jobs: package: - runs-on: ubuntu-latest + name: ${{ matrix.os }} / Python ${{ matrix.python }} + runs-on: ${{ matrix.os }} + timeout-minutes: 25 strategy: fail-fast: false matrix: + os: [ubuntu-latest, windows-latest, macos-latest] python: ['3.10', '3.11', '3.12', '3.13', '3.14'] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 - - uses: actions/setup-python@v5 + persist-credentials: false + ref: ${{ inputs.source_ref || github.sha }} + - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: ${{ matrix.python }} - - run: python -m pip install -r requirements-audit.txt -r requirements-build.txt six - - run: python -m pytest -q - - run: python tools/check_architecture_docs.py --examples --getting-started --api-reference --tutorials --showcase --website - - run: python -m build - - run: python -m twine check --strict dist/* - - run: python tools/verify_distribution.py --wheel dist/gem-1.0.0-py3-none-any.whl --sdist dist/gem-1.0.0.tar.gz - - run: python -m venv /tmp/gem-wheel && /tmp/gem-wheel/bin/python -m pip install dist/*.whl - - run: /tmp/gem-wheel/bin/python -I tools/verify_distribution.py --installed-root /tmp/gem-wheel/lib/python${{ matrix.python }}/site-packages - - run: python -m venv /tmp/gem-sdist && /tmp/gem-sdist/bin/python -m pip install dist/*.tar.gz - - run: /tmp/gem-sdist/bin/python -I tools/verify_distribution.py --installed-root /tmp/gem-sdist/lib/python${{ matrix.python }}/site-packages - - run: /tmp/gem-wheel/bin/python -I tools/check_architecture_docs.py --examples --getting-started --api-reference --tutorials --showcase --website --package-root /tmp/gem-wheel/lib/python${{ matrix.python }}/site-packages - - run: /tmp/gem-sdist/bin/python -I tools/check_architecture_docs.py --examples --getting-started --api-reference --tutorials --showcase --website --package-root /tmp/gem-sdist/lib/python${{ matrix.python }}/site-packages + - run: python -m pip install -r requirements-ci.txt + - name: Full suite, API examples, clean builds and isolated installs + run: python tools/ci_validate.py --output-dir "${{ runner.temp }}/gem-ci" --artifacts-dir dist + - name: Retain failure diagnostics and verification results + if: always() + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 + with: + name: gem-checks-${{ runner.os }}-py${{ matrix.python }}-${{ github.run_id }} + path: | + ${{ runner.temp }}/gem-ci/*.json + ${{ runner.temp }}/gem-ci/*.xml + ${{ runner.temp }}/gem-ci/*.log + if-no-files-found: warn + retention-days: 14 + - name: Retain validated candidate (one canonical build) + if: success() && matrix.os == 'ubuntu-latest' && matrix.python == '3.12' + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 + with: + name: gem-candidate-${{ github.run_id }} + path: | + dist/gem-1.0.0-py3-none-any.whl + dist/gem-1.0.0.tar.gz + dist/candidate-manifest.json + dist/SHA256SUMS + dist/INSTALL.md + if-no-files-found: error + retention-days: 14 diff --git a/.github/workflows/publish-docs.yml b/.github/workflows/publish-docs.yml index 2b6c935..a1eeb11 100644 --- a/.github/workflows/publish-docs.yml +++ b/.github/workflows/publish-docs.yml @@ -11,44 +11,32 @@ on: - 'README.md' - 'mkdocs.yml' - 'requirements-docs.txt' + - '.github/workflows/documentation-artifact.yml' - '.github/workflows/publish-docs.yml' workflow_dispatch: - permissions: contents: read - pages: write - id-token: write - concurrency: group: github-pages cancel-in-progress: false - jobs: build: - if: github.ref == 'refs/heads/master' - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - uses: actions/setup-python@v5 - with: - python-version: '3.12' - - run: python -m pip install -r requirements-docs.txt - - run: python -m pip install -e . - - run: python -m mkdocs build --strict - - run: python tools/check_site.py --site-dir site - - run: python tools/check_architecture_docs.py --examples --getting-started --api-reference --tutorials --showcase --website - - uses: actions/upload-pages-artifact@v3 - with: - path: site - + if: github.repository == 'AlexMarinescu/pyGameMath' && github.ref == 'refs/heads/master' + uses: ./.github/workflows/documentation-artifact.yml + with: + pages_artifact: true deploy: needs: build + if: github.repository == 'AlexMarinescu/pyGameMath' && github.ref == 'refs/heads/master' runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: + contents: read + pages: write + id-token: write environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} steps: - id: deployment - uses: actions/deploy-pages@v4 + uses: actions/deploy-pages@368f82528645a54fb793d4d04e342629a3f51346 # v5.0.1 diff --git a/.github/workflows/release-validation.yml b/.github/workflows/release-validation.yml new file mode 100644 index 0000000..7cf4a22 --- /dev/null +++ b/.github/workflows/release-validation.yml @@ -0,0 +1,101 @@ +name: Manual release candidate validation +on: + workflow_dispatch: + inputs: + commit_sha: + description: Full lowercase SHA of the reviewed master commit + type: string + required: true + version: + description: Exact source distribution version + type: string + default: '1.0.0' + required: true + tag: + description: Proposed tag (this workflow never creates it) + type: string + default: 'v1.0.0' + required: true + mode: + description: Validation only, or protected human review; neither publishes + type: choice + options: [validate, review] + default: validate +permissions: + contents: read +concurrency: + group: gem-release-validation + cancel-in-progress: false +jobs: + preflight: + if: github.repository == 'AlexMarinescu/pyGameMath' && github.ref == 'refs/heads/master' && github.event_name == 'workflow_dispatch' + runs-on: ubuntu-latest + timeout-minutes: 5 + permissions: + contents: read + actions: read + outputs: + commit: ${{ steps.gate.outputs.source_commit }} + tag: ${{ steps.gate.outputs.tag }} + mode: ${{ steps.gate.outputs.mode }} + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + fetch-depth: 0 + persist-credentials: false + - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + with: + python-version: '3.12' + - id: gate + run: python tools/release_gate.py preflight --output release-preflight.json + env: + INPUT_COMMIT: ${{ inputs.commit_sha }} + INPUT_VERSION: ${{ inputs.version }} + INPUT_TAG: ${{ inputs.tag }} + INPUT_MODE: ${{ inputs.mode }} + GITHUB_TOKEN: ${{ github.token }} + - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 + with: + name: release-preflight-${{ github.run_id }} + path: release-preflight.json + if-no-files-found: error + validation: + needs: preflight + uses: ./.github/workflows/package-validation.yml + with: + source_ref: ${{ needs.preflight.outputs.commit }} + integrity: + needs: [preflight, validation] + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: ${{ needs.preflight.outputs.commit }} + persist-credentials: false + - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + with: + python-version: '3.12' + - uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 + with: + name: gem-candidate-${{ github.run_id }} + path: ${{ runner.temp }}/gem-candidate + digest-mismatch: error + - run: python tools/release_gate.py verify-artifacts --directory "${{ runner.temp }}/gem-candidate" --commit "$CANDIDATE_COMMIT" --tag "$CANDIDATE_TAG" --output release-integrity.json + env: + CANDIDATE_COMMIT: ${{ needs.preflight.outputs.commit }} + CANDIDATE_TAG: ${{ needs.preflight.outputs.tag }} + - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 + with: + name: release-integrity-${{ github.run_id }} + path: release-integrity.json + if-no-files-found: error + review: + if: needs.preflight.outputs.mode == 'review' + needs: [preflight, validation, integrity] + runs-on: ubuntu-latest + timeout-minutes: 5 + environment: gem-release-review + steps: + - name: Record protected human review; publication remains disabled + run: echo "Candidate validation approved. PyPI authority remains unverified; no publication is enabled." diff --git a/audit/PHASE5B-CI-RELEASE.md b/audit/PHASE5B-CI-RELEASE.md new file mode 100644 index 0000000..250dc76 --- /dev/null +++ b/audit/PHASE5B-CI-RELEASE.md @@ -0,0 +1,146 @@ +# Phase 5B — CI and release safeguards + +Base: master `deea819172cf86f6f7aabeaa2693af1e33afb1b7` (PR #61). +Branch: `release/phase5b-ci`. PR: [#62](https://github.com/AlexMarinescu/pyGameMath/pull/62). +Verified implementation: `0e085b4bfd532935bb1267bb3eae95f04de3ed15`. +Final report changes follow that commit; they do not change the tested implementation. + +## Changes + +Reuse Phase 5A checks through a portable harness. Run CPython 3.10–3.14 on +Ubuntu, Windows and macOS, retaining diagnostics even when the full suite fails. +Pin official Actions to reviewed full SHAs, disable persisted checkout credentials, +and keep default permissions read-only. Strict documentation checks precede +Pages deployment; only its deployment job receives Pages/OIDC permissions. +No workflow writes to the Wiki. + +The manual release-validation workflow checks canonical repository, master, +commit, version, tag, clean-source provenance, archive contents and hashes. +Protected review requires reviewers, prevented self-review, no administrator +bypass and protected-branch restrictions. Missing protection fails closed. + +The separate GitHub Release workflow defaults to validation-only and consumes +the exact assets from a successful manual validation run without rebuilding. +Publication additionally requires `GEM_RELEASE_ENABLED=true`, the exact owner +confirmation phrase and protected review. It checks remote tag binding before +attaching the wheel, sdist, SHA-256 checksums and installation instructions. +Existing releases cannot be overwritten. PyPI publishing remains disabled. + +Portable changes address LF checkout behavior, fixture encoding, path separators, +virtual-environment invocation and benchmark environment metadata. Benchmark +workloads and comparison rules are unchanged. The documentation checker retains +its frozen API baseline with exact-hash allowances for three supporting files. +All 18 runtime Python files, packaging metadata, runtime dependencies and +reference assertions remain unchanged. + +## Verification + +[Machine-readable evidence](phase5b-verification.json) records interpreter and +platform details, exact commands, artifact digests, installed checks and job URLs. +Hosted [package run 38014899622](https://github.com/AlexMarinescu/pyGameMath/actions/runs/38014899622) +completed all 15 jobs at the verified implementation commit: + +| CPython | Ubuntu passed / failed | macOS passed / failed | Windows passed / failed | +| --- | --- | --- | --- | +| 3.10 | 4036 / 0 | 4036 / 0 | 4035 / 1 | +| 3.11 | 4036 / 0 | 4036 / 0 | 4035 / 1 | +| 3.12 | 4036 / 0 | 4036 / 0 | 4035 / 1 | +| 3.13 | 4036 / 0 | 4036 / 0 | 4035 / 1 | +| 3.14 | 4036 / 0 | 4036 / 0 | 4035 / 1 | + +Every job has zero expected failures and zero skips. All 15 jobs passed clean +builds, metadata/content checks, `twine check --strict`, isolated wheel and sdist +installation, mathematical smoke checks, 268 API declarations and 43 executable +documentation examples: 30 isolated installations in total. Each job tests its +own built artifacts. A failed suite still prevents candidate approval. + +Local Linux verification passed 4036 tests on each of CPython 3.10.21, 3.11.16, +3.12.14, 3.13.15 and 3.14.7. The host was Linux 6.18.44, x86-64, glibc 2.41. +An initial interpreter lacking ensurepip was replaced with an installable CPython +3.13 environment before final verification. + +The permanent release-safeguard suite has 70 passing tests, separate from +mathematical regressions. Eight deterministic tampering cases cover an extra +asset, dirty source, wrong commit, wrong tree, wrong version, modified digest, +modified checksum listing and empty installation instructions. Additional cases +exercise forks/events, protection settings, workflow permissions, failed test +results, archive paths, candidate provenance and remote tag binding. Publication +commands are mocked; validation-only execution performs no external mutations. + +Actionlint 1.7.12 and the workflow policy checker pass. Shellcheck was unavailable +and was not run. Hosted documentation +[run 38014899541](https://github.com/AlexMarinescu/pyGameMath/actions/runs/38014899541) +passed. Local strict MkDocs/site checks validated 69 pages, 7724 local links and +anchors, 24 MathML equations, 16 image references, seven navigation checks and +42 API search entries, with no external asset dependencies. All nine tutorials +remain present. + +### Windows blocker: CI-WIN01 + +The remaining failure on all five Windows interpreters is +`tests.test_showcase_examples::test_committed_artifacts_match_generation`. +The quaternion scene in `examples/showcase/scenes.py` generates a measurement +of `0.8660254037844389` where the committed JSON contains +`0.8660254037844387`. The strict dictionary comparison rejects this last-bit +difference. Numerical reference checks pass; SVG references match after the LF +checkout correction. This is a cross-platform exact-reference limitation, not +an independently confirmed mathematical defect. + +The assertion and committed reference are intact: no rounding, skip, xfail or +relaxed tolerance was introduced. Windows support is not fully verified. The +full matrix is red and release approval remains blocked until a reviewed change +resolves the portable-reference policy. Initial Windows encoding/path failures +and macOS affinity metadata failures were corrected without weakening assertions. + +### Reproduction commands + +From a clean checkout, install `requirements-ci.txt`, then run: + +```sh +python -m pytest -q +python -m pytest -q tests/test_release_workflows.py +python tools/check_workflows.py +python tools/ci_validate.py --output-dir /tmp/gem-ci --artifacts-dir /tmp/gem-assets +python -m mkdocs build --strict --site-dir /tmp/gem-site +python tools/check_site.py --site-dir /tmp/gem-site --output /tmp/gem-site.json +``` + +The final local candidate used: + +```sh +/tmp/phase5a-py312/bin/python tools/ci_validate.py --output-dir /tmp/phase5b-complete --artifacts-dir /tmp/phase5b-complete-assets --offline --wheelhouse /tmp/phase5b-wheelhouse +/tmp/phase5a-py312/bin/python tools/release_gate.py verify-artifacts --directory /tmp/phase5b-complete-assets --commit 0e085b4bfd532935bb1267bb3eae95f04de3ed15 --tag v1.0.0 --output /tmp/phase5b-complete-integrity.json +/tmp/phase5b-tools/actionlint -color=false -shellcheck='' .github/workflows/*.yml +``` + +These preparation artifacts passed installation and integrity checks; they are +not official release assets: + +| Artifact | SHA-256 | +| --- | --- | +| Wheel | `f4d6b096ab99c7c0c8f3b5bd71239a307ec0d467bc819d6d01e045f2c0b2d141` | +| Sdist | `230fca9becbcd0afb2b5e520eeae099fe60fc00213abf292d2bbcfdc1c134de8` | + +## Release safeguards and remaining prerequisites + +- CI-WIN01 blocks full-matrix release validation. +- Server-side release-environment configuration could not be inspected through + the available connector. Guards reject missing protection; configuration and + a real protected approval still require maintainer verification. Master reports + protected, but that alone does not prove all required status policies. +- A moderate default-branch Dependabot alert remains untriaged in this review: + [alert #2](https://github.com/AlexMarinescu/pyGameMath/security/dependabot/2). + Alert details were inaccessible; no vulnerability-free claim is made. +- Publishing authority for historical `gem` remains unverified. This blocks PyPI + upload only, not GitHub-first preparation or separately authorized publication. +- Real master-only release dispatch and protected approval were not executed on + this unmerged branch. Repository protection/enablement settings were not changed. + +See the [CI and release guide](../docs/development/ci-release.md) and +[prepared release notes](../docs/development/release-notes-1.0.0.md) for installation, +protection settings, artifact retention and failure handling. Phase 5C must +validate the final reviewed release commit. Phase 5D publication requires separate +explicit owner authorization. Preserve official verified archives for eventual +PyPI upload rather than silently rebuilding them. + +No tags, GitHub Releases, PyPI/TestPyPI uploads, Wiki writes or merges were performed. diff --git a/audit/phase5b-verification.json b/audit/phase5b-verification.json new file mode 100644 index 0000000..ae702ac --- /dev/null +++ b/audit/phase5b-verification.json @@ -0,0 +1,4443 @@ +{ + "schema_version": 1, + "base_commit": "deea819172cf86f6f7aabeaa2693af1e33afb1b7", + "verified_code_commit": "0e085b4bfd532935bb1267bb3eae95f04de3ed15", + "date_utc": "2026-10-10", + "release_strategy": "GitHub-first; preparation only", + "local_tests": [ + { + "python": "3.10.21", + "platform": "Linux-6.18.44-x86_64-with-glibc2.41", + "tests": { + "tests": 4036, + "failures": 0, + "errors": 0, + "skipped": 0, + "passed": 4036, + "xfailed": 0 + }, + "command": [ + "/tmp/phase5a-py310/bin/python", + "-m", + "pytest", + "-q", + "-o", + "junit_family=legacy", + "--junitxml", + "/tmp/phase5b-complete-pytest-310.xml" + ] + }, + { + "python": "3.11.16", + "platform": "Linux-6.18.44-x86_64-with-glibc2.41", + "tests": { + "tests": 4036, + "failures": 0, + "errors": 0, + "skipped": 0, + "passed": 4036, + "xfailed": 0 + }, + "command": [ + "/tmp/phase5a-py311/bin/python", + "-m", + "pytest", + "-q", + "-o", + "junit_family=legacy", + "--junitxml", + "/tmp/phase5b-complete-pytest-311.xml" + ] + }, + { + "python": "3.12.14", + "platform": "Linux-6.18.44-x86_64-with-glibc2.41", + "tests": { + "tests": 4036, + "failures": 0, + "errors": 0, + "skipped": 0, + "passed": 4036, + "xfailed": 0 + }, + "command": [ + "/tmp/phase5a-py312/bin/python", + "-m", + "pytest", + "-q", + "-o", + "junit_family=legacy", + "--junitxml", + "/tmp/phase5b-complete-pytest-312.xml" + ] + }, + { + "python": "3.13.15", + "platform": "Linux-6.18.44-x86_64-with-glibc2.41", + "tests": { + "tests": 4036, + "failures": 0, + "errors": 0, + "skipped": 0, + "passed": 4036, + "xfailed": 0 + }, + "command": [ + "/tmp/phase5b-py313/bin/python", + "-m", + "pytest", + "-q", + "-o", + "junit_family=legacy", + "--junitxml", + "/tmp/phase5b-complete-pytest-313.xml" + ] + }, + { + "python": "3.14.7", + "platform": "Linux-6.18.44-x86_64-with-glibc2.41", + "tests": { + "tests": 4036, + "failures": 0, + "errors": 0, + "skipped": 0, + "passed": 4036, + "xfailed": 0 + }, + "command": [ + "/tmp/phase5a-py314/bin/python", + "-m", + "pytest", + "-q", + "-o", + "junit_family=legacy", + "--junitxml", + "/tmp/phase5b-complete-pytest-314.xml" + ] + } + ], + "hosted_run": { + "id": 38014899622, + "url": "https://github.com/AlexMarinescu/pyGameMath/actions/runs/38014899622", + "conclusion": "failure", + "jobs": [ + { + "id": 114102890232, + "name": "windows-latest / Python 3.11", + "conclusion": "failure", + "html_url": "https://github.com/AlexMarinescu/pyGameMath/actions/runs/38014899622/job/114102890232" + }, + { + "id": 114102890299, + "name": "macos-latest / Python 3.12", + "conclusion": "success", + "html_url": "https://github.com/AlexMarinescu/pyGameMath/actions/runs/38014899622/job/114102890299" + }, + { + "id": 114102890311, + "name": "macos-latest / Python 3.11", + "conclusion": "success", + "html_url": "https://github.com/AlexMarinescu/pyGameMath/actions/runs/38014899622/job/114102890311" + }, + { + "id": 114102890332, + "name": "windows-latest / Python 3.10", + "conclusion": "failure", + "html_url": "https://github.com/AlexMarinescu/pyGameMath/actions/runs/38014899622/job/114102890332" + }, + { + "id": 114102890345, + "name": "windows-latest / Python 3.12", + "conclusion": "failure", + "html_url": "https://github.com/AlexMarinescu/pyGameMath/actions/runs/38014899622/job/114102890345" + }, + { + "id": 114102890353, + "name": "ubuntu-latest / Python 3.13", + "conclusion": "success", + "html_url": "https://github.com/AlexMarinescu/pyGameMath/actions/runs/38014899622/job/114102890353" + }, + { + "id": 114102890362, + "name": "windows-latest / Python 3.14", + "conclusion": "failure", + "html_url": "https://github.com/AlexMarinescu/pyGameMath/actions/runs/38014899622/job/114102890362" + }, + { + "id": 114102890367, + "name": "macos-latest / Python 3.14", + "conclusion": "success", + "html_url": "https://github.com/AlexMarinescu/pyGameMath/actions/runs/38014899622/job/114102890367" + }, + { + "id": 114102890368, + "name": "windows-latest / Python 3.13", + "conclusion": "failure", + "html_url": "https://github.com/AlexMarinescu/pyGameMath/actions/runs/38014899622/job/114102890368" + }, + { + "id": 114102890369, + "name": "ubuntu-latest / Python 3.11", + "conclusion": "success", + "html_url": "https://github.com/AlexMarinescu/pyGameMath/actions/runs/38014899622/job/114102890369" + }, + { + "id": 114102890370, + "name": "macos-latest / Python 3.10", + "conclusion": "success", + "html_url": "https://github.com/AlexMarinescu/pyGameMath/actions/runs/38014899622/job/114102890370" + }, + { + "id": 114102890375, + "name": "macos-latest / Python 3.13", + "conclusion": "success", + "html_url": "https://github.com/AlexMarinescu/pyGameMath/actions/runs/38014899622/job/114102890375" + }, + { + "id": 114102890377, + "name": "ubuntu-latest / Python 3.12", + "conclusion": "success", + "html_url": "https://github.com/AlexMarinescu/pyGameMath/actions/runs/38014899622/job/114102890377" + }, + { + "id": 114102890425, + "name": "ubuntu-latest / Python 3.14", + "conclusion": "success", + "html_url": "https://github.com/AlexMarinescu/pyGameMath/actions/runs/38014899622/job/114102890425" + }, + { + "id": 114102890428, + "name": "ubuntu-latest / Python 3.10", + "conclusion": "success", + "html_url": "https://github.com/AlexMarinescu/pyGameMath/actions/runs/38014899622/job/114102890428" + } + ] + }, + "hosted_results": [ + { + "name": "gem-checks-Linux-py3.10-38014899622", + "artifact_id": 11655734615, + "artifact_digest": "sha256:a3b47a7dfbb5bc811f02593b90e9f8df7b45ee745c3e8fb0fc9c54b8ba2ecc0c", + "python": "3.10.22", + "platform": "Linux-6.17.0-1022-azure-x86_64-with-glibc2.39", + "status": "passed", + "tests": { + "tests": 4036, + "failures": 0, + "errors": 0, + "skipped": 0, + "passed": 4036, + "xfailed": 0 + }, + "failed_tests": [], + "installed": { + "wheel": { + "smoke": { + "installed": { + "python": "3.10.22", + "implementation": "CPython", + "platform": "Linux-6.17.0-1022-azure-x86_64-with-glibc2.39", + "package_root": "/home/runner/work/_temp/gem-ci/wheel-environment/lib/python3.10/site-packages", + "version": "1.0.0", + "runtime_files_matched": 18, + "smoke": "passed" + } + }, + "api_declarations": 268, + "examples": 43 + }, + "sdist": { + "smoke": { + "installed": { + "python": "3.10.22", + "implementation": "CPython", + "platform": "Linux-6.17.0-1022-azure-x86_64-with-glibc2.39", + "package_root": "/home/runner/work/_temp/gem-ci/sdist-environment/lib/python3.10/site-packages", + "version": "1.0.0", + "runtime_files_matched": 18, + "smoke": "passed" + } + }, + "api_declarations": 268, + "examples": 43 + } + }, + "api_declarations": 268, + "examples": 43, + "artifacts": { + "metadata": { + "Metadata-Version": "2.4", + "Name": "gem", + "Version": "1.0.0", + "Summary": "Pure-Python mathematics for games and graphics", + "Author-email": "Alex Marinescu ", + "License-Expression": "BSD-2-Clause", + "Project-URL": "Issues, https://github.com/AlexMarinescu/pyGameMath/issues", + "Keywords": "math,game,graphics,library", + "Classifier": "Topic :: Software Development :: Libraries", + "Requires-Python": ">=3.10", + "Description-Content-Type": "text/markdown", + "License-File": "LICENSE", + "Requires-Dist": "six", + "Dynamic": "license-file" + }, + "runtime_files": 18 + }, + "commands": [ + { + "argv": [ + "/opt/hostedtoolcache/Python/3.10.22/x64/bin/python", + "-m", + "pytest", + "-q", + "-o", + "junit_family=legacy", + "--junitxml", + "/home/runner/work/_temp/gem-ci/pytest.xml" + ], + "cwd": "/home/runner/work/pyGameMath/pyGameMath", + "log": "pytest.log" + }, + { + "argv": [ + "/opt/hostedtoolcache/Python/3.10.22/x64/bin/python", + "/home/runner/work/pyGameMath/pyGameMath/tools/check_architecture_docs.py", + "--examples", + "--getting-started", + "--api-reference", + "--tutorials", + "--showcase", + "--website", + "--output", + "/home/runner/work/_temp/gem-ci/source-docs.json" + ], + "cwd": "/home/runner/work/pyGameMath/pyGameMath", + "log": "source-docs.log" + }, + { + "argv": [ + "/opt/hostedtoolcache/Python/3.10.22/x64/bin/python", + "-m", + "build", + "--outdir", + "/home/runner/work/pyGameMath/pyGameMath/dist" + ], + "cwd": "/home/runner/work/pyGameMath/pyGameMath", + "log": "build.log" + }, + { + "argv": [ + "/opt/hostedtoolcache/Python/3.10.22/x64/bin/python", + "-m", + "twine", + "check", + "--strict", + "/home/runner/work/pyGameMath/pyGameMath/dist/gem-1.0.0-py3-none-any.whl", + "/home/runner/work/pyGameMath/pyGameMath/dist/gem-1.0.0.tar.gz" + ], + "cwd": "/home/runner/work/pyGameMath/pyGameMath", + "log": "twine.log" + }, + { + "argv": [ + "/opt/hostedtoolcache/Python/3.10.22/x64/bin/python", + "/home/runner/work/pyGameMath/pyGameMath/tools/verify_distribution.py", + "--wheel", + "/home/runner/work/pyGameMath/pyGameMath/dist/gem-1.0.0-py3-none-any.whl", + "--sdist", + "/home/runner/work/pyGameMath/pyGameMath/dist/gem-1.0.0.tar.gz", + "--output", + "/home/runner/work/_temp/gem-ci/artifacts.json" + ], + "cwd": "/home/runner/work/pyGameMath/pyGameMath", + "log": "artifacts.log" + }, + { + "argv": [ + "/home/runner/work/_temp/gem-ci/wheel-environment/bin/python", + "-m", + "pip", + "install", + "six==1.17.0", + "setuptools==80.9.0", + "wheel==0.48.0" + ], + "cwd": "/home/runner/work/_temp/gem-ci", + "log": "wheel-dependencies.log" + }, + { + "argv": [ + "/home/runner/work/_temp/gem-ci/wheel-environment/bin/python", + "-I", + "-m", + "pip", + "install", + "--no-deps", + "--no-build-isolation", + "/home/runner/work/pyGameMath/pyGameMath/dist/gem-1.0.0-py3-none-any.whl" + ], + "cwd": "/home/runner/work/_temp/gem-ci", + "log": "wheel-install.log" + }, + { + "argv": [ + "/home/runner/work/_temp/gem-ci/wheel-environment/bin/python", + "-I", + "-X", + "utf8", + "/home/runner/work/pyGameMath/pyGameMath/tools/verify_distribution.py", + "--installed-root", + "/home/runner/work/_temp/gem-ci/wheel-environment/lib/python3.10/site-packages", + "--output", + "/home/runner/work/_temp/gem-ci/wheel-smoke.json" + ], + "cwd": "/home/runner/work/_temp/gem-ci", + "log": "wheel-smoke.log" + }, + { + "argv": [ + "/home/runner/work/_temp/gem-ci/wheel-environment/bin/python", + "-I", + "-X", + "utf8", + "/home/runner/work/pyGameMath/pyGameMath/tools/check_architecture_docs.py", + "--examples", + "--getting-started", + "--api-reference", + "--tutorials", + "--showcase", + "--website", + "--package-root", + "/home/runner/work/_temp/gem-ci/wheel-environment/lib/python3.10/site-packages", + "--output", + "/home/runner/work/_temp/gem-ci/wheel-docs.json" + ], + "cwd": "/home/runner/work/_temp/gem-ci", + "log": "wheel-docs.log" + }, + { + "argv": [ + "/home/runner/work/_temp/gem-ci/sdist-environment/bin/python", + "-m", + "pip", + "install", + "six==1.17.0", + "setuptools==80.9.0", + "wheel==0.48.0" + ], + "cwd": "/home/runner/work/_temp/gem-ci", + "log": "sdist-dependencies.log" + }, + { + "argv": [ + "/home/runner/work/_temp/gem-ci/sdist-environment/bin/python", + "-I", + "-m", + "pip", + "install", + "--no-deps", + "--no-build-isolation", + "/home/runner/work/pyGameMath/pyGameMath/dist/gem-1.0.0.tar.gz" + ], + "cwd": "/home/runner/work/_temp/gem-ci", + "log": "sdist-install.log" + }, + { + "argv": [ + "/home/runner/work/_temp/gem-ci/sdist-environment/bin/python", + "-I", + "-X", + "utf8", + "/home/runner/work/pyGameMath/pyGameMath/tools/verify_distribution.py", + "--installed-root", + "/home/runner/work/_temp/gem-ci/sdist-environment/lib/python3.10/site-packages", + "--output", + "/home/runner/work/_temp/gem-ci/sdist-smoke.json" + ], + "cwd": "/home/runner/work/_temp/gem-ci", + "log": "sdist-smoke.log" + }, + { + "argv": [ + "/home/runner/work/_temp/gem-ci/sdist-environment/bin/python", + "-I", + "-X", + "utf8", + "/home/runner/work/pyGameMath/pyGameMath/tools/check_architecture_docs.py", + "--examples", + "--getting-started", + "--api-reference", + "--tutorials", + "--showcase", + "--website", + "--package-root", + "/home/runner/work/_temp/gem-ci/sdist-environment/lib/python3.10/site-packages", + "--output", + "/home/runner/work/_temp/gem-ci/sdist-docs.json" + ], + "cwd": "/home/runner/work/_temp/gem-ci", + "log": "sdist-docs.log" + } + ] + }, + { + "name": "gem-checks-Linux-py3.11-38014899622", + "artifact_id": 11655674643, + "artifact_digest": "sha256:4ff838b436926e4cb204273f113ccf303473f45ef6ff03757e439263675de922", + "python": "3.11.17", + "platform": "Linux-6.17.0-1022-azure-x86_64-with-glibc2.39", + "status": "passed", + "tests": { + "tests": 4036, + "failures": 0, + "errors": 0, + "skipped": 0, + "passed": 4036, + "xfailed": 0 + }, + "failed_tests": [], + "installed": { + "wheel": { + "smoke": { + "installed": { + "python": "3.11.17", + "implementation": "CPython", + "platform": "Linux-6.17.0-1022-azure-x86_64-with-glibc2.39", + "package_root": "/home/runner/work/_temp/gem-ci/wheel-environment/lib/python3.11/site-packages", + "version": "1.0.0", + "runtime_files_matched": 18, + "smoke": "passed" + } + }, + "api_declarations": 268, + "examples": 43 + }, + "sdist": { + "smoke": { + "installed": { + "python": "3.11.17", + "implementation": "CPython", + "platform": "Linux-6.17.0-1022-azure-x86_64-with-glibc2.39", + "package_root": "/home/runner/work/_temp/gem-ci/sdist-environment/lib/python3.11/site-packages", + "version": "1.0.0", + "runtime_files_matched": 18, + "smoke": "passed" + } + }, + "api_declarations": 268, + "examples": 43 + } + }, + "api_declarations": 268, + "examples": 43, + "artifacts": { + "metadata": { + "Metadata-Version": "2.4", + "Name": "gem", + "Version": "1.0.0", + "Summary": "Pure-Python mathematics for games and graphics", + "Author-email": "Alex Marinescu ", + "License-Expression": "BSD-2-Clause", + "Project-URL": "Issues, https://github.com/AlexMarinescu/pyGameMath/issues", + "Keywords": "math,game,graphics,library", + "Classifier": "Topic :: Software Development :: Libraries", + "Requires-Python": ">=3.10", + "Description-Content-Type": "text/markdown", + "License-File": "LICENSE", + "Requires-Dist": "six", + "Dynamic": "license-file" + }, + "runtime_files": 18 + }, + "commands": [ + { + "argv": [ + "/opt/hostedtoolcache/Python/3.11.17/x64/bin/python", + "-m", + "pytest", + "-q", + "-o", + "junit_family=legacy", + "--junitxml", + "/home/runner/work/_temp/gem-ci/pytest.xml" + ], + "cwd": "/home/runner/work/pyGameMath/pyGameMath", + "log": "pytest.log" + }, + { + "argv": [ + "/opt/hostedtoolcache/Python/3.11.17/x64/bin/python", + "/home/runner/work/pyGameMath/pyGameMath/tools/check_architecture_docs.py", + "--examples", + "--getting-started", + "--api-reference", + "--tutorials", + "--showcase", + "--website", + "--output", + "/home/runner/work/_temp/gem-ci/source-docs.json" + ], + "cwd": "/home/runner/work/pyGameMath/pyGameMath", + "log": "source-docs.log" + }, + { + "argv": [ + "/opt/hostedtoolcache/Python/3.11.17/x64/bin/python", + "-m", + "build", + "--outdir", + "/home/runner/work/pyGameMath/pyGameMath/dist" + ], + "cwd": "/home/runner/work/pyGameMath/pyGameMath", + "log": "build.log" + }, + { + "argv": [ + "/opt/hostedtoolcache/Python/3.11.17/x64/bin/python", + "-m", + "twine", + "check", + "--strict", + "/home/runner/work/pyGameMath/pyGameMath/dist/gem-1.0.0-py3-none-any.whl", + "/home/runner/work/pyGameMath/pyGameMath/dist/gem-1.0.0.tar.gz" + ], + "cwd": "/home/runner/work/pyGameMath/pyGameMath", + "log": "twine.log" + }, + { + "argv": [ + "/opt/hostedtoolcache/Python/3.11.17/x64/bin/python", + "/home/runner/work/pyGameMath/pyGameMath/tools/verify_distribution.py", + "--wheel", + "/home/runner/work/pyGameMath/pyGameMath/dist/gem-1.0.0-py3-none-any.whl", + "--sdist", + "/home/runner/work/pyGameMath/pyGameMath/dist/gem-1.0.0.tar.gz", + "--output", + "/home/runner/work/_temp/gem-ci/artifacts.json" + ], + "cwd": "/home/runner/work/pyGameMath/pyGameMath", + "log": "artifacts.log" + }, + { + "argv": [ + "/home/runner/work/_temp/gem-ci/wheel-environment/bin/python", + "-m", + "pip", + "install", + "six==1.17.0", + "setuptools==80.9.0", + "wheel==0.48.0" + ], + "cwd": "/home/runner/work/_temp/gem-ci", + "log": "wheel-dependencies.log" + }, + { + "argv": [ + "/home/runner/work/_temp/gem-ci/wheel-environment/bin/python", + "-I", + "-m", + "pip", + "install", + "--no-deps", + "--no-build-isolation", + "/home/runner/work/pyGameMath/pyGameMath/dist/gem-1.0.0-py3-none-any.whl" + ], + "cwd": "/home/runner/work/_temp/gem-ci", + "log": "wheel-install.log" + }, + { + "argv": [ + "/home/runner/work/_temp/gem-ci/wheel-environment/bin/python", + "-I", + "-X", + "utf8", + "/home/runner/work/pyGameMath/pyGameMath/tools/verify_distribution.py", + "--installed-root", + "/home/runner/work/_temp/gem-ci/wheel-environment/lib/python3.11/site-packages", + "--output", + "/home/runner/work/_temp/gem-ci/wheel-smoke.json" + ], + "cwd": "/home/runner/work/_temp/gem-ci", + "log": "wheel-smoke.log" + }, + { + "argv": [ + "/home/runner/work/_temp/gem-ci/wheel-environment/bin/python", + "-I", + "-X", + "utf8", + "/home/runner/work/pyGameMath/pyGameMath/tools/check_architecture_docs.py", + "--examples", + "--getting-started", + "--api-reference", + "--tutorials", + "--showcase", + "--website", + "--package-root", + "/home/runner/work/_temp/gem-ci/wheel-environment/lib/python3.11/site-packages", + "--output", + "/home/runner/work/_temp/gem-ci/wheel-docs.json" + ], + "cwd": "/home/runner/work/_temp/gem-ci", + "log": "wheel-docs.log" + }, + { + "argv": [ + "/home/runner/work/_temp/gem-ci/sdist-environment/bin/python", + "-m", + "pip", + "install", + "six==1.17.0", + "setuptools==80.9.0", + "wheel==0.48.0" + ], + "cwd": "/home/runner/work/_temp/gem-ci", + "log": "sdist-dependencies.log" + }, + { + "argv": [ + "/home/runner/work/_temp/gem-ci/sdist-environment/bin/python", + "-I", + "-m", + "pip", + "install", + "--no-deps", + "--no-build-isolation", + "/home/runner/work/pyGameMath/pyGameMath/dist/gem-1.0.0.tar.gz" + ], + "cwd": "/home/runner/work/_temp/gem-ci", + "log": "sdist-install.log" + }, + { + "argv": [ + "/home/runner/work/_temp/gem-ci/sdist-environment/bin/python", + "-I", + "-X", + "utf8", + "/home/runner/work/pyGameMath/pyGameMath/tools/verify_distribution.py", + "--installed-root", + "/home/runner/work/_temp/gem-ci/sdist-environment/lib/python3.11/site-packages", + "--output", + "/home/runner/work/_temp/gem-ci/sdist-smoke.json" + ], + "cwd": "/home/runner/work/_temp/gem-ci", + "log": "sdist-smoke.log" + }, + { + "argv": [ + "/home/runner/work/_temp/gem-ci/sdist-environment/bin/python", + "-I", + "-X", + "utf8", + "/home/runner/work/pyGameMath/pyGameMath/tools/check_architecture_docs.py", + "--examples", + "--getting-started", + "--api-reference", + "--tutorials", + "--showcase", + "--website", + "--package-root", + "/home/runner/work/_temp/gem-ci/sdist-environment/lib/python3.11/site-packages", + "--output", + "/home/runner/work/_temp/gem-ci/sdist-docs.json" + ], + "cwd": "/home/runner/work/_temp/gem-ci", + "log": "sdist-docs.log" + } + ] + }, + { + "name": "gem-checks-macOS-py3.11-38014899622", + "artifact_id": 11655614447, + "artifact_digest": "sha256:166a6270ca618b6cbaea255933a8253bd8792d5abc060d1de526e4a46741aef0", + "python": "3.11.9", + "platform": "macOS-26.6.2-arm64-arm-64bit", + "status": "passed", + "tests": { + "tests": 4036, + "failures": 0, + "errors": 0, + "skipped": 0, + "passed": 4036, + "xfailed": 0 + }, + "failed_tests": [], + "installed": { + "wheel": { + "smoke": { + "installed": { + "python": "3.11.9", + "implementation": "CPython", + "platform": "macOS-26.6.2-arm64-arm-64bit", + "package_root": "/Users/runner/work/_temp/gem-ci/wheel-environment/lib/python3.11/site-packages", + "version": "1.0.0", + "runtime_files_matched": 18, + "smoke": "passed" + } + }, + "api_declarations": 268, + "examples": 43 + }, + "sdist": { + "smoke": { + "installed": { + "python": "3.11.9", + "implementation": "CPython", + "platform": "macOS-26.6.2-arm64-arm-64bit", + "package_root": "/Users/runner/work/_temp/gem-ci/sdist-environment/lib/python3.11/site-packages", + "version": "1.0.0", + "runtime_files_matched": 18, + "smoke": "passed" + } + }, + "api_declarations": 268, + "examples": 43 + } + }, + "api_declarations": 268, + "examples": 43, + "artifacts": { + "metadata": { + "Metadata-Version": "2.4", + "Name": "gem", + "Version": "1.0.0", + "Summary": "Pure-Python mathematics for games and graphics", + "Author-email": "Alex Marinescu ", + "License-Expression": "BSD-2-Clause", + "Project-URL": "Issues, https://github.com/AlexMarinescu/pyGameMath/issues", + "Keywords": "math,game,graphics,library", + "Classifier": "Topic :: Software Development :: Libraries", + "Requires-Python": ">=3.10", + "Description-Content-Type": "text/markdown", + "License-File": "LICENSE", + "Requires-Dist": "six", + "Dynamic": "license-file" + }, + "runtime_files": 18 + }, + "commands": [ + { + "argv": [ + "/Library/Frameworks/Python.framework/Versions/3.11/bin/python", + "-m", + "pytest", + "-q", + "-o", + "junit_family=legacy", + "--junitxml", + "/Users/runner/work/_temp/gem-ci/pytest.xml" + ], + "cwd": "/Users/runner/work/pyGameMath/pyGameMath", + "log": "pytest.log" + }, + { + "argv": [ + "/Library/Frameworks/Python.framework/Versions/3.11/bin/python", + "/Users/runner/work/pyGameMath/pyGameMath/tools/check_architecture_docs.py", + "--examples", + "--getting-started", + "--api-reference", + "--tutorials", + "--showcase", + "--website", + "--output", + "/Users/runner/work/_temp/gem-ci/source-docs.json" + ], + "cwd": "/Users/runner/work/pyGameMath/pyGameMath", + "log": "source-docs.log" + }, + { + "argv": [ + "/Library/Frameworks/Python.framework/Versions/3.11/bin/python", + "-m", + "build", + "--outdir", + "/Users/runner/work/pyGameMath/pyGameMath/dist" + ], + "cwd": "/Users/runner/work/pyGameMath/pyGameMath", + "log": "build.log" + }, + { + "argv": [ + "/Library/Frameworks/Python.framework/Versions/3.11/bin/python", + "-m", + "twine", + "check", + "--strict", + "/Users/runner/work/pyGameMath/pyGameMath/dist/gem-1.0.0-py3-none-any.whl", + "/Users/runner/work/pyGameMath/pyGameMath/dist/gem-1.0.0.tar.gz" + ], + "cwd": "/Users/runner/work/pyGameMath/pyGameMath", + "log": "twine.log" + }, + { + "argv": [ + "/Library/Frameworks/Python.framework/Versions/3.11/bin/python", + "/Users/runner/work/pyGameMath/pyGameMath/tools/verify_distribution.py", + "--wheel", + "/Users/runner/work/pyGameMath/pyGameMath/dist/gem-1.0.0-py3-none-any.whl", + "--sdist", + "/Users/runner/work/pyGameMath/pyGameMath/dist/gem-1.0.0.tar.gz", + "--output", + "/Users/runner/work/_temp/gem-ci/artifacts.json" + ], + "cwd": "/Users/runner/work/pyGameMath/pyGameMath", + "log": "artifacts.log" + }, + { + "argv": [ + "/Users/runner/work/_temp/gem-ci/wheel-environment/bin/python", + "-m", + "pip", + "install", + "six==1.17.0", + "setuptools==80.9.0", + "wheel==0.48.0" + ], + "cwd": "/Users/runner/work/_temp/gem-ci", + "log": "wheel-dependencies.log" + }, + { + "argv": [ + "/Users/runner/work/_temp/gem-ci/wheel-environment/bin/python", + "-I", + "-m", + "pip", + "install", + "--no-deps", + "--no-build-isolation", + "/Users/runner/work/pyGameMath/pyGameMath/dist/gem-1.0.0-py3-none-any.whl" + ], + "cwd": "/Users/runner/work/_temp/gem-ci", + "log": "wheel-install.log" + }, + { + "argv": [ + "/Users/runner/work/_temp/gem-ci/wheel-environment/bin/python", + "-I", + "-X", + "utf8", + "/Users/runner/work/pyGameMath/pyGameMath/tools/verify_distribution.py", + "--installed-root", + "/Users/runner/work/_temp/gem-ci/wheel-environment/lib/python3.11/site-packages", + "--output", + "/Users/runner/work/_temp/gem-ci/wheel-smoke.json" + ], + "cwd": "/Users/runner/work/_temp/gem-ci", + "log": "wheel-smoke.log" + }, + { + "argv": [ + "/Users/runner/work/_temp/gem-ci/wheel-environment/bin/python", + "-I", + "-X", + "utf8", + "/Users/runner/work/pyGameMath/pyGameMath/tools/check_architecture_docs.py", + "--examples", + "--getting-started", + "--api-reference", + "--tutorials", + "--showcase", + "--website", + "--package-root", + "/Users/runner/work/_temp/gem-ci/wheel-environment/lib/python3.11/site-packages", + "--output", + "/Users/runner/work/_temp/gem-ci/wheel-docs.json" + ], + "cwd": "/Users/runner/work/_temp/gem-ci", + "log": "wheel-docs.log" + }, + { + "argv": [ + "/Users/runner/work/_temp/gem-ci/sdist-environment/bin/python", + "-m", + "pip", + "install", + "six==1.17.0", + "setuptools==80.9.0", + "wheel==0.48.0" + ], + "cwd": "/Users/runner/work/_temp/gem-ci", + "log": "sdist-dependencies.log" + }, + { + "argv": [ + "/Users/runner/work/_temp/gem-ci/sdist-environment/bin/python", + "-I", + "-m", + "pip", + "install", + "--no-deps", + "--no-build-isolation", + "/Users/runner/work/pyGameMath/pyGameMath/dist/gem-1.0.0.tar.gz" + ], + "cwd": "/Users/runner/work/_temp/gem-ci", + "log": "sdist-install.log" + }, + { + "argv": [ + "/Users/runner/work/_temp/gem-ci/sdist-environment/bin/python", + "-I", + "-X", + "utf8", + "/Users/runner/work/pyGameMath/pyGameMath/tools/verify_distribution.py", + "--installed-root", + "/Users/runner/work/_temp/gem-ci/sdist-environment/lib/python3.11/site-packages", + "--output", + "/Users/runner/work/_temp/gem-ci/sdist-smoke.json" + ], + "cwd": "/Users/runner/work/_temp/gem-ci", + "log": "sdist-smoke.log" + }, + { + "argv": [ + "/Users/runner/work/_temp/gem-ci/sdist-environment/bin/python", + "-I", + "-X", + "utf8", + "/Users/runner/work/pyGameMath/pyGameMath/tools/check_architecture_docs.py", + "--examples", + "--getting-started", + "--api-reference", + "--tutorials", + "--showcase", + "--website", + "--package-root", + "/Users/runner/work/_temp/gem-ci/sdist-environment/lib/python3.11/site-packages", + "--output", + "/Users/runner/work/_temp/gem-ci/sdist-docs.json" + ], + "cwd": "/Users/runner/work/_temp/gem-ci", + "log": "sdist-docs.log" + } + ] + }, + { + "name": "gem-checks-Windows-py3.13-38014899622", + "artifact_id": 11655569791, + "artifact_digest": "sha256:44bedb400b1141e08a79b1e4c634da8301d349216e71ecae007f747d6da909ca", + "python": "3.13.15", + "platform": "Windows-2025Server-10.0.26100-SP0", + "status": "failed", + "tests": { + "tests": 4036, + "failures": 1, + "errors": 0, + "skipped": 0, + "passed": 4035, + "xfailed": 0 + }, + "failed_tests": [ + "tests.test_showcase_examples::test_committed_artifacts_match_generation" + ], + "installed": { + "wheel": { + "smoke": { + "installed": { + "python": "3.13.15", + "implementation": "CPython", + "platform": "Windows-2025Server-10.0.26100-SP0", + "package_root": "D:\\a\\_temp\\gem-ci\\wheel-environment\\Lib\\site-packages", + "version": "1.0.0", + "runtime_files_matched": 18, + "smoke": "passed" + } + }, + "api_declarations": 268, + "examples": 43 + }, + "sdist": { + "smoke": { + "installed": { + "python": "3.13.15", + "implementation": "CPython", + "platform": "Windows-2025Server-10.0.26100-SP0", + "package_root": "D:\\a\\_temp\\gem-ci\\sdist-environment\\Lib\\site-packages", + "version": "1.0.0", + "runtime_files_matched": 18, + "smoke": "passed" + } + }, + "api_declarations": 268, + "examples": 43 + } + }, + "api_declarations": 268, + "examples": 43, + "artifacts": { + "metadata": { + "Metadata-Version": "2.4", + "Name": "gem", + "Version": "1.0.0", + "Summary": "Pure-Python mathematics for games and graphics", + "Author-email": "Alex Marinescu ", + "License-Expression": "BSD-2-Clause", + "Project-URL": "Issues, https://github.com/AlexMarinescu/pyGameMath/issues", + "Keywords": "math,game,graphics,library", + "Classifier": "Topic :: Software Development :: Libraries", + "Requires-Python": ">=3.10", + "Description-Content-Type": "text/markdown", + "License-File": "LICENSE", + "Requires-Dist": "six", + "Dynamic": "license-file" + }, + "runtime_files": 18 + }, + "commands": [ + { + "argv": [ + "C:\\hostedtoolcache\\windows\\Python\\3.13.15\\x64\\python.exe", + "-m", + "pytest", + "-q", + "-o", + "junit_family=legacy", + "--junitxml", + "D:\\a\\_temp\\gem-ci\\pytest.xml" + ], + "cwd": "D:\\a\\pyGameMath\\pyGameMath", + "log": "pytest.log" + }, + { + "argv": [ + "C:\\hostedtoolcache\\windows\\Python\\3.13.15\\x64\\python.exe", + "D:\\a\\pyGameMath\\pyGameMath\\tools\\check_architecture_docs.py", + "--examples", + "--getting-started", + "--api-reference", + "--tutorials", + "--showcase", + "--website", + "--output", + "D:\\a\\_temp\\gem-ci\\source-docs.json" + ], + "cwd": "D:\\a\\pyGameMath\\pyGameMath", + "log": "source-docs.log" + }, + { + "argv": [ + "C:\\hostedtoolcache\\windows\\Python\\3.13.15\\x64\\python.exe", + "-m", + "build", + "--outdir", + "D:\\a\\pyGameMath\\pyGameMath\\dist" + ], + "cwd": "D:\\a\\pyGameMath\\pyGameMath", + "log": "build.log" + }, + { + "argv": [ + "C:\\hostedtoolcache\\windows\\Python\\3.13.15\\x64\\python.exe", + "-m", + "twine", + "check", + "--strict", + "D:\\a\\pyGameMath\\pyGameMath\\dist\\gem-1.0.0-py3-none-any.whl", + "D:\\a\\pyGameMath\\pyGameMath\\dist\\gem-1.0.0.tar.gz" + ], + "cwd": "D:\\a\\pyGameMath\\pyGameMath", + "log": "twine.log" + }, + { + "argv": [ + "C:\\hostedtoolcache\\windows\\Python\\3.13.15\\x64\\python.exe", + "D:\\a\\pyGameMath\\pyGameMath\\tools\\verify_distribution.py", + "--wheel", + "D:\\a\\pyGameMath\\pyGameMath\\dist\\gem-1.0.0-py3-none-any.whl", + "--sdist", + "D:\\a\\pyGameMath\\pyGameMath\\dist\\gem-1.0.0.tar.gz", + "--output", + "D:\\a\\_temp\\gem-ci\\artifacts.json" + ], + "cwd": "D:\\a\\pyGameMath\\pyGameMath", + "log": "artifacts.log" + }, + { + "argv": [ + "D:\\a\\_temp\\gem-ci\\wheel-environment\\Scripts\\python.exe", + "-m", + "pip", + "install", + "six==1.17.0", + "setuptools==80.9.0", + "wheel==0.48.0" + ], + "cwd": "D:\\a\\_temp\\gem-ci", + "log": "wheel-dependencies.log" + }, + { + "argv": [ + "D:\\a\\_temp\\gem-ci\\wheel-environment\\Scripts\\python.exe", + "-I", + "-m", + "pip", + "install", + "--no-deps", + "--no-build-isolation", + "D:\\a\\pyGameMath\\pyGameMath\\dist\\gem-1.0.0-py3-none-any.whl" + ], + "cwd": "D:\\a\\_temp\\gem-ci", + "log": "wheel-install.log" + }, + { + "argv": [ + "D:\\a\\_temp\\gem-ci\\wheel-environment\\Scripts\\python.exe", + "-I", + "-X", + "utf8", + "D:\\a\\pyGameMath\\pyGameMath\\tools\\verify_distribution.py", + "--installed-root", + "D:\\a\\_temp\\gem-ci\\wheel-environment\\Lib\\site-packages", + "--output", + "D:\\a\\_temp\\gem-ci\\wheel-smoke.json" + ], + "cwd": "D:\\a\\_temp\\gem-ci", + "log": "wheel-smoke.log" + }, + { + "argv": [ + "D:\\a\\_temp\\gem-ci\\wheel-environment\\Scripts\\python.exe", + "-I", + "-X", + "utf8", + "D:\\a\\pyGameMath\\pyGameMath\\tools\\check_architecture_docs.py", + "--examples", + "--getting-started", + "--api-reference", + "--tutorials", + "--showcase", + "--website", + "--package-root", + "D:\\a\\_temp\\gem-ci\\wheel-environment\\Lib\\site-packages", + "--output", + "D:\\a\\_temp\\gem-ci\\wheel-docs.json" + ], + "cwd": "D:\\a\\_temp\\gem-ci", + "log": "wheel-docs.log" + }, + { + "argv": [ + "D:\\a\\_temp\\gem-ci\\sdist-environment\\Scripts\\python.exe", + "-m", + "pip", + "install", + "six==1.17.0", + "setuptools==80.9.0", + "wheel==0.48.0" + ], + "cwd": "D:\\a\\_temp\\gem-ci", + "log": "sdist-dependencies.log" + }, + { + "argv": [ + "D:\\a\\_temp\\gem-ci\\sdist-environment\\Scripts\\python.exe", + "-I", + "-m", + "pip", + "install", + "--no-deps", + "--no-build-isolation", + "D:\\a\\pyGameMath\\pyGameMath\\dist\\gem-1.0.0.tar.gz" + ], + "cwd": "D:\\a\\_temp\\gem-ci", + "log": "sdist-install.log" + }, + { + "argv": [ + "D:\\a\\_temp\\gem-ci\\sdist-environment\\Scripts\\python.exe", + "-I", + "-X", + "utf8", + "D:\\a\\pyGameMath\\pyGameMath\\tools\\verify_distribution.py", + "--installed-root", + "D:\\a\\_temp\\gem-ci\\sdist-environment\\Lib\\site-packages", + "--output", + "D:\\a\\_temp\\gem-ci\\sdist-smoke.json" + ], + "cwd": "D:\\a\\_temp\\gem-ci", + "log": "sdist-smoke.log" + }, + { + "argv": [ + "D:\\a\\_temp\\gem-ci\\sdist-environment\\Scripts\\python.exe", + "-I", + "-X", + "utf8", + "D:\\a\\pyGameMath\\pyGameMath\\tools\\check_architecture_docs.py", + "--examples", + "--getting-started", + "--api-reference", + "--tutorials", + "--showcase", + "--website", + "--package-root", + "D:\\a\\_temp\\gem-ci\\sdist-environment\\Lib\\site-packages", + "--output", + "D:\\a\\_temp\\gem-ci\\sdist-docs.json" + ], + "cwd": "D:\\a\\_temp\\gem-ci", + "log": "sdist-docs.log" + } + ] + }, + { + "name": "gem-checks-macOS-py3.13-38014899622", + "artifact_id": 11655499824, + "artifact_digest": "sha256:96bf77c97881334a7a7969084ac9ef441f04fb4e9027db49d4997f99ab86a757", + "python": "3.13.15", + "platform": "macOS-26.6.2-arm64-arm-64bit-Mach-O", + "status": "passed", + "tests": { + "tests": 4036, + "failures": 0, + "errors": 0, + "skipped": 0, + "passed": 4036, + "xfailed": 0 + }, + "failed_tests": [], + "installed": { + "wheel": { + "smoke": { + "installed": { + "python": "3.13.15", + "implementation": "CPython", + "platform": "macOS-26.6.2-arm64-arm-64bit-Mach-O", + "package_root": "/Users/runner/work/_temp/gem-ci/wheel-environment/lib/python3.13/site-packages", + "version": "1.0.0", + "runtime_files_matched": 18, + "smoke": "passed" + } + }, + "api_declarations": 268, + "examples": 43 + }, + "sdist": { + "smoke": { + "installed": { + "python": "3.13.15", + "implementation": "CPython", + "platform": "macOS-26.6.2-arm64-arm-64bit-Mach-O", + "package_root": "/Users/runner/work/_temp/gem-ci/sdist-environment/lib/python3.13/site-packages", + "version": "1.0.0", + "runtime_files_matched": 18, + "smoke": "passed" + } + }, + "api_declarations": 268, + "examples": 43 + } + }, + "api_declarations": 268, + "examples": 43, + "artifacts": { + "metadata": { + "Metadata-Version": "2.4", + "Name": "gem", + "Version": "1.0.0", + "Summary": "Pure-Python mathematics for games and graphics", + "Author-email": "Alex Marinescu ", + "License-Expression": "BSD-2-Clause", + "Project-URL": "Issues, https://github.com/AlexMarinescu/pyGameMath/issues", + "Keywords": "math,game,graphics,library", + "Classifier": "Topic :: Software Development :: Libraries", + "Requires-Python": ">=3.10", + "Description-Content-Type": "text/markdown", + "License-File": "LICENSE", + "Requires-Dist": "six", + "Dynamic": "license-file" + }, + "runtime_files": 18 + }, + "commands": [ + { + "argv": [ + "/Library/Frameworks/Python.framework/Versions/3.13/bin/python", + "-m", + "pytest", + "-q", + "-o", + "junit_family=legacy", + "--junitxml", + "/Users/runner/work/_temp/gem-ci/pytest.xml" + ], + "cwd": "/Users/runner/work/pyGameMath/pyGameMath", + "log": "pytest.log" + }, + { + "argv": [ + "/Library/Frameworks/Python.framework/Versions/3.13/bin/python", + "/Users/runner/work/pyGameMath/pyGameMath/tools/check_architecture_docs.py", + "--examples", + "--getting-started", + "--api-reference", + "--tutorials", + "--showcase", + "--website", + "--output", + "/Users/runner/work/_temp/gem-ci/source-docs.json" + ], + "cwd": "/Users/runner/work/pyGameMath/pyGameMath", + "log": "source-docs.log" + }, + { + "argv": [ + "/Library/Frameworks/Python.framework/Versions/3.13/bin/python", + "-m", + "build", + "--outdir", + "/Users/runner/work/pyGameMath/pyGameMath/dist" + ], + "cwd": "/Users/runner/work/pyGameMath/pyGameMath", + "log": "build.log" + }, + { + "argv": [ + "/Library/Frameworks/Python.framework/Versions/3.13/bin/python", + "-m", + "twine", + "check", + "--strict", + "/Users/runner/work/pyGameMath/pyGameMath/dist/gem-1.0.0-py3-none-any.whl", + "/Users/runner/work/pyGameMath/pyGameMath/dist/gem-1.0.0.tar.gz" + ], + "cwd": "/Users/runner/work/pyGameMath/pyGameMath", + "log": "twine.log" + }, + { + "argv": [ + "/Library/Frameworks/Python.framework/Versions/3.13/bin/python", + "/Users/runner/work/pyGameMath/pyGameMath/tools/verify_distribution.py", + "--wheel", + "/Users/runner/work/pyGameMath/pyGameMath/dist/gem-1.0.0-py3-none-any.whl", + "--sdist", + "/Users/runner/work/pyGameMath/pyGameMath/dist/gem-1.0.0.tar.gz", + "--output", + "/Users/runner/work/_temp/gem-ci/artifacts.json" + ], + "cwd": "/Users/runner/work/pyGameMath/pyGameMath", + "log": "artifacts.log" + }, + { + "argv": [ + "/Users/runner/work/_temp/gem-ci/wheel-environment/bin/python", + "-m", + "pip", + "install", + "six==1.17.0", + "setuptools==80.9.0", + "wheel==0.48.0" + ], + "cwd": "/Users/runner/work/_temp/gem-ci", + "log": "wheel-dependencies.log" + }, + { + "argv": [ + "/Users/runner/work/_temp/gem-ci/wheel-environment/bin/python", + "-I", + "-m", + "pip", + "install", + "--no-deps", + "--no-build-isolation", + "/Users/runner/work/pyGameMath/pyGameMath/dist/gem-1.0.0-py3-none-any.whl" + ], + "cwd": "/Users/runner/work/_temp/gem-ci", + "log": "wheel-install.log" + }, + { + "argv": [ + "/Users/runner/work/_temp/gem-ci/wheel-environment/bin/python", + "-I", + "-X", + "utf8", + "/Users/runner/work/pyGameMath/pyGameMath/tools/verify_distribution.py", + "--installed-root", + "/Users/runner/work/_temp/gem-ci/wheel-environment/lib/python3.13/site-packages", + "--output", + "/Users/runner/work/_temp/gem-ci/wheel-smoke.json" + ], + "cwd": "/Users/runner/work/_temp/gem-ci", + "log": "wheel-smoke.log" + }, + { + "argv": [ + "/Users/runner/work/_temp/gem-ci/wheel-environment/bin/python", + "-I", + "-X", + "utf8", + "/Users/runner/work/pyGameMath/pyGameMath/tools/check_architecture_docs.py", + "--examples", + "--getting-started", + "--api-reference", + "--tutorials", + "--showcase", + "--website", + "--package-root", + "/Users/runner/work/_temp/gem-ci/wheel-environment/lib/python3.13/site-packages", + "--output", + "/Users/runner/work/_temp/gem-ci/wheel-docs.json" + ], + "cwd": "/Users/runner/work/_temp/gem-ci", + "log": "wheel-docs.log" + }, + { + "argv": [ + "/Users/runner/work/_temp/gem-ci/sdist-environment/bin/python", + "-m", + "pip", + "install", + "six==1.17.0", + "setuptools==80.9.0", + "wheel==0.48.0" + ], + "cwd": "/Users/runner/work/_temp/gem-ci", + "log": "sdist-dependencies.log" + }, + { + "argv": [ + "/Users/runner/work/_temp/gem-ci/sdist-environment/bin/python", + "-I", + "-m", + "pip", + "install", + "--no-deps", + "--no-build-isolation", + "/Users/runner/work/pyGameMath/pyGameMath/dist/gem-1.0.0.tar.gz" + ], + "cwd": "/Users/runner/work/_temp/gem-ci", + "log": "sdist-install.log" + }, + { + "argv": [ + "/Users/runner/work/_temp/gem-ci/sdist-environment/bin/python", + "-I", + "-X", + "utf8", + "/Users/runner/work/pyGameMath/pyGameMath/tools/verify_distribution.py", + "--installed-root", + "/Users/runner/work/_temp/gem-ci/sdist-environment/lib/python3.13/site-packages", + "--output", + "/Users/runner/work/_temp/gem-ci/sdist-smoke.json" + ], + "cwd": "/Users/runner/work/_temp/gem-ci", + "log": "sdist-smoke.log" + }, + { + "argv": [ + "/Users/runner/work/_temp/gem-ci/sdist-environment/bin/python", + "-I", + "-X", + "utf8", + "/Users/runner/work/pyGameMath/pyGameMath/tools/check_architecture_docs.py", + "--examples", + "--getting-started", + "--api-reference", + "--tutorials", + "--showcase", + "--website", + "--package-root", + "/Users/runner/work/_temp/gem-ci/sdist-environment/lib/python3.13/site-packages", + "--output", + "/Users/runner/work/_temp/gem-ci/sdist-docs.json" + ], + "cwd": "/Users/runner/work/_temp/gem-ci", + "log": "sdist-docs.log" + } + ] + }, + { + "name": "gem-checks-Linux-py3.14-38014899622", + "artifact_id": 11655404847, + "artifact_digest": "sha256:3bff1db3cadeb93c6caf24dea1ca45b8aa1eff9acf944738ab6bf9f5a8b87eb1", + "python": "3.14.8", + "platform": "Linux-6.17.0-1022-azure-x86_64-with-glibc2.39", + "status": "passed", + "tests": { + "tests": 4036, + "failures": 0, + "errors": 0, + "skipped": 0, + "passed": 4036, + "xfailed": 0 + }, + "failed_tests": [], + "installed": { + "wheel": { + "smoke": { + "installed": { + "python": "3.14.8", + "implementation": "CPython", + "platform": "Linux-6.17.0-1022-azure-x86_64-with-glibc2.39", + "package_root": "/home/runner/work/_temp/gem-ci/wheel-environment/lib/python3.14/site-packages", + "version": "1.0.0", + "runtime_files_matched": 18, + "smoke": "passed" + } + }, + "api_declarations": 268, + "examples": 43 + }, + "sdist": { + "smoke": { + "installed": { + "python": "3.14.8", + "implementation": "CPython", + "platform": "Linux-6.17.0-1022-azure-x86_64-with-glibc2.39", + "package_root": "/home/runner/work/_temp/gem-ci/sdist-environment/lib/python3.14/site-packages", + "version": "1.0.0", + "runtime_files_matched": 18, + "smoke": "passed" + } + }, + "api_declarations": 268, + "examples": 43 + } + }, + "api_declarations": 268, + "examples": 43, + "artifacts": { + "metadata": { + "Metadata-Version": "2.4", + "Name": "gem", + "Version": "1.0.0", + "Summary": "Pure-Python mathematics for games and graphics", + "Author-email": "Alex Marinescu ", + "License-Expression": "BSD-2-Clause", + "Project-URL": "Issues, https://github.com/AlexMarinescu/pyGameMath/issues", + "Keywords": "math,game,graphics,library", + "Classifier": "Topic :: Software Development :: Libraries", + "Requires-Python": ">=3.10", + "Description-Content-Type": "text/markdown", + "License-File": "LICENSE", + "Requires-Dist": "six", + "Dynamic": "license-file" + }, + "runtime_files": 18 + }, + "commands": [ + { + "argv": [ + "/opt/hostedtoolcache/Python/3.14.8/x64/bin/python", + "-m", + "pytest", + "-q", + "-o", + "junit_family=legacy", + "--junitxml", + "/home/runner/work/_temp/gem-ci/pytest.xml" + ], + "cwd": "/home/runner/work/pyGameMath/pyGameMath", + "log": "pytest.log" + }, + { + "argv": [ + "/opt/hostedtoolcache/Python/3.14.8/x64/bin/python", + "/home/runner/work/pyGameMath/pyGameMath/tools/check_architecture_docs.py", + "--examples", + "--getting-started", + "--api-reference", + "--tutorials", + "--showcase", + "--website", + "--output", + "/home/runner/work/_temp/gem-ci/source-docs.json" + ], + "cwd": "/home/runner/work/pyGameMath/pyGameMath", + "log": "source-docs.log" + }, + { + "argv": [ + "/opt/hostedtoolcache/Python/3.14.8/x64/bin/python", + "-m", + "build", + "--outdir", + "/home/runner/work/pyGameMath/pyGameMath/dist" + ], + "cwd": "/home/runner/work/pyGameMath/pyGameMath", + "log": "build.log" + }, + { + "argv": [ + "/opt/hostedtoolcache/Python/3.14.8/x64/bin/python", + "-m", + "twine", + "check", + "--strict", + "/home/runner/work/pyGameMath/pyGameMath/dist/gem-1.0.0-py3-none-any.whl", + "/home/runner/work/pyGameMath/pyGameMath/dist/gem-1.0.0.tar.gz" + ], + "cwd": "/home/runner/work/pyGameMath/pyGameMath", + "log": "twine.log" + }, + { + "argv": [ + "/opt/hostedtoolcache/Python/3.14.8/x64/bin/python", + "/home/runner/work/pyGameMath/pyGameMath/tools/verify_distribution.py", + "--wheel", + "/home/runner/work/pyGameMath/pyGameMath/dist/gem-1.0.0-py3-none-any.whl", + "--sdist", + "/home/runner/work/pyGameMath/pyGameMath/dist/gem-1.0.0.tar.gz", + "--output", + "/home/runner/work/_temp/gem-ci/artifacts.json" + ], + "cwd": "/home/runner/work/pyGameMath/pyGameMath", + "log": "artifacts.log" + }, + { + "argv": [ + "/home/runner/work/_temp/gem-ci/wheel-environment/bin/python", + "-m", + "pip", + "install", + "six==1.17.0", + "setuptools==80.9.0", + "wheel==0.48.0" + ], + "cwd": "/home/runner/work/_temp/gem-ci", + "log": "wheel-dependencies.log" + }, + { + "argv": [ + "/home/runner/work/_temp/gem-ci/wheel-environment/bin/python", + "-I", + "-m", + "pip", + "install", + "--no-deps", + "--no-build-isolation", + "/home/runner/work/pyGameMath/pyGameMath/dist/gem-1.0.0-py3-none-any.whl" + ], + "cwd": "/home/runner/work/_temp/gem-ci", + "log": "wheel-install.log" + }, + { + "argv": [ + "/home/runner/work/_temp/gem-ci/wheel-environment/bin/python", + "-I", + "-X", + "utf8", + "/home/runner/work/pyGameMath/pyGameMath/tools/verify_distribution.py", + "--installed-root", + "/home/runner/work/_temp/gem-ci/wheel-environment/lib/python3.14/site-packages", + "--output", + "/home/runner/work/_temp/gem-ci/wheel-smoke.json" + ], + "cwd": "/home/runner/work/_temp/gem-ci", + "log": "wheel-smoke.log" + }, + { + "argv": [ + "/home/runner/work/_temp/gem-ci/wheel-environment/bin/python", + "-I", + "-X", + "utf8", + "/home/runner/work/pyGameMath/pyGameMath/tools/check_architecture_docs.py", + "--examples", + "--getting-started", + "--api-reference", + "--tutorials", + "--showcase", + "--website", + "--package-root", + "/home/runner/work/_temp/gem-ci/wheel-environment/lib/python3.14/site-packages", + "--output", + "/home/runner/work/_temp/gem-ci/wheel-docs.json" + ], + "cwd": "/home/runner/work/_temp/gem-ci", + "log": "wheel-docs.log" + }, + { + "argv": [ + "/home/runner/work/_temp/gem-ci/sdist-environment/bin/python", + "-m", + "pip", + "install", + "six==1.17.0", + "setuptools==80.9.0", + "wheel==0.48.0" + ], + "cwd": "/home/runner/work/_temp/gem-ci", + "log": "sdist-dependencies.log" + }, + { + "argv": [ + "/home/runner/work/_temp/gem-ci/sdist-environment/bin/python", + "-I", + "-m", + "pip", + "install", + "--no-deps", + "--no-build-isolation", + "/home/runner/work/pyGameMath/pyGameMath/dist/gem-1.0.0.tar.gz" + ], + "cwd": "/home/runner/work/_temp/gem-ci", + "log": "sdist-install.log" + }, + { + "argv": [ + "/home/runner/work/_temp/gem-ci/sdist-environment/bin/python", + "-I", + "-X", + "utf8", + "/home/runner/work/pyGameMath/pyGameMath/tools/verify_distribution.py", + "--installed-root", + "/home/runner/work/_temp/gem-ci/sdist-environment/lib/python3.14/site-packages", + "--output", + "/home/runner/work/_temp/gem-ci/sdist-smoke.json" + ], + "cwd": "/home/runner/work/_temp/gem-ci", + "log": "sdist-smoke.log" + }, + { + "argv": [ + "/home/runner/work/_temp/gem-ci/sdist-environment/bin/python", + "-I", + "-X", + "utf8", + "/home/runner/work/pyGameMath/pyGameMath/tools/check_architecture_docs.py", + "--examples", + "--getting-started", + "--api-reference", + "--tutorials", + "--showcase", + "--website", + "--package-root", + "/home/runner/work/_temp/gem-ci/sdist-environment/lib/python3.14/site-packages", + "--output", + "/home/runner/work/_temp/gem-ci/sdist-docs.json" + ], + "cwd": "/home/runner/work/_temp/gem-ci", + "log": "sdist-docs.log" + } + ] + }, + { + "name": "gem-checks-Windows-py3.11-38014899622", + "artifact_id": 11655354985, + "artifact_digest": "sha256:8ae28d481315489789d46c952808555f88504f8d48238cb69a750554b8a3d072", + "python": "3.11.9", + "platform": "Windows-10-10.0.26100-SP0", + "status": "failed", + "tests": { + "tests": 4036, + "failures": 1, + "errors": 0, + "skipped": 0, + "passed": 4035, + "xfailed": 0 + }, + "failed_tests": [ + "tests.test_showcase_examples::test_committed_artifacts_match_generation" + ], + "installed": { + "wheel": { + "smoke": { + "installed": { + "python": "3.11.9", + "implementation": "CPython", + "platform": "Windows-10-10.0.26100-SP0", + "package_root": "D:\\a\\_temp\\gem-ci\\wheel-environment\\Lib\\site-packages", + "version": "1.0.0", + "runtime_files_matched": 18, + "smoke": "passed" + } + }, + "api_declarations": 268, + "examples": 43 + }, + "sdist": { + "smoke": { + "installed": { + "python": "3.11.9", + "implementation": "CPython", + "platform": "Windows-10-10.0.26100-SP0", + "package_root": "D:\\a\\_temp\\gem-ci\\sdist-environment\\Lib\\site-packages", + "version": "1.0.0", + "runtime_files_matched": 18, + "smoke": "passed" + } + }, + "api_declarations": 268, + "examples": 43 + } + }, + "api_declarations": 268, + "examples": 43, + "artifacts": { + "metadata": { + "Metadata-Version": "2.4", + "Name": "gem", + "Version": "1.0.0", + "Summary": "Pure-Python mathematics for games and graphics", + "Author-email": "Alex Marinescu ", + "License-Expression": "BSD-2-Clause", + "Project-URL": "Issues, https://github.com/AlexMarinescu/pyGameMath/issues", + "Keywords": "math,game,graphics,library", + "Classifier": "Topic :: Software Development :: Libraries", + "Requires-Python": ">=3.10", + "Description-Content-Type": "text/markdown", + "License-File": "LICENSE", + "Requires-Dist": "six", + "Dynamic": "license-file" + }, + "runtime_files": 18 + }, + "commands": [ + { + "argv": [ + "C:\\hostedtoolcache\\windows\\Python\\3.11.9\\x64\\python.exe", + "-m", + "pytest", + "-q", + "-o", + "junit_family=legacy", + "--junitxml", + "D:\\a\\_temp\\gem-ci\\pytest.xml" + ], + "cwd": "D:\\a\\pyGameMath\\pyGameMath", + "log": "pytest.log" + }, + { + "argv": [ + "C:\\hostedtoolcache\\windows\\Python\\3.11.9\\x64\\python.exe", + "D:\\a\\pyGameMath\\pyGameMath\\tools\\check_architecture_docs.py", + "--examples", + "--getting-started", + "--api-reference", + "--tutorials", + "--showcase", + "--website", + "--output", + "D:\\a\\_temp\\gem-ci\\source-docs.json" + ], + "cwd": "D:\\a\\pyGameMath\\pyGameMath", + "log": "source-docs.log" + }, + { + "argv": [ + "C:\\hostedtoolcache\\windows\\Python\\3.11.9\\x64\\python.exe", + "-m", + "build", + "--outdir", + "D:\\a\\pyGameMath\\pyGameMath\\dist" + ], + "cwd": "D:\\a\\pyGameMath\\pyGameMath", + "log": "build.log" + }, + { + "argv": [ + "C:\\hostedtoolcache\\windows\\Python\\3.11.9\\x64\\python.exe", + "-m", + "twine", + "check", + "--strict", + "D:\\a\\pyGameMath\\pyGameMath\\dist\\gem-1.0.0-py3-none-any.whl", + "D:\\a\\pyGameMath\\pyGameMath\\dist\\gem-1.0.0.tar.gz" + ], + "cwd": "D:\\a\\pyGameMath\\pyGameMath", + "log": "twine.log" + }, + { + "argv": [ + "C:\\hostedtoolcache\\windows\\Python\\3.11.9\\x64\\python.exe", + "D:\\a\\pyGameMath\\pyGameMath\\tools\\verify_distribution.py", + "--wheel", + "D:\\a\\pyGameMath\\pyGameMath\\dist\\gem-1.0.0-py3-none-any.whl", + "--sdist", + "D:\\a\\pyGameMath\\pyGameMath\\dist\\gem-1.0.0.tar.gz", + "--output", + "D:\\a\\_temp\\gem-ci\\artifacts.json" + ], + "cwd": "D:\\a\\pyGameMath\\pyGameMath", + "log": "artifacts.log" + }, + { + "argv": [ + "D:\\a\\_temp\\gem-ci\\wheel-environment\\Scripts\\python.exe", + "-m", + "pip", + "install", + "six==1.17.0", + "setuptools==80.9.0", + "wheel==0.48.0" + ], + "cwd": "D:\\a\\_temp\\gem-ci", + "log": "wheel-dependencies.log" + }, + { + "argv": [ + "D:\\a\\_temp\\gem-ci\\wheel-environment\\Scripts\\python.exe", + "-I", + "-m", + "pip", + "install", + "--no-deps", + "--no-build-isolation", + "D:\\a\\pyGameMath\\pyGameMath\\dist\\gem-1.0.0-py3-none-any.whl" + ], + "cwd": "D:\\a\\_temp\\gem-ci", + "log": "wheel-install.log" + }, + { + "argv": [ + "D:\\a\\_temp\\gem-ci\\wheel-environment\\Scripts\\python.exe", + "-I", + "-X", + "utf8", + "D:\\a\\pyGameMath\\pyGameMath\\tools\\verify_distribution.py", + "--installed-root", + "D:\\a\\_temp\\gem-ci\\wheel-environment\\Lib\\site-packages", + "--output", + "D:\\a\\_temp\\gem-ci\\wheel-smoke.json" + ], + "cwd": "D:\\a\\_temp\\gem-ci", + "log": "wheel-smoke.log" + }, + { + "argv": [ + "D:\\a\\_temp\\gem-ci\\wheel-environment\\Scripts\\python.exe", + "-I", + "-X", + "utf8", + "D:\\a\\pyGameMath\\pyGameMath\\tools\\check_architecture_docs.py", + "--examples", + "--getting-started", + "--api-reference", + "--tutorials", + "--showcase", + "--website", + "--package-root", + "D:\\a\\_temp\\gem-ci\\wheel-environment\\Lib\\site-packages", + "--output", + "D:\\a\\_temp\\gem-ci\\wheel-docs.json" + ], + "cwd": "D:\\a\\_temp\\gem-ci", + "log": "wheel-docs.log" + }, + { + "argv": [ + "D:\\a\\_temp\\gem-ci\\sdist-environment\\Scripts\\python.exe", + "-m", + "pip", + "install", + "six==1.17.0", + "setuptools==80.9.0", + "wheel==0.48.0" + ], + "cwd": "D:\\a\\_temp\\gem-ci", + "log": "sdist-dependencies.log" + }, + { + "argv": [ + "D:\\a\\_temp\\gem-ci\\sdist-environment\\Scripts\\python.exe", + "-I", + "-m", + "pip", + "install", + "--no-deps", + "--no-build-isolation", + "D:\\a\\pyGameMath\\pyGameMath\\dist\\gem-1.0.0.tar.gz" + ], + "cwd": "D:\\a\\_temp\\gem-ci", + "log": "sdist-install.log" + }, + { + "argv": [ + "D:\\a\\_temp\\gem-ci\\sdist-environment\\Scripts\\python.exe", + "-I", + "-X", + "utf8", + "D:\\a\\pyGameMath\\pyGameMath\\tools\\verify_distribution.py", + "--installed-root", + "D:\\a\\_temp\\gem-ci\\sdist-environment\\Lib\\site-packages", + "--output", + "D:\\a\\_temp\\gem-ci\\sdist-smoke.json" + ], + "cwd": "D:\\a\\_temp\\gem-ci", + "log": "sdist-smoke.log" + }, + { + "argv": [ + "D:\\a\\_temp\\gem-ci\\sdist-environment\\Scripts\\python.exe", + "-I", + "-X", + "utf8", + "D:\\a\\pyGameMath\\pyGameMath\\tools\\check_architecture_docs.py", + "--examples", + "--getting-started", + "--api-reference", + "--tutorials", + "--showcase", + "--website", + "--package-root", + "D:\\a\\_temp\\gem-ci\\sdist-environment\\Lib\\site-packages", + "--output", + "D:\\a\\_temp\\gem-ci\\sdist-docs.json" + ], + "cwd": "D:\\a\\_temp\\gem-ci", + "log": "sdist-docs.log" + } + ] + }, + { + "name": "gem-checks-Windows-py3.14-38014899622", + "artifact_id": 11655339722, + "artifact_digest": "sha256:262693fd50796898a979d55ee6375d40b815c82e57493a714f342c7351298bce", + "python": "3.14.7", + "platform": "Windows-2025Server-10.0.26100-SP0", + "status": "failed", + "tests": { + "tests": 4036, + "failures": 1, + "errors": 0, + "skipped": 0, + "passed": 4035, + "xfailed": 0 + }, + "failed_tests": [ + "tests.test_showcase_examples::test_committed_artifacts_match_generation" + ], + "installed": { + "wheel": { + "smoke": { + "installed": { + "python": "3.14.7", + "implementation": "CPython", + "platform": "Windows-2025Server-10.0.26100-SP0", + "package_root": "D:\\a\\_temp\\gem-ci\\wheel-environment\\Lib\\site-packages", + "version": "1.0.0", + "runtime_files_matched": 18, + "smoke": "passed" + } + }, + "api_declarations": 268, + "examples": 43 + }, + "sdist": { + "smoke": { + "installed": { + "python": "3.14.7", + "implementation": "CPython", + "platform": "Windows-2025Server-10.0.26100-SP0", + "package_root": "D:\\a\\_temp\\gem-ci\\sdist-environment\\Lib\\site-packages", + "version": "1.0.0", + "runtime_files_matched": 18, + "smoke": "passed" + } + }, + "api_declarations": 268, + "examples": 43 + } + }, + "api_declarations": 268, + "examples": 43, + "artifacts": { + "metadata": { + "Metadata-Version": "2.4", + "Name": "gem", + "Version": "1.0.0", + "Summary": "Pure-Python mathematics for games and graphics", + "Author-email": "Alex Marinescu ", + "License-Expression": "BSD-2-Clause", + "Project-URL": "Issues, https://github.com/AlexMarinescu/pyGameMath/issues", + "Keywords": "math,game,graphics,library", + "Classifier": "Topic :: Software Development :: Libraries", + "Requires-Python": ">=3.10", + "Description-Content-Type": "text/markdown", + "License-File": "LICENSE", + "Requires-Dist": "six", + "Dynamic": "license-file" + }, + "runtime_files": 18 + }, + "commands": [ + { + "argv": [ + "C:\\hostedtoolcache\\windows\\Python\\3.14.7\\x64\\python.exe", + "-m", + "pytest", + "-q", + "-o", + "junit_family=legacy", + "--junitxml", + "D:\\a\\_temp\\gem-ci\\pytest.xml" + ], + "cwd": "D:\\a\\pyGameMath\\pyGameMath", + "log": "pytest.log" + }, + { + "argv": [ + "C:\\hostedtoolcache\\windows\\Python\\3.14.7\\x64\\python.exe", + "D:\\a\\pyGameMath\\pyGameMath\\tools\\check_architecture_docs.py", + "--examples", + "--getting-started", + "--api-reference", + "--tutorials", + "--showcase", + "--website", + "--output", + "D:\\a\\_temp\\gem-ci\\source-docs.json" + ], + "cwd": "D:\\a\\pyGameMath\\pyGameMath", + "log": "source-docs.log" + }, + { + "argv": [ + "C:\\hostedtoolcache\\windows\\Python\\3.14.7\\x64\\python.exe", + "-m", + "build", + "--outdir", + "D:\\a\\pyGameMath\\pyGameMath\\dist" + ], + "cwd": "D:\\a\\pyGameMath\\pyGameMath", + "log": "build.log" + }, + { + "argv": [ + "C:\\hostedtoolcache\\windows\\Python\\3.14.7\\x64\\python.exe", + "-m", + "twine", + "check", + "--strict", + "D:\\a\\pyGameMath\\pyGameMath\\dist\\gem-1.0.0-py3-none-any.whl", + "D:\\a\\pyGameMath\\pyGameMath\\dist\\gem-1.0.0.tar.gz" + ], + "cwd": "D:\\a\\pyGameMath\\pyGameMath", + "log": "twine.log" + }, + { + "argv": [ + "C:\\hostedtoolcache\\windows\\Python\\3.14.7\\x64\\python.exe", + "D:\\a\\pyGameMath\\pyGameMath\\tools\\verify_distribution.py", + "--wheel", + "D:\\a\\pyGameMath\\pyGameMath\\dist\\gem-1.0.0-py3-none-any.whl", + "--sdist", + "D:\\a\\pyGameMath\\pyGameMath\\dist\\gem-1.0.0.tar.gz", + "--output", + "D:\\a\\_temp\\gem-ci\\artifacts.json" + ], + "cwd": "D:\\a\\pyGameMath\\pyGameMath", + "log": "artifacts.log" + }, + { + "argv": [ + "D:\\a\\_temp\\gem-ci\\wheel-environment\\Scripts\\python.exe", + "-m", + "pip", + "install", + "six==1.17.0", + "setuptools==80.9.0", + "wheel==0.48.0" + ], + "cwd": "D:\\a\\_temp\\gem-ci", + "log": "wheel-dependencies.log" + }, + { + "argv": [ + "D:\\a\\_temp\\gem-ci\\wheel-environment\\Scripts\\python.exe", + "-I", + "-m", + "pip", + "install", + "--no-deps", + "--no-build-isolation", + "D:\\a\\pyGameMath\\pyGameMath\\dist\\gem-1.0.0-py3-none-any.whl" + ], + "cwd": "D:\\a\\_temp\\gem-ci", + "log": "wheel-install.log" + }, + { + "argv": [ + "D:\\a\\_temp\\gem-ci\\wheel-environment\\Scripts\\python.exe", + "-I", + "-X", + "utf8", + "D:\\a\\pyGameMath\\pyGameMath\\tools\\verify_distribution.py", + "--installed-root", + "D:\\a\\_temp\\gem-ci\\wheel-environment\\Lib\\site-packages", + "--output", + "D:\\a\\_temp\\gem-ci\\wheel-smoke.json" + ], + "cwd": "D:\\a\\_temp\\gem-ci", + "log": "wheel-smoke.log" + }, + { + "argv": [ + "D:\\a\\_temp\\gem-ci\\wheel-environment\\Scripts\\python.exe", + "-I", + "-X", + "utf8", + "D:\\a\\pyGameMath\\pyGameMath\\tools\\check_architecture_docs.py", + "--examples", + "--getting-started", + "--api-reference", + "--tutorials", + "--showcase", + "--website", + "--package-root", + "D:\\a\\_temp\\gem-ci\\wheel-environment\\Lib\\site-packages", + "--output", + "D:\\a\\_temp\\gem-ci\\wheel-docs.json" + ], + "cwd": "D:\\a\\_temp\\gem-ci", + "log": "wheel-docs.log" + }, + { + "argv": [ + "D:\\a\\_temp\\gem-ci\\sdist-environment\\Scripts\\python.exe", + "-m", + "pip", + "install", + "six==1.17.0", + "setuptools==80.9.0", + "wheel==0.48.0" + ], + "cwd": "D:\\a\\_temp\\gem-ci", + "log": "sdist-dependencies.log" + }, + { + "argv": [ + "D:\\a\\_temp\\gem-ci\\sdist-environment\\Scripts\\python.exe", + "-I", + "-m", + "pip", + "install", + "--no-deps", + "--no-build-isolation", + "D:\\a\\pyGameMath\\pyGameMath\\dist\\gem-1.0.0.tar.gz" + ], + "cwd": "D:\\a\\_temp\\gem-ci", + "log": "sdist-install.log" + }, + { + "argv": [ + "D:\\a\\_temp\\gem-ci\\sdist-environment\\Scripts\\python.exe", + "-I", + "-X", + "utf8", + "D:\\a\\pyGameMath\\pyGameMath\\tools\\verify_distribution.py", + "--installed-root", + "D:\\a\\_temp\\gem-ci\\sdist-environment\\Lib\\site-packages", + "--output", + "D:\\a\\_temp\\gem-ci\\sdist-smoke.json" + ], + "cwd": "D:\\a\\_temp\\gem-ci", + "log": "sdist-smoke.log" + }, + { + "argv": [ + "D:\\a\\_temp\\gem-ci\\sdist-environment\\Scripts\\python.exe", + "-I", + "-X", + "utf8", + "D:\\a\\pyGameMath\\pyGameMath\\tools\\check_architecture_docs.py", + "--examples", + "--getting-started", + "--api-reference", + "--tutorials", + "--showcase", + "--website", + "--package-root", + "D:\\a\\_temp\\gem-ci\\sdist-environment\\Lib\\site-packages", + "--output", + "D:\\a\\_temp\\gem-ci\\sdist-docs.json" + ], + "cwd": "D:\\a\\_temp\\gem-ci", + "log": "sdist-docs.log" + } + ] + }, + { + "name": "gem-checks-macOS-py3.14-38014899622", + "artifact_id": 11655314638, + "artifact_digest": "sha256:6b80f50af290c324871f5355d8957961b93d4dc15c3e4d27a14985a80a17b63c", + "python": "3.14.7", + "platform": "macOS-26.6.2-arm64-arm-64bit-Mach-O", + "status": "passed", + "tests": { + "tests": 4036, + "failures": 0, + "errors": 0, + "skipped": 0, + "passed": 4036, + "xfailed": 0 + }, + "failed_tests": [], + "installed": { + "wheel": { + "smoke": { + "installed": { + "python": "3.14.7", + "implementation": "CPython", + "platform": "macOS-26.6.2-arm64-arm-64bit-Mach-O", + "package_root": "/Users/runner/work/_temp/gem-ci/wheel-environment/lib/python3.14/site-packages", + "version": "1.0.0", + "runtime_files_matched": 18, + "smoke": "passed" + } + }, + "api_declarations": 268, + "examples": 43 + }, + "sdist": { + "smoke": { + "installed": { + "python": "3.14.7", + "implementation": "CPython", + "platform": "macOS-26.6.2-arm64-arm-64bit-Mach-O", + "package_root": "/Users/runner/work/_temp/gem-ci/sdist-environment/lib/python3.14/site-packages", + "version": "1.0.0", + "runtime_files_matched": 18, + "smoke": "passed" + } + }, + "api_declarations": 268, + "examples": 43 + } + }, + "api_declarations": 268, + "examples": 43, + "artifacts": { + "metadata": { + "Metadata-Version": "2.4", + "Name": "gem", + "Version": "1.0.0", + "Summary": "Pure-Python mathematics for games and graphics", + "Author-email": "Alex Marinescu ", + "License-Expression": "BSD-2-Clause", + "Project-URL": "Issues, https://github.com/AlexMarinescu/pyGameMath/issues", + "Keywords": "math,game,graphics,library", + "Classifier": "Topic :: Software Development :: Libraries", + "Requires-Python": ">=3.10", + "Description-Content-Type": "text/markdown", + "License-File": "LICENSE", + "Requires-Dist": "six", + "Dynamic": "license-file" + }, + "runtime_files": 18 + }, + "commands": [ + { + "argv": [ + "/Library/Frameworks/Python.framework/Versions/3.14/bin/python", + "-m", + "pytest", + "-q", + "-o", + "junit_family=legacy", + "--junitxml", + "/Users/runner/work/_temp/gem-ci/pytest.xml" + ], + "cwd": "/Users/runner/work/pyGameMath/pyGameMath", + "log": "pytest.log" + }, + { + "argv": [ + "/Library/Frameworks/Python.framework/Versions/3.14/bin/python", + "/Users/runner/work/pyGameMath/pyGameMath/tools/check_architecture_docs.py", + "--examples", + "--getting-started", + "--api-reference", + "--tutorials", + "--showcase", + "--website", + "--output", + "/Users/runner/work/_temp/gem-ci/source-docs.json" + ], + "cwd": "/Users/runner/work/pyGameMath/pyGameMath", + "log": "source-docs.log" + }, + { + "argv": [ + "/Library/Frameworks/Python.framework/Versions/3.14/bin/python", + "-m", + "build", + "--outdir", + "/Users/runner/work/pyGameMath/pyGameMath/dist" + ], + "cwd": "/Users/runner/work/pyGameMath/pyGameMath", + "log": "build.log" + }, + { + "argv": [ + "/Library/Frameworks/Python.framework/Versions/3.14/bin/python", + "-m", + "twine", + "check", + "--strict", + "/Users/runner/work/pyGameMath/pyGameMath/dist/gem-1.0.0-py3-none-any.whl", + "/Users/runner/work/pyGameMath/pyGameMath/dist/gem-1.0.0.tar.gz" + ], + "cwd": "/Users/runner/work/pyGameMath/pyGameMath", + "log": "twine.log" + }, + { + "argv": [ + "/Library/Frameworks/Python.framework/Versions/3.14/bin/python", + "/Users/runner/work/pyGameMath/pyGameMath/tools/verify_distribution.py", + "--wheel", + "/Users/runner/work/pyGameMath/pyGameMath/dist/gem-1.0.0-py3-none-any.whl", + "--sdist", + "/Users/runner/work/pyGameMath/pyGameMath/dist/gem-1.0.0.tar.gz", + "--output", + "/Users/runner/work/_temp/gem-ci/artifacts.json" + ], + "cwd": "/Users/runner/work/pyGameMath/pyGameMath", + "log": "artifacts.log" + }, + { + "argv": [ + "/Users/runner/work/_temp/gem-ci/wheel-environment/bin/python", + "-m", + "pip", + "install", + "six==1.17.0", + "setuptools==80.9.0", + "wheel==0.48.0" + ], + "cwd": "/Users/runner/work/_temp/gem-ci", + "log": "wheel-dependencies.log" + }, + { + "argv": [ + "/Users/runner/work/_temp/gem-ci/wheel-environment/bin/python", + "-I", + "-m", + "pip", + "install", + "--no-deps", + "--no-build-isolation", + "/Users/runner/work/pyGameMath/pyGameMath/dist/gem-1.0.0-py3-none-any.whl" + ], + "cwd": "/Users/runner/work/_temp/gem-ci", + "log": "wheel-install.log" + }, + { + "argv": [ + "/Users/runner/work/_temp/gem-ci/wheel-environment/bin/python", + "-I", + "-X", + "utf8", + "/Users/runner/work/pyGameMath/pyGameMath/tools/verify_distribution.py", + "--installed-root", + "/Users/runner/work/_temp/gem-ci/wheel-environment/lib/python3.14/site-packages", + "--output", + "/Users/runner/work/_temp/gem-ci/wheel-smoke.json" + ], + "cwd": "/Users/runner/work/_temp/gem-ci", + "log": "wheel-smoke.log" + }, + { + "argv": [ + "/Users/runner/work/_temp/gem-ci/wheel-environment/bin/python", + "-I", + "-X", + "utf8", + "/Users/runner/work/pyGameMath/pyGameMath/tools/check_architecture_docs.py", + "--examples", + "--getting-started", + "--api-reference", + "--tutorials", + "--showcase", + "--website", + "--package-root", + "/Users/runner/work/_temp/gem-ci/wheel-environment/lib/python3.14/site-packages", + "--output", + "/Users/runner/work/_temp/gem-ci/wheel-docs.json" + ], + "cwd": "/Users/runner/work/_temp/gem-ci", + "log": "wheel-docs.log" + }, + { + "argv": [ + "/Users/runner/work/_temp/gem-ci/sdist-environment/bin/python", + "-m", + "pip", + "install", + "six==1.17.0", + "setuptools==80.9.0", + "wheel==0.48.0" + ], + "cwd": "/Users/runner/work/_temp/gem-ci", + "log": "sdist-dependencies.log" + }, + { + "argv": [ + "/Users/runner/work/_temp/gem-ci/sdist-environment/bin/python", + "-I", + "-m", + "pip", + "install", + "--no-deps", + "--no-build-isolation", + "/Users/runner/work/pyGameMath/pyGameMath/dist/gem-1.0.0.tar.gz" + ], + "cwd": "/Users/runner/work/_temp/gem-ci", + "log": "sdist-install.log" + }, + { + "argv": [ + "/Users/runner/work/_temp/gem-ci/sdist-environment/bin/python", + "-I", + "-X", + "utf8", + "/Users/runner/work/pyGameMath/pyGameMath/tools/verify_distribution.py", + "--installed-root", + "/Users/runner/work/_temp/gem-ci/sdist-environment/lib/python3.14/site-packages", + "--output", + "/Users/runner/work/_temp/gem-ci/sdist-smoke.json" + ], + "cwd": "/Users/runner/work/_temp/gem-ci", + "log": "sdist-smoke.log" + }, + { + "argv": [ + "/Users/runner/work/_temp/gem-ci/sdist-environment/bin/python", + "-I", + "-X", + "utf8", + "/Users/runner/work/pyGameMath/pyGameMath/tools/check_architecture_docs.py", + "--examples", + "--getting-started", + "--api-reference", + "--tutorials", + "--showcase", + "--website", + "--package-root", + "/Users/runner/work/_temp/gem-ci/sdist-environment/lib/python3.14/site-packages", + "--output", + "/Users/runner/work/_temp/gem-ci/sdist-docs.json" + ], + "cwd": "/Users/runner/work/_temp/gem-ci", + "log": "sdist-docs.log" + } + ] + }, + { + "name": "gem-checks-Linux-py3.12-38014899622", + "artifact_id": 11655279763, + "artifact_digest": "sha256:23a89da45b199bce308dc80710633a89db36b54820d070144a6df84057047f75", + "python": "3.12.15", + "platform": "Linux-6.17.0-1022-azure-x86_64-with-glibc2.39", + "status": "passed", + "tests": { + "tests": 4036, + "failures": 0, + "errors": 0, + "skipped": 0, + "passed": 4036, + "xfailed": 0 + }, + "failed_tests": [], + "installed": { + "wheel": { + "smoke": { + "installed": { + "python": "3.12.15", + "implementation": "CPython", + "platform": "Linux-6.17.0-1022-azure-x86_64-with-glibc2.39", + "package_root": "/home/runner/work/_temp/gem-ci/wheel-environment/lib/python3.12/site-packages", + "version": "1.0.0", + "runtime_files_matched": 18, + "smoke": "passed" + } + }, + "api_declarations": 268, + "examples": 43 + }, + "sdist": { + "smoke": { + "installed": { + "python": "3.12.15", + "implementation": "CPython", + "platform": "Linux-6.17.0-1022-azure-x86_64-with-glibc2.39", + "package_root": "/home/runner/work/_temp/gem-ci/sdist-environment/lib/python3.12/site-packages", + "version": "1.0.0", + "runtime_files_matched": 18, + "smoke": "passed" + } + }, + "api_declarations": 268, + "examples": 43 + } + }, + "api_declarations": 268, + "examples": 43, + "artifacts": { + "metadata": { + "Metadata-Version": "2.4", + "Name": "gem", + "Version": "1.0.0", + "Summary": "Pure-Python mathematics for games and graphics", + "Author-email": "Alex Marinescu ", + "License-Expression": "BSD-2-Clause", + "Project-URL": "Issues, https://github.com/AlexMarinescu/pyGameMath/issues", + "Keywords": "math,game,graphics,library", + "Classifier": "Topic :: Software Development :: Libraries", + "Requires-Python": ">=3.10", + "Description-Content-Type": "text/markdown", + "License-File": "LICENSE", + "Requires-Dist": "six", + "Dynamic": "license-file" + }, + "runtime_files": 18 + }, + "commands": [ + { + "argv": [ + "/opt/hostedtoolcache/Python/3.12.15/x64/bin/python", + "-m", + "pytest", + "-q", + "-o", + "junit_family=legacy", + "--junitxml", + "/home/runner/work/_temp/gem-ci/pytest.xml" + ], + "cwd": "/home/runner/work/pyGameMath/pyGameMath", + "log": "pytest.log" + }, + { + "argv": [ + "/opt/hostedtoolcache/Python/3.12.15/x64/bin/python", + "/home/runner/work/pyGameMath/pyGameMath/tools/check_architecture_docs.py", + "--examples", + "--getting-started", + "--api-reference", + "--tutorials", + "--showcase", + "--website", + "--output", + "/home/runner/work/_temp/gem-ci/source-docs.json" + ], + "cwd": "/home/runner/work/pyGameMath/pyGameMath", + "log": "source-docs.log" + }, + { + "argv": [ + "/opt/hostedtoolcache/Python/3.12.15/x64/bin/python", + "-m", + "build", + "--outdir", + "/home/runner/work/pyGameMath/pyGameMath/dist" + ], + "cwd": "/home/runner/work/pyGameMath/pyGameMath", + "log": "build.log" + }, + { + "argv": [ + "/opt/hostedtoolcache/Python/3.12.15/x64/bin/python", + "-m", + "twine", + "check", + "--strict", + "/home/runner/work/pyGameMath/pyGameMath/dist/gem-1.0.0-py3-none-any.whl", + "/home/runner/work/pyGameMath/pyGameMath/dist/gem-1.0.0.tar.gz" + ], + "cwd": "/home/runner/work/pyGameMath/pyGameMath", + "log": "twine.log" + }, + { + "argv": [ + "/opt/hostedtoolcache/Python/3.12.15/x64/bin/python", + "/home/runner/work/pyGameMath/pyGameMath/tools/verify_distribution.py", + "--wheel", + "/home/runner/work/pyGameMath/pyGameMath/dist/gem-1.0.0-py3-none-any.whl", + "--sdist", + "/home/runner/work/pyGameMath/pyGameMath/dist/gem-1.0.0.tar.gz", + "--output", + "/home/runner/work/_temp/gem-ci/artifacts.json" + ], + "cwd": "/home/runner/work/pyGameMath/pyGameMath", + "log": "artifacts.log" + }, + { + "argv": [ + "/home/runner/work/_temp/gem-ci/wheel-environment/bin/python", + "-m", + "pip", + "install", + "six==1.17.0", + "setuptools==80.9.0", + "wheel==0.48.0" + ], + "cwd": "/home/runner/work/_temp/gem-ci", + "log": "wheel-dependencies.log" + }, + { + "argv": [ + "/home/runner/work/_temp/gem-ci/wheel-environment/bin/python", + "-I", + "-m", + "pip", + "install", + "--no-deps", + "--no-build-isolation", + "/home/runner/work/pyGameMath/pyGameMath/dist/gem-1.0.0-py3-none-any.whl" + ], + "cwd": "/home/runner/work/_temp/gem-ci", + "log": "wheel-install.log" + }, + { + "argv": [ + "/home/runner/work/_temp/gem-ci/wheel-environment/bin/python", + "-I", + "-X", + "utf8", + "/home/runner/work/pyGameMath/pyGameMath/tools/verify_distribution.py", + "--installed-root", + "/home/runner/work/_temp/gem-ci/wheel-environment/lib/python3.12/site-packages", + "--output", + "/home/runner/work/_temp/gem-ci/wheel-smoke.json" + ], + "cwd": "/home/runner/work/_temp/gem-ci", + "log": "wheel-smoke.log" + }, + { + "argv": [ + "/home/runner/work/_temp/gem-ci/wheel-environment/bin/python", + "-I", + "-X", + "utf8", + "/home/runner/work/pyGameMath/pyGameMath/tools/check_architecture_docs.py", + "--examples", + "--getting-started", + "--api-reference", + "--tutorials", + "--showcase", + "--website", + "--package-root", + "/home/runner/work/_temp/gem-ci/wheel-environment/lib/python3.12/site-packages", + "--output", + "/home/runner/work/_temp/gem-ci/wheel-docs.json" + ], + "cwd": "/home/runner/work/_temp/gem-ci", + "log": "wheel-docs.log" + }, + { + "argv": [ + "/home/runner/work/_temp/gem-ci/sdist-environment/bin/python", + "-m", + "pip", + "install", + "six==1.17.0", + "setuptools==80.9.0", + "wheel==0.48.0" + ], + "cwd": "/home/runner/work/_temp/gem-ci", + "log": "sdist-dependencies.log" + }, + { + "argv": [ + "/home/runner/work/_temp/gem-ci/sdist-environment/bin/python", + "-I", + "-m", + "pip", + "install", + "--no-deps", + "--no-build-isolation", + "/home/runner/work/pyGameMath/pyGameMath/dist/gem-1.0.0.tar.gz" + ], + "cwd": "/home/runner/work/_temp/gem-ci", + "log": "sdist-install.log" + }, + { + "argv": [ + "/home/runner/work/_temp/gem-ci/sdist-environment/bin/python", + "-I", + "-X", + "utf8", + "/home/runner/work/pyGameMath/pyGameMath/tools/verify_distribution.py", + "--installed-root", + "/home/runner/work/_temp/gem-ci/sdist-environment/lib/python3.12/site-packages", + "--output", + "/home/runner/work/_temp/gem-ci/sdist-smoke.json" + ], + "cwd": "/home/runner/work/_temp/gem-ci", + "log": "sdist-smoke.log" + }, + { + "argv": [ + "/home/runner/work/_temp/gem-ci/sdist-environment/bin/python", + "-I", + "-X", + "utf8", + "/home/runner/work/pyGameMath/pyGameMath/tools/check_architecture_docs.py", + "--examples", + "--getting-started", + "--api-reference", + "--tutorials", + "--showcase", + "--website", + "--package-root", + "/home/runner/work/_temp/gem-ci/sdist-environment/lib/python3.12/site-packages", + "--output", + "/home/runner/work/_temp/gem-ci/sdist-docs.json" + ], + "cwd": "/home/runner/work/_temp/gem-ci", + "log": "sdist-docs.log" + } + ] + }, + { + "name": "gem-checks-macOS-py3.12-38014899622", + "artifact_id": 11655134745, + "artifact_digest": "sha256:f169696d7b765ffcc3048f687bcc00d1ce3243c6097d38da35f9267bea7a9717", + "python": "3.12.10", + "platform": "macOS-26.6.2-arm64-arm-64bit", + "status": "passed", + "tests": { + "tests": 4036, + "failures": 0, + "errors": 0, + "skipped": 0, + "passed": 4036, + "xfailed": 0 + }, + "failed_tests": [], + "installed": { + "wheel": { + "smoke": { + "installed": { + "python": "3.12.10", + "implementation": "CPython", + "platform": "macOS-26.6.2-arm64-arm-64bit", + "package_root": "/Users/runner/work/_temp/gem-ci/wheel-environment/lib/python3.12/site-packages", + "version": "1.0.0", + "runtime_files_matched": 18, + "smoke": "passed" + } + }, + "api_declarations": 268, + "examples": 43 + }, + "sdist": { + "smoke": { + "installed": { + "python": "3.12.10", + "implementation": "CPython", + "platform": "macOS-26.6.2-arm64-arm-64bit", + "package_root": "/Users/runner/work/_temp/gem-ci/sdist-environment/lib/python3.12/site-packages", + "version": "1.0.0", + "runtime_files_matched": 18, + "smoke": "passed" + } + }, + "api_declarations": 268, + "examples": 43 + } + }, + "api_declarations": 268, + "examples": 43, + "artifacts": { + "metadata": { + "Metadata-Version": "2.4", + "Name": "gem", + "Version": "1.0.0", + "Summary": "Pure-Python mathematics for games and graphics", + "Author-email": "Alex Marinescu ", + "License-Expression": "BSD-2-Clause", + "Project-URL": "Issues, https://github.com/AlexMarinescu/pyGameMath/issues", + "Keywords": "math,game,graphics,library", + "Classifier": "Topic :: Software Development :: Libraries", + "Requires-Python": ">=3.10", + "Description-Content-Type": "text/markdown", + "License-File": "LICENSE", + "Requires-Dist": "six", + "Dynamic": "license-file" + }, + "runtime_files": 18 + }, + "commands": [ + { + "argv": [ + "/Library/Frameworks/Python.framework/Versions/3.12/bin/python", + "-m", + "pytest", + "-q", + "-o", + "junit_family=legacy", + "--junitxml", + "/Users/runner/work/_temp/gem-ci/pytest.xml" + ], + "cwd": "/Users/runner/work/pyGameMath/pyGameMath", + "log": "pytest.log" + }, + { + "argv": [ + "/Library/Frameworks/Python.framework/Versions/3.12/bin/python", + "/Users/runner/work/pyGameMath/pyGameMath/tools/check_architecture_docs.py", + "--examples", + "--getting-started", + "--api-reference", + "--tutorials", + "--showcase", + "--website", + "--output", + "/Users/runner/work/_temp/gem-ci/source-docs.json" + ], + "cwd": "/Users/runner/work/pyGameMath/pyGameMath", + "log": "source-docs.log" + }, + { + "argv": [ + "/Library/Frameworks/Python.framework/Versions/3.12/bin/python", + "-m", + "build", + "--outdir", + "/Users/runner/work/pyGameMath/pyGameMath/dist" + ], + "cwd": "/Users/runner/work/pyGameMath/pyGameMath", + "log": "build.log" + }, + { + "argv": [ + "/Library/Frameworks/Python.framework/Versions/3.12/bin/python", + "-m", + "twine", + "check", + "--strict", + "/Users/runner/work/pyGameMath/pyGameMath/dist/gem-1.0.0-py3-none-any.whl", + "/Users/runner/work/pyGameMath/pyGameMath/dist/gem-1.0.0.tar.gz" + ], + "cwd": "/Users/runner/work/pyGameMath/pyGameMath", + "log": "twine.log" + }, + { + "argv": [ + "/Library/Frameworks/Python.framework/Versions/3.12/bin/python", + "/Users/runner/work/pyGameMath/pyGameMath/tools/verify_distribution.py", + "--wheel", + "/Users/runner/work/pyGameMath/pyGameMath/dist/gem-1.0.0-py3-none-any.whl", + "--sdist", + "/Users/runner/work/pyGameMath/pyGameMath/dist/gem-1.0.0.tar.gz", + "--output", + "/Users/runner/work/_temp/gem-ci/artifacts.json" + ], + "cwd": "/Users/runner/work/pyGameMath/pyGameMath", + "log": "artifacts.log" + }, + { + "argv": [ + "/Users/runner/work/_temp/gem-ci/wheel-environment/bin/python", + "-m", + "pip", + "install", + "six==1.17.0", + "setuptools==80.9.0", + "wheel==0.48.0" + ], + "cwd": "/Users/runner/work/_temp/gem-ci", + "log": "wheel-dependencies.log" + }, + { + "argv": [ + "/Users/runner/work/_temp/gem-ci/wheel-environment/bin/python", + "-I", + "-m", + "pip", + "install", + "--no-deps", + "--no-build-isolation", + "/Users/runner/work/pyGameMath/pyGameMath/dist/gem-1.0.0-py3-none-any.whl" + ], + "cwd": "/Users/runner/work/_temp/gem-ci", + "log": "wheel-install.log" + }, + { + "argv": [ + "/Users/runner/work/_temp/gem-ci/wheel-environment/bin/python", + "-I", + "-X", + "utf8", + "/Users/runner/work/pyGameMath/pyGameMath/tools/verify_distribution.py", + "--installed-root", + "/Users/runner/work/_temp/gem-ci/wheel-environment/lib/python3.12/site-packages", + "--output", + "/Users/runner/work/_temp/gem-ci/wheel-smoke.json" + ], + "cwd": "/Users/runner/work/_temp/gem-ci", + "log": "wheel-smoke.log" + }, + { + "argv": [ + "/Users/runner/work/_temp/gem-ci/wheel-environment/bin/python", + "-I", + "-X", + "utf8", + "/Users/runner/work/pyGameMath/pyGameMath/tools/check_architecture_docs.py", + "--examples", + "--getting-started", + "--api-reference", + "--tutorials", + "--showcase", + "--website", + "--package-root", + "/Users/runner/work/_temp/gem-ci/wheel-environment/lib/python3.12/site-packages", + "--output", + "/Users/runner/work/_temp/gem-ci/wheel-docs.json" + ], + "cwd": "/Users/runner/work/_temp/gem-ci", + "log": "wheel-docs.log" + }, + { + "argv": [ + "/Users/runner/work/_temp/gem-ci/sdist-environment/bin/python", + "-m", + "pip", + "install", + "six==1.17.0", + "setuptools==80.9.0", + "wheel==0.48.0" + ], + "cwd": "/Users/runner/work/_temp/gem-ci", + "log": "sdist-dependencies.log" + }, + { + "argv": [ + "/Users/runner/work/_temp/gem-ci/sdist-environment/bin/python", + "-I", + "-m", + "pip", + "install", + "--no-deps", + "--no-build-isolation", + "/Users/runner/work/pyGameMath/pyGameMath/dist/gem-1.0.0.tar.gz" + ], + "cwd": "/Users/runner/work/_temp/gem-ci", + "log": "sdist-install.log" + }, + { + "argv": [ + "/Users/runner/work/_temp/gem-ci/sdist-environment/bin/python", + "-I", + "-X", + "utf8", + "/Users/runner/work/pyGameMath/pyGameMath/tools/verify_distribution.py", + "--installed-root", + "/Users/runner/work/_temp/gem-ci/sdist-environment/lib/python3.12/site-packages", + "--output", + "/Users/runner/work/_temp/gem-ci/sdist-smoke.json" + ], + "cwd": "/Users/runner/work/_temp/gem-ci", + "log": "sdist-smoke.log" + }, + { + "argv": [ + "/Users/runner/work/_temp/gem-ci/sdist-environment/bin/python", + "-I", + "-X", + "utf8", + "/Users/runner/work/pyGameMath/pyGameMath/tools/check_architecture_docs.py", + "--examples", + "--getting-started", + "--api-reference", + "--tutorials", + "--showcase", + "--website", + "--package-root", + "/Users/runner/work/_temp/gem-ci/sdist-environment/lib/python3.12/site-packages", + "--output", + "/Users/runner/work/_temp/gem-ci/sdist-docs.json" + ], + "cwd": "/Users/runner/work/_temp/gem-ci", + "log": "sdist-docs.log" + } + ] + }, + { + "name": "gem-checks-Windows-py3.12-38014899622", + "artifact_id": 11655099925, + "artifact_digest": "sha256:6aa7af4b9a1585943a519f50eafabd296769efad410b54d2ecbdc4ad1d910698", + "python": "3.12.10", + "platform": "Windows-2025Server-10.0.26100-SP0", + "status": "failed", + "tests": { + "tests": 4036, + "failures": 1, + "errors": 0, + "skipped": 0, + "passed": 4035, + "xfailed": 0 + }, + "failed_tests": [ + "tests.test_showcase_examples::test_committed_artifacts_match_generation" + ], + "installed": { + "wheel": { + "smoke": { + "installed": { + "python": "3.12.10", + "implementation": "CPython", + "platform": "Windows-2025Server-10.0.26100-SP0", + "package_root": "D:\\a\\_temp\\gem-ci\\wheel-environment\\Lib\\site-packages", + "version": "1.0.0", + "runtime_files_matched": 18, + "smoke": "passed" + } + }, + "api_declarations": 268, + "examples": 43 + }, + "sdist": { + "smoke": { + "installed": { + "python": "3.12.10", + "implementation": "CPython", + "platform": "Windows-2025Server-10.0.26100-SP0", + "package_root": "D:\\a\\_temp\\gem-ci\\sdist-environment\\Lib\\site-packages", + "version": "1.0.0", + "runtime_files_matched": 18, + "smoke": "passed" + } + }, + "api_declarations": 268, + "examples": 43 + } + }, + "api_declarations": 268, + "examples": 43, + "artifacts": { + "metadata": { + "Metadata-Version": "2.4", + "Name": "gem", + "Version": "1.0.0", + "Summary": "Pure-Python mathematics for games and graphics", + "Author-email": "Alex Marinescu ", + "License-Expression": "BSD-2-Clause", + "Project-URL": "Issues, https://github.com/AlexMarinescu/pyGameMath/issues", + "Keywords": "math,game,graphics,library", + "Classifier": "Topic :: Software Development :: Libraries", + "Requires-Python": ">=3.10", + "Description-Content-Type": "text/markdown", + "License-File": "LICENSE", + "Requires-Dist": "six", + "Dynamic": "license-file" + }, + "runtime_files": 18 + }, + "commands": [ + { + "argv": [ + "C:\\hostedtoolcache\\windows\\Python\\3.12.10\\x64\\python.exe", + "-m", + "pytest", + "-q", + "-o", + "junit_family=legacy", + "--junitxml", + "D:\\a\\_temp\\gem-ci\\pytest.xml" + ], + "cwd": "D:\\a\\pyGameMath\\pyGameMath", + "log": "pytest.log" + }, + { + "argv": [ + "C:\\hostedtoolcache\\windows\\Python\\3.12.10\\x64\\python.exe", + "D:\\a\\pyGameMath\\pyGameMath\\tools\\check_architecture_docs.py", + "--examples", + "--getting-started", + "--api-reference", + "--tutorials", + "--showcase", + "--website", + "--output", + "D:\\a\\_temp\\gem-ci\\source-docs.json" + ], + "cwd": "D:\\a\\pyGameMath\\pyGameMath", + "log": "source-docs.log" + }, + { + "argv": [ + "C:\\hostedtoolcache\\windows\\Python\\3.12.10\\x64\\python.exe", + "-m", + "build", + "--outdir", + "D:\\a\\pyGameMath\\pyGameMath\\dist" + ], + "cwd": "D:\\a\\pyGameMath\\pyGameMath", + "log": "build.log" + }, + { + "argv": [ + "C:\\hostedtoolcache\\windows\\Python\\3.12.10\\x64\\python.exe", + "-m", + "twine", + "check", + "--strict", + "D:\\a\\pyGameMath\\pyGameMath\\dist\\gem-1.0.0-py3-none-any.whl", + "D:\\a\\pyGameMath\\pyGameMath\\dist\\gem-1.0.0.tar.gz" + ], + "cwd": "D:\\a\\pyGameMath\\pyGameMath", + "log": "twine.log" + }, + { + "argv": [ + "C:\\hostedtoolcache\\windows\\Python\\3.12.10\\x64\\python.exe", + "D:\\a\\pyGameMath\\pyGameMath\\tools\\verify_distribution.py", + "--wheel", + "D:\\a\\pyGameMath\\pyGameMath\\dist\\gem-1.0.0-py3-none-any.whl", + "--sdist", + "D:\\a\\pyGameMath\\pyGameMath\\dist\\gem-1.0.0.tar.gz", + "--output", + "D:\\a\\_temp\\gem-ci\\artifacts.json" + ], + "cwd": "D:\\a\\pyGameMath\\pyGameMath", + "log": "artifacts.log" + }, + { + "argv": [ + "D:\\a\\_temp\\gem-ci\\wheel-environment\\Scripts\\python.exe", + "-m", + "pip", + "install", + "six==1.17.0", + "setuptools==80.9.0", + "wheel==0.48.0" + ], + "cwd": "D:\\a\\_temp\\gem-ci", + "log": "wheel-dependencies.log" + }, + { + "argv": [ + "D:\\a\\_temp\\gem-ci\\wheel-environment\\Scripts\\python.exe", + "-I", + "-m", + "pip", + "install", + "--no-deps", + "--no-build-isolation", + "D:\\a\\pyGameMath\\pyGameMath\\dist\\gem-1.0.0-py3-none-any.whl" + ], + "cwd": "D:\\a\\_temp\\gem-ci", + "log": "wheel-install.log" + }, + { + "argv": [ + "D:\\a\\_temp\\gem-ci\\wheel-environment\\Scripts\\python.exe", + "-I", + "-X", + "utf8", + "D:\\a\\pyGameMath\\pyGameMath\\tools\\verify_distribution.py", + "--installed-root", + "D:\\a\\_temp\\gem-ci\\wheel-environment\\Lib\\site-packages", + "--output", + "D:\\a\\_temp\\gem-ci\\wheel-smoke.json" + ], + "cwd": "D:\\a\\_temp\\gem-ci", + "log": "wheel-smoke.log" + }, + { + "argv": [ + "D:\\a\\_temp\\gem-ci\\wheel-environment\\Scripts\\python.exe", + "-I", + "-X", + "utf8", + "D:\\a\\pyGameMath\\pyGameMath\\tools\\check_architecture_docs.py", + "--examples", + "--getting-started", + "--api-reference", + "--tutorials", + "--showcase", + "--website", + "--package-root", + "D:\\a\\_temp\\gem-ci\\wheel-environment\\Lib\\site-packages", + "--output", + "D:\\a\\_temp\\gem-ci\\wheel-docs.json" + ], + "cwd": "D:\\a\\_temp\\gem-ci", + "log": "wheel-docs.log" + }, + { + "argv": [ + "D:\\a\\_temp\\gem-ci\\sdist-environment\\Scripts\\python.exe", + "-m", + "pip", + "install", + "six==1.17.0", + "setuptools==80.9.0", + "wheel==0.48.0" + ], + "cwd": "D:\\a\\_temp\\gem-ci", + "log": "sdist-dependencies.log" + }, + { + "argv": [ + "D:\\a\\_temp\\gem-ci\\sdist-environment\\Scripts\\python.exe", + "-I", + "-m", + "pip", + "install", + "--no-deps", + "--no-build-isolation", + "D:\\a\\pyGameMath\\pyGameMath\\dist\\gem-1.0.0.tar.gz" + ], + "cwd": "D:\\a\\_temp\\gem-ci", + "log": "sdist-install.log" + }, + { + "argv": [ + "D:\\a\\_temp\\gem-ci\\sdist-environment\\Scripts\\python.exe", + "-I", + "-X", + "utf8", + "D:\\a\\pyGameMath\\pyGameMath\\tools\\verify_distribution.py", + "--installed-root", + "D:\\a\\_temp\\gem-ci\\sdist-environment\\Lib\\site-packages", + "--output", + "D:\\a\\_temp\\gem-ci\\sdist-smoke.json" + ], + "cwd": "D:\\a\\_temp\\gem-ci", + "log": "sdist-smoke.log" + }, + { + "argv": [ + "D:\\a\\_temp\\gem-ci\\sdist-environment\\Scripts\\python.exe", + "-I", + "-X", + "utf8", + "D:\\a\\pyGameMath\\pyGameMath\\tools\\check_architecture_docs.py", + "--examples", + "--getting-started", + "--api-reference", + "--tutorials", + "--showcase", + "--website", + "--package-root", + "D:\\a\\_temp\\gem-ci\\sdist-environment\\Lib\\site-packages", + "--output", + "D:\\a\\_temp\\gem-ci\\sdist-docs.json" + ], + "cwd": "D:\\a\\_temp\\gem-ci", + "log": "sdist-docs.log" + } + ] + }, + { + "name": "gem-checks-macOS-py3.10-38014899622", + "artifact_id": 11654724254, + "artifact_digest": "sha256:6d3113e152b6d436aad01c7a14e9bf84dd42ddf3e8ca5f2aab5f38a71eecc856", + "python": "3.10.11", + "platform": "macOS-26.6.2-arm64-arm-64bit", + "status": "passed", + "tests": { + "tests": 4036, + "failures": 0, + "errors": 0, + "skipped": 0, + "passed": 4036, + "xfailed": 0 + }, + "failed_tests": [], + "installed": { + "wheel": { + "smoke": { + "installed": { + "python": "3.10.11", + "implementation": "CPython", + "platform": "macOS-26.6.2-arm64-arm-64bit", + "package_root": "/Users/runner/work/_temp/gem-ci/wheel-environment/lib/python3.10/site-packages", + "version": "1.0.0", + "runtime_files_matched": 18, + "smoke": "passed" + } + }, + "api_declarations": 268, + "examples": 43 + }, + "sdist": { + "smoke": { + "installed": { + "python": "3.10.11", + "implementation": "CPython", + "platform": "macOS-26.6.2-arm64-arm-64bit", + "package_root": "/Users/runner/work/_temp/gem-ci/sdist-environment/lib/python3.10/site-packages", + "version": "1.0.0", + "runtime_files_matched": 18, + "smoke": "passed" + } + }, + "api_declarations": 268, + "examples": 43 + } + }, + "api_declarations": 268, + "examples": 43, + "artifacts": { + "metadata": { + "Metadata-Version": "2.4", + "Name": "gem", + "Version": "1.0.0", + "Summary": "Pure-Python mathematics for games and graphics", + "Author-email": "Alex Marinescu ", + "License-Expression": "BSD-2-Clause", + "Project-URL": "Issues, https://github.com/AlexMarinescu/pyGameMath/issues", + "Keywords": "math,game,graphics,library", + "Classifier": "Topic :: Software Development :: Libraries", + "Requires-Python": ">=3.10", + "Description-Content-Type": "text/markdown", + "License-File": "LICENSE", + "Requires-Dist": "six", + "Dynamic": "license-file" + }, + "runtime_files": 18 + }, + "commands": [ + { + "argv": [ + "/Library/Frameworks/Python.framework/Versions/3.10/bin/python", + "-m", + "pytest", + "-q", + "-o", + "junit_family=legacy", + "--junitxml", + "/Users/runner/work/_temp/gem-ci/pytest.xml" + ], + "cwd": "/Users/runner/work/pyGameMath/pyGameMath", + "log": "pytest.log" + }, + { + "argv": [ + "/Library/Frameworks/Python.framework/Versions/3.10/bin/python", + "/Users/runner/work/pyGameMath/pyGameMath/tools/check_architecture_docs.py", + "--examples", + "--getting-started", + "--api-reference", + "--tutorials", + "--showcase", + "--website", + "--output", + "/Users/runner/work/_temp/gem-ci/source-docs.json" + ], + "cwd": "/Users/runner/work/pyGameMath/pyGameMath", + "log": "source-docs.log" + }, + { + "argv": [ + "/Library/Frameworks/Python.framework/Versions/3.10/bin/python", + "-m", + "build", + "--outdir", + "/Users/runner/work/pyGameMath/pyGameMath/dist" + ], + "cwd": "/Users/runner/work/pyGameMath/pyGameMath", + "log": "build.log" + }, + { + "argv": [ + "/Library/Frameworks/Python.framework/Versions/3.10/bin/python", + "-m", + "twine", + "check", + "--strict", + "/Users/runner/work/pyGameMath/pyGameMath/dist/gem-1.0.0-py3-none-any.whl", + "/Users/runner/work/pyGameMath/pyGameMath/dist/gem-1.0.0.tar.gz" + ], + "cwd": "/Users/runner/work/pyGameMath/pyGameMath", + "log": "twine.log" + }, + { + "argv": [ + "/Library/Frameworks/Python.framework/Versions/3.10/bin/python", + "/Users/runner/work/pyGameMath/pyGameMath/tools/verify_distribution.py", + "--wheel", + "/Users/runner/work/pyGameMath/pyGameMath/dist/gem-1.0.0-py3-none-any.whl", + "--sdist", + "/Users/runner/work/pyGameMath/pyGameMath/dist/gem-1.0.0.tar.gz", + "--output", + "/Users/runner/work/_temp/gem-ci/artifacts.json" + ], + "cwd": "/Users/runner/work/pyGameMath/pyGameMath", + "log": "artifacts.log" + }, + { + "argv": [ + "/Users/runner/work/_temp/gem-ci/wheel-environment/bin/python", + "-m", + "pip", + "install", + "six==1.17.0", + "setuptools==80.9.0", + "wheel==0.48.0" + ], + "cwd": "/Users/runner/work/_temp/gem-ci", + "log": "wheel-dependencies.log" + }, + { + "argv": [ + "/Users/runner/work/_temp/gem-ci/wheel-environment/bin/python", + "-I", + "-m", + "pip", + "install", + "--no-deps", + "--no-build-isolation", + "/Users/runner/work/pyGameMath/pyGameMath/dist/gem-1.0.0-py3-none-any.whl" + ], + "cwd": "/Users/runner/work/_temp/gem-ci", + "log": "wheel-install.log" + }, + { + "argv": [ + "/Users/runner/work/_temp/gem-ci/wheel-environment/bin/python", + "-I", + "-X", + "utf8", + "/Users/runner/work/pyGameMath/pyGameMath/tools/verify_distribution.py", + "--installed-root", + "/Users/runner/work/_temp/gem-ci/wheel-environment/lib/python3.10/site-packages", + "--output", + "/Users/runner/work/_temp/gem-ci/wheel-smoke.json" + ], + "cwd": "/Users/runner/work/_temp/gem-ci", + "log": "wheel-smoke.log" + }, + { + "argv": [ + "/Users/runner/work/_temp/gem-ci/wheel-environment/bin/python", + "-I", + "-X", + "utf8", + "/Users/runner/work/pyGameMath/pyGameMath/tools/check_architecture_docs.py", + "--examples", + "--getting-started", + "--api-reference", + "--tutorials", + "--showcase", + "--website", + "--package-root", + "/Users/runner/work/_temp/gem-ci/wheel-environment/lib/python3.10/site-packages", + "--output", + "/Users/runner/work/_temp/gem-ci/wheel-docs.json" + ], + "cwd": "/Users/runner/work/_temp/gem-ci", + "log": "wheel-docs.log" + }, + { + "argv": [ + "/Users/runner/work/_temp/gem-ci/sdist-environment/bin/python", + "-m", + "pip", + "install", + "six==1.17.0", + "setuptools==80.9.0", + "wheel==0.48.0" + ], + "cwd": "/Users/runner/work/_temp/gem-ci", + "log": "sdist-dependencies.log" + }, + { + "argv": [ + "/Users/runner/work/_temp/gem-ci/sdist-environment/bin/python", + "-I", + "-m", + "pip", + "install", + "--no-deps", + "--no-build-isolation", + "/Users/runner/work/pyGameMath/pyGameMath/dist/gem-1.0.0.tar.gz" + ], + "cwd": "/Users/runner/work/_temp/gem-ci", + "log": "sdist-install.log" + }, + { + "argv": [ + "/Users/runner/work/_temp/gem-ci/sdist-environment/bin/python", + "-I", + "-X", + "utf8", + "/Users/runner/work/pyGameMath/pyGameMath/tools/verify_distribution.py", + "--installed-root", + "/Users/runner/work/_temp/gem-ci/sdist-environment/lib/python3.10/site-packages", + "--output", + "/Users/runner/work/_temp/gem-ci/sdist-smoke.json" + ], + "cwd": "/Users/runner/work/_temp/gem-ci", + "log": "sdist-smoke.log" + }, + { + "argv": [ + "/Users/runner/work/_temp/gem-ci/sdist-environment/bin/python", + "-I", + "-X", + "utf8", + "/Users/runner/work/pyGameMath/pyGameMath/tools/check_architecture_docs.py", + "--examples", + "--getting-started", + "--api-reference", + "--tutorials", + "--showcase", + "--website", + "--package-root", + "/Users/runner/work/_temp/gem-ci/sdist-environment/lib/python3.10/site-packages", + "--output", + "/Users/runner/work/_temp/gem-ci/sdist-docs.json" + ], + "cwd": "/Users/runner/work/_temp/gem-ci", + "log": "sdist-docs.log" + } + ] + }, + { + "name": "gem-checks-Windows-py3.10-38014899622", + "artifact_id": 11654489416, + "artifact_digest": "sha256:fbbc8c215e3c611daf1669c04fb19fed71a1e30e2cc078fe2860205c67beb023", + "python": "3.10.11", + "platform": "Windows-10-10.0.26100-SP0", + "status": "failed", + "tests": { + "tests": 4036, + "failures": 1, + "errors": 0, + "skipped": 0, + "passed": 4035, + "xfailed": 0 + }, + "failed_tests": [ + "tests.test_showcase_examples::test_committed_artifacts_match_generation" + ], + "installed": { + "wheel": { + "smoke": { + "installed": { + "python": "3.10.11", + "implementation": "CPython", + "platform": "Windows-10-10.0.26100-SP0", + "package_root": "D:\\a\\_temp\\gem-ci\\wheel-environment\\Lib\\site-packages", + "version": "1.0.0", + "runtime_files_matched": 18, + "smoke": "passed" + } + }, + "api_declarations": 268, + "examples": 43 + }, + "sdist": { + "smoke": { + "installed": { + "python": "3.10.11", + "implementation": "CPython", + "platform": "Windows-10-10.0.26100-SP0", + "package_root": "D:\\a\\_temp\\gem-ci\\sdist-environment\\Lib\\site-packages", + "version": "1.0.0", + "runtime_files_matched": 18, + "smoke": "passed" + } + }, + "api_declarations": 268, + "examples": 43 + } + }, + "api_declarations": 268, + "examples": 43, + "artifacts": { + "metadata": { + "Metadata-Version": "2.4", + "Name": "gem", + "Version": "1.0.0", + "Summary": "Pure-Python mathematics for games and graphics", + "Author-email": "Alex Marinescu ", + "License-Expression": "BSD-2-Clause", + "Project-URL": "Issues, https://github.com/AlexMarinescu/pyGameMath/issues", + "Keywords": "math,game,graphics,library", + "Classifier": "Topic :: Software Development :: Libraries", + "Requires-Python": ">=3.10", + "Description-Content-Type": "text/markdown", + "License-File": "LICENSE", + "Requires-Dist": "six", + "Dynamic": "license-file" + }, + "runtime_files": 18 + }, + "commands": [ + { + "argv": [ + "C:\\hostedtoolcache\\windows\\Python\\3.10.11\\x64\\python.exe", + "-m", + "pytest", + "-q", + "-o", + "junit_family=legacy", + "--junitxml", + "D:\\a\\_temp\\gem-ci\\pytest.xml" + ], + "cwd": "D:\\a\\pyGameMath\\pyGameMath", + "log": "pytest.log" + }, + { + "argv": [ + "C:\\hostedtoolcache\\windows\\Python\\3.10.11\\x64\\python.exe", + "D:\\a\\pyGameMath\\pyGameMath\\tools\\check_architecture_docs.py", + "--examples", + "--getting-started", + "--api-reference", + "--tutorials", + "--showcase", + "--website", + "--output", + "D:\\a\\_temp\\gem-ci\\source-docs.json" + ], + "cwd": "D:\\a\\pyGameMath\\pyGameMath", + "log": "source-docs.log" + }, + { + "argv": [ + "C:\\hostedtoolcache\\windows\\Python\\3.10.11\\x64\\python.exe", + "-m", + "build", + "--outdir", + "D:\\a\\pyGameMath\\pyGameMath\\dist" + ], + "cwd": "D:\\a\\pyGameMath\\pyGameMath", + "log": "build.log" + }, + { + "argv": [ + "C:\\hostedtoolcache\\windows\\Python\\3.10.11\\x64\\python.exe", + "-m", + "twine", + "check", + "--strict", + "D:\\a\\pyGameMath\\pyGameMath\\dist\\gem-1.0.0-py3-none-any.whl", + "D:\\a\\pyGameMath\\pyGameMath\\dist\\gem-1.0.0.tar.gz" + ], + "cwd": "D:\\a\\pyGameMath\\pyGameMath", + "log": "twine.log" + }, + { + "argv": [ + "C:\\hostedtoolcache\\windows\\Python\\3.10.11\\x64\\python.exe", + "D:\\a\\pyGameMath\\pyGameMath\\tools\\verify_distribution.py", + "--wheel", + "D:\\a\\pyGameMath\\pyGameMath\\dist\\gem-1.0.0-py3-none-any.whl", + "--sdist", + "D:\\a\\pyGameMath\\pyGameMath\\dist\\gem-1.0.0.tar.gz", + "--output", + "D:\\a\\_temp\\gem-ci\\artifacts.json" + ], + "cwd": "D:\\a\\pyGameMath\\pyGameMath", + "log": "artifacts.log" + }, + { + "argv": [ + "D:\\a\\_temp\\gem-ci\\wheel-environment\\Scripts\\python.exe", + "-m", + "pip", + "install", + "six==1.17.0", + "setuptools==80.9.0", + "wheel==0.48.0" + ], + "cwd": "D:\\a\\_temp\\gem-ci", + "log": "wheel-dependencies.log" + }, + { + "argv": [ + "D:\\a\\_temp\\gem-ci\\wheel-environment\\Scripts\\python.exe", + "-I", + "-m", + "pip", + "install", + "--no-deps", + "--no-build-isolation", + "D:\\a\\pyGameMath\\pyGameMath\\dist\\gem-1.0.0-py3-none-any.whl" + ], + "cwd": "D:\\a\\_temp\\gem-ci", + "log": "wheel-install.log" + }, + { + "argv": [ + "D:\\a\\_temp\\gem-ci\\wheel-environment\\Scripts\\python.exe", + "-I", + "-X", + "utf8", + "D:\\a\\pyGameMath\\pyGameMath\\tools\\verify_distribution.py", + "--installed-root", + "D:\\a\\_temp\\gem-ci\\wheel-environment\\Lib\\site-packages", + "--output", + "D:\\a\\_temp\\gem-ci\\wheel-smoke.json" + ], + "cwd": "D:\\a\\_temp\\gem-ci", + "log": "wheel-smoke.log" + }, + { + "argv": [ + "D:\\a\\_temp\\gem-ci\\wheel-environment\\Scripts\\python.exe", + "-I", + "-X", + "utf8", + "D:\\a\\pyGameMath\\pyGameMath\\tools\\check_architecture_docs.py", + "--examples", + "--getting-started", + "--api-reference", + "--tutorials", + "--showcase", + "--website", + "--package-root", + "D:\\a\\_temp\\gem-ci\\wheel-environment\\Lib\\site-packages", + "--output", + "D:\\a\\_temp\\gem-ci\\wheel-docs.json" + ], + "cwd": "D:\\a\\_temp\\gem-ci", + "log": "wheel-docs.log" + }, + { + "argv": [ + "D:\\a\\_temp\\gem-ci\\sdist-environment\\Scripts\\python.exe", + "-m", + "pip", + "install", + "six==1.17.0", + "setuptools==80.9.0", + "wheel==0.48.0" + ], + "cwd": "D:\\a\\_temp\\gem-ci", + "log": "sdist-dependencies.log" + }, + { + "argv": [ + "D:\\a\\_temp\\gem-ci\\sdist-environment\\Scripts\\python.exe", + "-I", + "-m", + "pip", + "install", + "--no-deps", + "--no-build-isolation", + "D:\\a\\pyGameMath\\pyGameMath\\dist\\gem-1.0.0.tar.gz" + ], + "cwd": "D:\\a\\_temp\\gem-ci", + "log": "sdist-install.log" + }, + { + "argv": [ + "D:\\a\\_temp\\gem-ci\\sdist-environment\\Scripts\\python.exe", + "-I", + "-X", + "utf8", + "D:\\a\\pyGameMath\\pyGameMath\\tools\\verify_distribution.py", + "--installed-root", + "D:\\a\\_temp\\gem-ci\\sdist-environment\\Lib\\site-packages", + "--output", + "D:\\a\\_temp\\gem-ci\\sdist-smoke.json" + ], + "cwd": "D:\\a\\_temp\\gem-ci", + "log": "sdist-smoke.log" + }, + { + "argv": [ + "D:\\a\\_temp\\gem-ci\\sdist-environment\\Scripts\\python.exe", + "-I", + "-X", + "utf8", + "D:\\a\\pyGameMath\\pyGameMath\\tools\\check_architecture_docs.py", + "--examples", + "--getting-started", + "--api-reference", + "--tutorials", + "--showcase", + "--website", + "--package-root", + "D:\\a\\_temp\\gem-ci\\sdist-environment\\Lib\\site-packages", + "--output", + "D:\\a\\_temp\\gem-ci\\sdist-docs.json" + ], + "cwd": "D:\\a\\_temp\\gem-ci", + "log": "sdist-docs.log" + } + ] + }, + { + "name": "gem-checks-Linux-py3.13-38014899622", + "artifact_id": 11654489319, + "artifact_digest": "sha256:7b84bfb0f0de322e9b17703f0878a06af0e5836d6f3c4ab85292ef39d2686492", + "python": "3.13.16", + "platform": "Linux-6.17.0-1022-azure-x86_64-with-glibc2.39", + "status": "passed", + "tests": { + "tests": 4036, + "failures": 0, + "errors": 0, + "skipped": 0, + "passed": 4036, + "xfailed": 0 + }, + "failed_tests": [], + "installed": { + "wheel": { + "smoke": { + "installed": { + "python": "3.13.16", + "implementation": "CPython", + "platform": "Linux-6.17.0-1022-azure-x86_64-with-glibc2.39", + "package_root": "/home/runner/work/_temp/gem-ci/wheel-environment/lib/python3.13/site-packages", + "version": "1.0.0", + "runtime_files_matched": 18, + "smoke": "passed" + } + }, + "api_declarations": 268, + "examples": 43 + }, + "sdist": { + "smoke": { + "installed": { + "python": "3.13.16", + "implementation": "CPython", + "platform": "Linux-6.17.0-1022-azure-x86_64-with-glibc2.39", + "package_root": "/home/runner/work/_temp/gem-ci/sdist-environment/lib/python3.13/site-packages", + "version": "1.0.0", + "runtime_files_matched": 18, + "smoke": "passed" + } + }, + "api_declarations": 268, + "examples": 43 + } + }, + "api_declarations": 268, + "examples": 43, + "artifacts": { + "metadata": { + "Metadata-Version": "2.4", + "Name": "gem", + "Version": "1.0.0", + "Summary": "Pure-Python mathematics for games and graphics", + "Author-email": "Alex Marinescu ", + "License-Expression": "BSD-2-Clause", + "Project-URL": "Issues, https://github.com/AlexMarinescu/pyGameMath/issues", + "Keywords": "math,game,graphics,library", + "Classifier": "Topic :: Software Development :: Libraries", + "Requires-Python": ">=3.10", + "Description-Content-Type": "text/markdown", + "License-File": "LICENSE", + "Requires-Dist": "six", + "Dynamic": "license-file" + }, + "runtime_files": 18 + }, + "commands": [ + { + "argv": [ + "/opt/hostedtoolcache/Python/3.13.16/x64/bin/python", + "-m", + "pytest", + "-q", + "-o", + "junit_family=legacy", + "--junitxml", + "/home/runner/work/_temp/gem-ci/pytest.xml" + ], + "cwd": "/home/runner/work/pyGameMath/pyGameMath", + "log": "pytest.log" + }, + { + "argv": [ + "/opt/hostedtoolcache/Python/3.13.16/x64/bin/python", + "/home/runner/work/pyGameMath/pyGameMath/tools/check_architecture_docs.py", + "--examples", + "--getting-started", + "--api-reference", + "--tutorials", + "--showcase", + "--website", + "--output", + "/home/runner/work/_temp/gem-ci/source-docs.json" + ], + "cwd": "/home/runner/work/pyGameMath/pyGameMath", + "log": "source-docs.log" + }, + { + "argv": [ + "/opt/hostedtoolcache/Python/3.13.16/x64/bin/python", + "-m", + "build", + "--outdir", + "/home/runner/work/pyGameMath/pyGameMath/dist" + ], + "cwd": "/home/runner/work/pyGameMath/pyGameMath", + "log": "build.log" + }, + { + "argv": [ + "/opt/hostedtoolcache/Python/3.13.16/x64/bin/python", + "-m", + "twine", + "check", + "--strict", + "/home/runner/work/pyGameMath/pyGameMath/dist/gem-1.0.0-py3-none-any.whl", + "/home/runner/work/pyGameMath/pyGameMath/dist/gem-1.0.0.tar.gz" + ], + "cwd": "/home/runner/work/pyGameMath/pyGameMath", + "log": "twine.log" + }, + { + "argv": [ + "/opt/hostedtoolcache/Python/3.13.16/x64/bin/python", + "/home/runner/work/pyGameMath/pyGameMath/tools/verify_distribution.py", + "--wheel", + "/home/runner/work/pyGameMath/pyGameMath/dist/gem-1.0.0-py3-none-any.whl", + "--sdist", + "/home/runner/work/pyGameMath/pyGameMath/dist/gem-1.0.0.tar.gz", + "--output", + "/home/runner/work/_temp/gem-ci/artifacts.json" + ], + "cwd": "/home/runner/work/pyGameMath/pyGameMath", + "log": "artifacts.log" + }, + { + "argv": [ + "/home/runner/work/_temp/gem-ci/wheel-environment/bin/python", + "-m", + "pip", + "install", + "six==1.17.0", + "setuptools==80.9.0", + "wheel==0.48.0" + ], + "cwd": "/home/runner/work/_temp/gem-ci", + "log": "wheel-dependencies.log" + }, + { + "argv": [ + "/home/runner/work/_temp/gem-ci/wheel-environment/bin/python", + "-I", + "-m", + "pip", + "install", + "--no-deps", + "--no-build-isolation", + "/home/runner/work/pyGameMath/pyGameMath/dist/gem-1.0.0-py3-none-any.whl" + ], + "cwd": "/home/runner/work/_temp/gem-ci", + "log": "wheel-install.log" + }, + { + "argv": [ + "/home/runner/work/_temp/gem-ci/wheel-environment/bin/python", + "-I", + "-X", + "utf8", + "/home/runner/work/pyGameMath/pyGameMath/tools/verify_distribution.py", + "--installed-root", + "/home/runner/work/_temp/gem-ci/wheel-environment/lib/python3.13/site-packages", + "--output", + "/home/runner/work/_temp/gem-ci/wheel-smoke.json" + ], + "cwd": "/home/runner/work/_temp/gem-ci", + "log": "wheel-smoke.log" + }, + { + "argv": [ + "/home/runner/work/_temp/gem-ci/wheel-environment/bin/python", + "-I", + "-X", + "utf8", + "/home/runner/work/pyGameMath/pyGameMath/tools/check_architecture_docs.py", + "--examples", + "--getting-started", + "--api-reference", + "--tutorials", + "--showcase", + "--website", + "--package-root", + "/home/runner/work/_temp/gem-ci/wheel-environment/lib/python3.13/site-packages", + "--output", + "/home/runner/work/_temp/gem-ci/wheel-docs.json" + ], + "cwd": "/home/runner/work/_temp/gem-ci", + "log": "wheel-docs.log" + }, + { + "argv": [ + "/home/runner/work/_temp/gem-ci/sdist-environment/bin/python", + "-m", + "pip", + "install", + "six==1.17.0", + "setuptools==80.9.0", + "wheel==0.48.0" + ], + "cwd": "/home/runner/work/_temp/gem-ci", + "log": "sdist-dependencies.log" + }, + { + "argv": [ + "/home/runner/work/_temp/gem-ci/sdist-environment/bin/python", + "-I", + "-m", + "pip", + "install", + "--no-deps", + "--no-build-isolation", + "/home/runner/work/pyGameMath/pyGameMath/dist/gem-1.0.0.tar.gz" + ], + "cwd": "/home/runner/work/_temp/gem-ci", + "log": "sdist-install.log" + }, + { + "argv": [ + "/home/runner/work/_temp/gem-ci/sdist-environment/bin/python", + "-I", + "-X", + "utf8", + "/home/runner/work/pyGameMath/pyGameMath/tools/verify_distribution.py", + "--installed-root", + "/home/runner/work/_temp/gem-ci/sdist-environment/lib/python3.13/site-packages", + "--output", + "/home/runner/work/_temp/gem-ci/sdist-smoke.json" + ], + "cwd": "/home/runner/work/_temp/gem-ci", + "log": "sdist-smoke.log" + }, + { + "argv": [ + "/home/runner/work/_temp/gem-ci/sdist-environment/bin/python", + "-I", + "-X", + "utf8", + "/home/runner/work/pyGameMath/pyGameMath/tools/check_architecture_docs.py", + "--examples", + "--getting-started", + "--api-reference", + "--tutorials", + "--showcase", + "--website", + "--package-root", + "/home/runner/work/_temp/gem-ci/sdist-environment/lib/python3.13/site-packages", + "--output", + "/home/runner/work/_temp/gem-ci/sdist-docs.json" + ], + "cwd": "/home/runner/work/_temp/gem-ci", + "log": "sdist-docs.log" + } + ] + } + ], + "documentation": { + "run_id": 38014899541, + "url": "https://github.com/AlexMarinescu/pyGameMath/actions/runs/38014899541", + "status": "success", + "api_declarations": 268, + "executable_examples": 43, + "site": { + "external_asset_dependencies": 0, + "hosting": "relative project-prefix-safe paths", + "html_pages": 69, + "image_references": 16, + "local_links_checked": 7724, + "mathml_equations": 24, + "navigation_checked": 7, + "search_api_entries": 42, + "syntax_highlighting": true + }, + "commands": [ + "/tmp/phase4f-docs-env/bin/python -m mkdocs build --strict --site-dir /tmp/phase5b-site", + "/tmp/phase4f-docs-env/bin/python tools/check_site.py --site-dir /tmp/phase5b-site --output /tmp/phase5b-site.json" + ] + }, + "local_candidate": { + "command": "/tmp/phase5a-py312/bin/python tools/ci_validate.py --output-dir /tmp/phase5b-complete --artifacts-dir /tmp/phase5b-complete-assets --offline --wheelhouse /tmp/phase5b-wheelhouse", + "status": "passed", + "manifest": { + "schema_version": 1, + "distribution": "gem", + "version": "1.0.0", + "tag": "v1.0.0", + "source_commit": "0e085b4bfd532935bb1267bb3eae95f04de3ed15", + "source_tree": "2007da95f1b7e6d58bd50f970fbbb49204faf85a", + "source_clean": true, + "files": { + "gem-1.0.0-py3-none-any.whl": "f4d6b096ab99c7c0c8f3b5bd71239a307ec0d467bc819d6d01e045f2c0b2d141", + "gem-1.0.0.tar.gz": "230fca9becbcd0afb2b5e520eeae099fe60fc00213abf292d2bbcfdc1c134de8" + } + }, + "installed": { + "wheel": { + "smoke": { + "installed": { + "python": "3.12.14", + "implementation": "CPython", + "platform": "Linux-6.18.44-x86_64-with-glibc2.41", + "package_root": "/tmp/phase5b-complete/wheel-environment/lib/python3.12/site-packages", + "version": "1.0.0", + "runtime_files_matched": 18, + "smoke": "passed" + } + }, + "api_declarations": 268, + "examples": 43 + }, + "sdist": { + "smoke": { + "installed": { + "python": "3.12.14", + "implementation": "CPython", + "platform": "Linux-6.18.44-x86_64-with-glibc2.41", + "package_root": "/tmp/phase5b-complete/sdist-environment/lib/python3.12/site-packages", + "version": "1.0.0", + "runtime_files_matched": 18, + "smoke": "passed" + } + }, + "api_declarations": 268, + "examples": 43 + } + }, + "integrity_command": "/tmp/phase5a-py312/bin/python tools/release_gate.py verify-artifacts --directory /tmp/phase5b-complete-assets --commit 0e085b4bfd532935bb1267bb3eae95f04de3ed15 --tag v1.0.0 --output /tmp/phase5b-complete-integrity.json", + "integrity": "passed", + "official_release_assets": false + }, + "workflow_policy": { + "workflows": [ + "documentation-artifact.yml", + "github-release.yml", + "package-validation.yml", + "publish-docs.yml", + "release-validation.yml" + ], + "action_pins": [ + "actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1", + "actions/deploy-pages@368f82528645a54fb793d4d04e342629a3f51346", + "actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333", + "actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97", + "actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9", + "actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9" + ], + "matrix_jobs": 15, + "package_publication_enabled": false, + "wiki_mutation_enabled": false, + "pages_write_scope": "master-only deployment job", + "manual_release_modes": [ + "validate", + "review" + ] + }, + "actionlint": { + "version": "1.7.12", + "status": "passed", + "command": "/tmp/phase5b-tools/actionlint -color=false -shellcheck='' .github/workflows/*.yml", + "shellcheck_executed": false, + "download_sha256": "8aca8db96f1b94770f1b0d72b6dddcb1ebb8123cb3712530b08cc387b349a3d8" + }, + "guard_tests": { + "command": "/tmp/phase5a-py312/bin/python -m pytest -q tests/test_release_workflows.py", + "passed": 70, + "network_mutations": "mocked; no publication performed", + "tampered_candidates": [ + { + "case": "extra_file", + "status": "rejected", + "reason": "candidate bundle must contain two archives, manifest, checksums and installation instructions" + }, + { + "case": "dirty_source", + "status": "rejected", + "reason": "dirty source cannot become a release candidate" + }, + { + "case": "wrong_commit", + "status": "rejected", + "reason": "artifact source commit mismatch" + }, + { + "case": "wrong_tree", + "status": "rejected", + "reason": "checkout/tree does not match artifact provenance" + }, + { + "case": "wrong_version", + "status": "rejected", + "reason": "artifact version/tag mismatch" + }, + { + "case": "digest", + "status": "rejected", + "reason": "artifact digest mismatch: gem-1.0.0-py3-none-any.whl" + }, + { + "case": "checksums", + "status": "rejected", + "reason": "checksum file mismatch" + }, + { + "case": "empty_instructions", + "status": "rejected", + "reason": "installation instructions missing" + } + ] + }, + "runtime_sha256": { + "gem/__init__.py": "ed387aee9744786790d8c56cda8f17e9c311039ef37ba9af0216d782494de735", + "gem/_version.py": "49a1ce110282313f047abcfceec9291077a8a1d8495afae37177e05511144a4d", + "gem/bezier.py": "1d354070a425ea0cb99e590f984f1dd86cf5b2a03432bee848f3352f85fc75b3", + "gem/common.py": "2384eae0490eff93499fca6fe56cc25074119b6955b40ca3d6823f8099406ec8", + "gem/experimental/__init__.py": "01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b", + "gem/experimental/_bezier_legacy.py": "39f9522eaeb171837e7797ab604bb4bacf718053375266a61c39a0de9217a034", + "gem/experimental/bezier.py": "4d61b9ad9b6a807101a06e4641b74dae96325a82cdc5796bd2326f7f1afdaf15", + "gem/experimental/legendre.py": "4f1fb87056d71982405e1cd5d83fbb07fe9f8a9c464d18ac641486cc47633db5", + "gem/experimental/sph.py": "192b1c96295ddf6585da4c91c1da372b5c4f53bc844ee9125ff38b913496ff0d", + "gem/experimental/sph_irradiance_map.py": "e7d8fa175ad566fb2d4def36978d632a537e930b5acb02396c906ae1465bab94", + "gem/experimental/sph_sample.py": "fbf10456e724005116524fa797474fe767cbf0224468cfc878e5fd8e6f278c15", + "gem/legendre.py": "bcad8a96d35f47eb4f03a1535cf5083017a351e89129534b6acb673966a254f8", + "gem/matrix.py": "e539db231c9b71bc0846954f2a0fad0840b0b03ffd78ae65a4189a634b56c17c", + "gem/plane.py": "bbffce137d4885d3587efcb0aa0f2d5084d12191f9c62efb54e2cbe4dd9033f6", + "gem/quaternion.py": "c17e8fd731d601e36ff21bae86613e1f4982e6275b07d1d27be6f6405dff8059", + "gem/ray.py": "307dfac3a68a2235c3342d4bfe0d93fd15335dba4c28eff4cf6b79ae6167c1a4", + "gem/spherical_harmonics.py": "25bb90b46e4ba5175e320a7f232ac6a2523b31d495e82412db5bdc9579f0d06b", + "gem/vector.py": "df658aa596999334153e143c0801eabc5e5fb4e5bc402770e78a0178dc83c28c" + }, + "unchanged": [ + "all 18 runtime files", + "public API signatures", + "version and distribution identity", + "packaging metadata", + "runtime dependencies", + "reference assets and existing assertions" + ], + "limitations": [ + { + "id": "CI-WIN01", + "scope": "release matrix", + "status": "blocked", + "detail": "All five Windows jobs retain one strict showcase measurements.json byte-reference failure caused by ordinary platform floating-point differences; all other tests and independent installed checks pass." + }, + { + "id": "PYPI-AUTH", + "scope": "PyPI only", + "status": "blocked", + "detail": "Historical gem publishing authority remains unverified; no PyPI workflow exists." + }, + { + "id": "REVIEW-CONFIG", + "scope": "future GitHub publication", + "status": "unverified", + "detail": "Protected release environment settings could not be inspected through the public connector. Workflows validate them at runtime and fail closed; no server settings were changed." + }, + { + "id": "DEPENDABOT-REVIEW", + "scope": "maintainer security review", + "status": "untriaged", + "detail": "Git push reports one moderate default-branch alert. Its details were unavailable through the connected public endpoint; no vulnerability-free claim is made." + } + ], + "publication_performed": { + "pypi": false, + "testpypi": false, + "github_release": false, + "release_tags": false, + "wiki": false + }, + "manual_dispatch_executed": false, + "dependencies": { + "pytest": "9.1.1", + "coverage": "7.16.2", + "build": "1.6.1", + "setuptools": "80.9.0", + "wheel": "0.48.0", + "twine": "7.0.0", + "six": "1.17.0", + "PyYAML": "6.0.3" + } +} diff --git a/benchmarks/run_core.py b/benchmarks/run_core.py index 2d5d754..ab35fd1 100644 --- a/benchmarks/run_core.py +++ b/benchmarks/run_core.py @@ -25,18 +25,20 @@ def environment(): - cpu = 'unavailable' + cpu = platform.processor() or os.environ.get('PROCESSOR_IDENTIFIER') or 'unavailable' if Path('/proc/cpuinfo').exists(): cpu = next((line.split(':', 1)[1].strip() for line in Path('/proc/cpuinfo').read_text().splitlines() if line.startswith('model name')), cpu) + if cpu == 'unavailable' and platform.system() == 'Darwin': + cpu = subprocess.check_output(['sysctl', '-n', 'machdep.cpu.brand_string'], text=True).strip() machine = Path('/etc/machine-id') host = (machine.read_text().strip() if machine.exists() else '') + platform.node() return {'python': sys.version, 'implementation': platform.python_implementation(), 'platform': platform.platform(), 'architecture': platform.machine(), 'cpu': cpu, 'cpu_count': os.cpu_count(), 'host_id': hashlib.sha256(host.encode()).hexdigest() if host else None, - 'affinity': sorted(os.sched_getaffinity(0)) if hasattr(os, 'sched_getaffinity') else None, + 'affinity': sorted(os.sched_getaffinity(0)) if hasattr(os, 'sched_getaffinity') else 'unsupported', 'six': importlib.metadata.version('six')} diff --git a/docs/development/ci-release.md b/docs/development/ci-release.md new file mode 100644 index 0000000..0d61392 --- /dev/null +++ b/docs/development/ci-release.md @@ -0,0 +1,120 @@ +# CI and release-candidate review + +Ordinary pull requests and master pushes validate the package; they never upload +it. The matrix covers CPython 3.10–3.14 on Ubuntu, Windows and macOS. A configured +job is not evidence that a platform passed: consult its completed run and the +[verification report](../../audit/PHASE5B-CI-RELEASE.md). + +## Reproduce validation + +Install CI tools separately from gem's runtime dependency: + +```sh +python -m pip install -r requirements-ci.txt +python tools/check_workflows.py +python tools/ci_validate.py --output-dir /tmp/gem-ci --artifacts-dir dist +``` + +Use an empty artifact directory and an output directory outside the checkout. +The portable harness runs the complete suite, checks 268 API declarations and +43 executable examples, builds both distributions, runs strict Twine checks, +and installs each archive into a fresh isolated environment. It checks installed +runtime bytes and repeats the API/examples checks. Skips and expected failures +fail the candidate gate. Diagnostics remain available when a matrix job fails. + +The archive inspector rejects unexpected wheel contents, links, unsafe paths, +caches, temporary credential files and recognizable credential-shaped content. +This leak heuristic cannot prove the absence of every possible secret. + +## Manual candidate workflow + +Run **Release candidate validation** from master, supplying its full current +commit SHA, version `1.0.0`, proposed tag `v1.0.0` and mode `validate` or `review`. +The supplied SHA must equal the workflow's dispatched master SHA. A missing +proposed tag is allowed; no tag is created. An existing tag must resolve to the +same commit. Forks and other branches cannot enter candidate validation. + +`validate` runs the complete matrix and verifies the resulting candidate bundle. +`review` additionally requires the `gem-release-review` environment to have: + +- Required human reviewers with self-review prevented. +- Administrative approval bypass disabled. +- Deployment limited to protected branches, with master protected server-side. + +Configure these protections in repository settings before using review mode. +The preflight reads and validates the server configuration; a missing or +inaccessible protection fails closed. It does not create an environment or +change repository settings. Human approval occurs only after the matrix and +artifact integrity checks pass. Approval still enables **no publication**. + +Candidate artifacts contain the wheel, sdist, `SHA256SUMS`, installation instructions +and a manifest with source commit, +tree and SHA-256 digests. Only artifacts from the same workflow run are consumed. +Download digest verification is mandatory; the separate gate verifies archive +hashes, clean-source provenance, exact version and contents against the checked-out +candidate. Hashes detect corruption; a hash supplied by an untrusted producer +is not an independent authenticity guarantee. + +## GitHub-first release policy and publication checklist + +GitHub Release v1.0.0 preparation is independent of PyPI account recovery. +Publishing that release and creating/pushing its tag require final validation +and explicit owner authorization for each action. This workflow performs neither. +Preserve the approved wheel and sdist unchanged for eventual PyPI publication; +never silently rebuild official 1.0.0 artifacts. + +Current authority to publish the historical **gem** PyPI distribution remains +unverified. Repository access does not establish PyPI authority. Before a future, +separately reviewed publishing workflow is enabled, establish: + +- The authorized PyPI Owner/Maintainer account and its authority for `gem`. +- Maintainer approval of the exact artifacts, version and release commit. +- Account security requirements, including PyPI's applicable two-factor policy. +- An explicitly authorized trusted-publisher binding for this repository, + workflow and protected environment, if OIDC is selected. +- Protected branch/review settings and an approved release/tag procedure. + +No PyPI token is required or hardcoded. There is no PyPI/TestPyPI upload action, +OIDC package publishing permission, GitHub Release creation or tag creation in +this pipeline. Introducing any of them requires a separate reviewed change; +package identity must not be changed to bypass ownership verification. + +If validation fails, retain diagnostics, discard the candidate and rerun from a +reviewed clean commit after correction. Never upload a partially validated +bundle. If publication is enabled in a future phase, its failure/rollback policy +must account for immutable PyPI versions; do not assume deleting and reusing a +version is safe. + +## Documentation publication + +PR previews and Pages use the same strict documentation build, API/tutorial +checks and emitted-site link/anchor checks. Only the master deployment job has +Pages write/OIDC permissions. Documentation publication is independent of +package release. None of these workflows writes the GitHub Wiki. + +Action versions are pinned to reviewed full commit SHAs. Refresh pins through a +reviewed PR with action metadata and syntax verification, not floating tags. + +## Phase 5D GitHub publication workflow + +**GitHub release preparation** defaults to `validate`. Supply the final master +SHA and successful manual candidate-validation run ID. The workflow downloads +those exact assets rather than rebuilding, verifies run provenance and hashes, +checks the proposed tag, and rejects an existing release. Archive retention is +14 days; if expired, validate a new candidate before official publication. +Preserve official assets durably once released. + +Phase 5B tests preparation only. Phase 5C performs final validation. In Phase 5D, +separate explicit owner authorization must precede setting repository variable +`GEM_RELEASE_ENABLED=true` and dispatching mode `publish` with the exact +phrase `publish v1.0.0`. The protected environment requires human approval. +Only this publish job has contents-write permission. It rechecks provenance and +protections before creating the tag/release at the exact reviewed commit and +attaching the original archives, checksums and installation instructions. +Prepared [release notes](release-notes-1.0.0.md) describe compatibility and limits. + +Leave the switch unset/false during preparation. A dry run creates no tag or +release. If a future authorized upload fails partway, inspect the existing +release/tag and assets before any recovery; this workflow refuses overwrites. +Never silently replace official artifacts. PyPI remains separately disabled, +regardless of GitHub publication approval. diff --git a/docs/development/release-notes-1.0.0.md b/docs/development/release-notes-1.0.0.md new file mode 100644 index 0000000..ac4e116 --- /dev/null +++ b/docs/development/release-notes-1.0.0.md @@ -0,0 +1,32 @@ +# gem 1.0.0 release notes (prepared) + +Pure-Python graphics mathematics: vectors, matrices, quaternions, planes, rays, +Bezier curves, Legendre polynomials and spherical harmonics. The gem 1.0 +mathematical API is frozen, with 268 documented declarations and 43 executable +examples. Matrices use row-major storage and row-vector transformations; +quaternions use [w,x,y,z] and Hamilton multiplication. + +The release strategy is GitHub-first. This document prepares release notes; +it does not establish that a release has been published. PyPI 1.0.0 publication +is deferred until publishing authority is verified. + +CPython 3.10–3.14 are the supported interpreter targets. Completed CI runs must +establish platform verification before release; workflow configuration alone +is not evidence of Windows/macOS support. Python 2.7 is unsupported. + +Download the approved wheel or sdist, verify SHA-256 checksums, then install +with `python -m pip install ./gem-1.0.0-py3-none-any.whl` or the corresponding +sdist path. Do not use `pip install gem==1.0.0` as GitHub installation guidance. +The only mandatory runtime dependency is six. No OpenGL context or compiled +numerical backend is required. Optional example/documentation dependencies +are separate. + +Historical experimental import shims remain transitional; canonical modules +are gem.bezier, gem.legendre and gem.spherical_harmonics. Unfinished shadow +transport is retired. Numerical guarantees remain subject to binary64 limits +and documented domains; SH lighting is a low-order approximation, not a +visibility or rendering engine. See the mathematical conventions and +compatibility documentation for detailed contracts. + +Preserve the approved artifacts unchanged for eventual PyPI upload. Do not +rebuild or overwrite official release assets silently. diff --git a/docs/development/releases.md b/docs/development/releases.md index 6cd3e39..9193e2e 100644 --- a/docs/development/releases.md +++ b/docs/development/releases.md @@ -8,8 +8,9 @@ to use the current implementation. The [packaging guide](packaging.md) describes the CPython 3.10–3.14 Linux test matrix, build commands, version policy and artifact contents. Historical package association is established; current PyPI maintainer authority and publishing -credentials still need verification before any upload. No publishing workflow, -release tag or ownership change is included. +credentials still need verification before any upload. The [CI and release-candidate guide](ci-release.md) describes manual validation +and protected human review. Publication remains disabled; no release tag or +ownership change is included. ## Change history diff --git a/mkdocs.yml b/mkdocs.yml index 57f38fd..17f0c07 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -134,6 +134,8 @@ nav: - Contributing: development/contributing.md - Release status: development/releases.md - Packaging and Python support: development/packaging.md + - CI and release safeguards: development/ci-release.md + - Prepared 1.0.0 release notes: development/release-notes-1.0.0.md - Verification workflow: development/verification.md - Documentation build: development/website.md - Stack and licensing: development/documentation-stack.md diff --git a/requirements-ci.txt b/requirements-ci.txt new file mode 100644 index 0000000..a00ec3e --- /dev/null +++ b/requirements-ci.txt @@ -0,0 +1,5 @@ +# CI/development tools only; no installed gem runtime dependencies. +-r requirements-audit.txt +-r requirements-build.txt +six==1.17.0 +PyYAML==6.0.3 diff --git a/tests/test_documentation_overhaul.py b/tests/test_documentation_overhaul.py index 4fdb66d..e5b978f 100644 --- a/tests/test_documentation_overhaul.py +++ b/tests/test_documentation_overhaul.py @@ -17,8 +17,8 @@ def checker(tmp_path, monkeypatch): for name, text in {'gem/example.py': 'VALUE = 1\n', 'setup.py': '# metadata\n', 'tests/existing.py': '# retained test\n', 'docs/tutorials/vectors.md': '# Tutorial\n\n```python\nx = 1\nassert x == 1\n```\n'}.items(): - path = tmp_path/name; path.parent.mkdir(parents=True, exist_ok=True); path.write_text(text) - for args in (['init'], ['add', '.'], ['-c', 'user.name=Fixture', '-c', + path = tmp_path/name; path.parent.mkdir(parents=True, exist_ok=True); path.write_bytes(text.encode()) + for args in (['init'], ['config', 'core.autocrlf', 'false'], ['add', '.'], ['-c', 'user.name=Fixture', '-c', 'user.email=fixture@example.invalid', 'commit', '-m', 'fixture']): subprocess.run(['git', *args], cwd=tmp_path, capture_output=True, check=True) base = subprocess.check_output(['git', 'rev-parse', 'HEAD'], cwd=tmp_path, text=True).strip() @@ -85,7 +85,7 @@ def wiki_update(tmp_path, monkeypatch): for name, text in {'Home.md':'Old portal\n', 'Historical-Home.md':'Historical text\n', 'Matrix-Class.md':'Historical mathematics\n'}.items(): (wiki/name).write_text(text) - for args in (['init'], ['add', '.'], ['-c', 'user.name=Fixture', '-c', + for args in (['init'], ['config', 'core.autocrlf', 'false'], ['add', '.'], ['-c', 'user.name=Fixture', '-c', 'user.email=fixture@example.invalid', 'commit', '-m', 'fixture']): subprocess.run(['git', *args], cwd=wiki, capture_output=True, check=True) package = tmp_path/'package'; pages = package/'pages'; pages.mkdir(parents=True) diff --git a/tests/test_release_packaging.py b/tests/test_release_packaging.py index 3f72424..4057f43 100644 --- a/tests/test_release_packaging.py +++ b/tests/test_release_packaging.py @@ -38,7 +38,7 @@ def test_metadata_changes_do_not_modify_frozen_mathematics(): import subprocess base='15fbce6' for path in (ROOT/'gem').rglob('*.py'): - name=str(path.relative_to(ROOT)) + name=path.relative_to(ROOT).as_posix() if name in ('gem/__init__.py','gem/_version.py'): continue assert path.read_bytes() == subprocess.check_output(['git','show',base+':'+name],cwd=ROOT) @@ -48,8 +48,9 @@ def test_packaging_manifest_rejects_unreviewed_content(tmp_path,monkeypatch,name spec=importlib.util.spec_from_file_location('scope_guard',ROOT/'tools/check_architecture_docs.py') tool=importlib.util.module_from_spec(spec);spec.loader.exec_module(tool) import subprocess - (tmp_path/'gem').mkdir();(tmp_path/'gem/original.py').write_text('VALUE=1\n') + (tmp_path/'gem').mkdir();(tmp_path/'gem/original.py').write_bytes(b'VALUE=1\n') subprocess.run(['git','init'],cwd=tmp_path,check=True,capture_output=True) + subprocess.run(['git','config','core.autocrlf','false'],cwd=tmp_path,check=True) subprocess.run(['git','add','.'],cwd=tmp_path,check=True) subprocess.run(['git','-c','user.name=Fixture','-c','user.email=f@example.invalid','commit','-m','fixture'],cwd=tmp_path,check=True,capture_output=True) base=subprocess.check_output(['git','rev-parse','HEAD'],cwd=tmp_path,text=True).strip() diff --git a/tests/test_release_workflows.py b/tests/test_release_workflows.py new file mode 100644 index 0000000..ba5c104 --- /dev/null +++ b/tests/test_release_workflows.py @@ -0,0 +1,266 @@ +"""Independent negative checks for CI and release-candidate boundaries.""" +import copy +import importlib.util +from pathlib import Path +import subprocess + +import pytest + +ROOT = Path(__file__).resolve().parents[1] + + +def load(name): + spec = importlib.util.spec_from_file_location(name, ROOT / 'tools' / (name + '.py')) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module + + +gate = load('release_gate') +ci = load('ci_validate') +archives = load('verify_distribution') +workflows = load('check_workflows') +SHA = 'a' * 40 +CONTEXT = dict(event_name='workflow_dispatch', repository=gate.REPOSITORY, + fork=False, ref='refs/heads/master', sha=SHA) + + +def test_manual_validation_never_enables_publication(): + for mode in ('validate', 'review'): + result = gate.validate_context(CONTEXT, SHA, '1.0.0', 'v1.0.0', mode) + assert result['publication_enabled'] is False + assert result['ownership_verified'] is False + + +@pytest.mark.parametrize('field,value', [ + ('event_name', 'push'), ('event_name', 'pull_request'), + ('repository', 'someone/pyGameMath'), ('fork', True), + ('ref', 'refs/heads/topic'), ('sha', 'b' * 40)]) +def test_reject_unreviewed_context(field, value): + context = dict(CONTEXT, **{field: value}) + with pytest.raises(ValueError): + gate.validate_context(context, SHA, '1.0.0', 'v1.0.0', 'validate') + + +@pytest.mark.parametrize('commit,version,tag,mode', [ + ('a' * 7, '1.0.0', 'v1.0.0', 'validate'), + (SHA.upper(), '1.0.0', 'v1.0.0', 'validate'), + (SHA, '1.0.1', 'v1.0.0', 'validate'), + (SHA, '1.0.0', 'v1.0.1', 'validate'), + (SHA, '1.0.0', 'v1.0.0', 'publish')]) +def test_reject_unapproved_candidate(commit, version, tag, mode): + with pytest.raises(ValueError): + gate.validate_context(CONTEXT, commit, version, tag, mode) + + +def protected_environment(): + return dict(name=gate.ENVIRONMENT, can_admins_bypass=False, + protection_rules=[dict(type='required_reviewers', reviewers=[{'id': 1}], + prevent_self_review=True)], + deployment_branch_policy=dict(protected_branches=True, + custom_branch_policies=False)) + + +@pytest.mark.parametrize('defect', ['reviewers', 'self_review', 'bypass', 'branches', 'protection']) +def test_review_requires_server_side_protection(defect): + environment = protected_environment() + branch = {'protected': True} + assert gate.verify_review_configuration(environment, branch)['required_reviewers'] + if defect == 'reviewers': environment['protection_rules'][0]['reviewers'] = [] + if defect == 'self_review': environment['protection_rules'][0]['prevent_self_review'] = False + if defect == 'bypass': environment['can_admins_bypass'] = True + if defect == 'branches': environment['deployment_branch_policy']['protected_branches'] = False + if defect == 'protection': branch['protected'] = False + with pytest.raises(ValueError): + gate.verify_review_configuration(environment, branch) + + +@pytest.mark.parametrize('returncode', [0, 1, 2]) +def test_tag_detection_fails_closed(monkeypatch, returncode): + monkeypatch.setattr(gate.subprocess, 'check_output', lambda *a, **k: SHA + '\n') + monkeypatch.setattr(gate.subprocess, 'run', lambda *a, **k: subprocess.CompletedProcess(a, returncode)) + if returncode == 2: + with pytest.raises(ValueError): gate.preflight(CONTEXT, SHA, '1.0.0', 'v1.0.0', 'validate') + else: + result = gate.preflight(CONTEXT, SHA, '1.0.0', 'v1.0.0', 'validate') + assert ('absent' in result['tag_state']) == (returncode == 1) + + +def test_cross_platform_environment_paths(tmp_path): + assert ci.environment_python(tmp_path, 'nt') == tmp_path / 'Scripts/python.exe' + assert ci.environment_python(tmp_path, 'posix') == tmp_path / 'bin/python' + + +@pytest.mark.parametrize('child,tests', [('', 1), ('', 1), ('', 1), + ('', 1), ('', 0), ('', 2)]) +def test_junit_gate_checks_actual_cases(tmp_path, child, tests): + path = tmp_path / 'results.xml' + path.write_text('%s' % (tests, child)) + if tests == 1 and not child: + assert ci.assert_clean_junit(path)['passed'] == 1 + else: + with pytest.raises(ValueError): ci.assert_clean_junit(path) + + +@pytest.mark.parametrize('name', ['../secret', '/absolute', 'gem/../secret', 'gem//bad', + 'gem\\bad', '.git/config', 'gem/__pycache__/a.pyc', + 'dist/a.whl', '.env', 'secret.pem', 'audit/cache.tmp']) +def test_unsafe_archive_members(name): + with pytest.raises(ValueError): archives.check_member(name) + + +def test_credential_guard_does_not_echo_credentials(): + token = b'pypi-' + b'x' * 60 + with pytest.raises(ValueError) as error: archives.check_member('example.txt', token) + assert token.decode() not in str(error.value) + archives.check_member('tools/site/check_site.py', b'ordinary source') + + +def test_reviewed_workflow_inventory(): + result = workflows.validate(workflows.load_workflows()) + assert result['matrix_jobs'] == 15 + assert result['package_publication_enabled'] is False + + +@pytest.mark.parametrize('defect', ['permissions', 'event', 'pin', 'mode', 'review', 'matrix']) +def test_workflow_policy_rejects_bypasses(defect): + data = copy.deepcopy(workflows.load_workflows()) + release = data['release-validation.yml'] + if defect == 'permissions': release['permissions']['contents'] = 'write' + if defect == 'event': release['on']['push'] = None + if defect == 'pin': release['jobs']['preflight']['steps'][0]['uses'] = 'actions/checkout@main' + if defect == 'mode': release['on']['workflow_dispatch']['inputs']['mode']['options'].append('publish') + if defect == 'review': release['jobs']['review']['environment'] = 'unprotected' + if defect == 'matrix': data['package-validation.yml']['jobs']['package']['strategy']['matrix']['python'].pop() + with pytest.raises(ValueError): workflows.validate(data) + + +release = load('github_release') + + +@pytest.mark.parametrize('enabled,approved', [('false', 'publish v1.0.0'), + ('true', ''), (None, None)]) +def test_github_publication_requires_explicit_enablement(enabled, approved): + with pytest.raises(ValueError): + release.validate_publication(CONTEXT, enabled, approved, 'publish') + release.validate_publication(CONTEXT, enabled, approved, 'validate') + + +def test_github_publication_requires_canonical_context(): + release.validate_publication(CONTEXT, 'true', 'publish v1.0.0', 'publish') + with pytest.raises(ValueError): + release.validate_publication(dict(CONTEXT, fork=True), 'true', 'publish v1.0.0', 'publish') + + +@pytest.mark.parametrize('field,value', [('head_sha', 'b' * 40), ('event', 'push'), + ('head_branch', 'topic'), ('conclusion', 'failure'), + ('status', 'in_progress'), ('path', 'other.yml')]) +def test_github_release_requires_exact_successful_candidate_run(field, value): + run = dict(id=123, repository={'full_name': gate.REPOSITORY}, + path='.github/workflows/release-validation.yml', event='workflow_dispatch', + head_branch='master', head_sha=SHA, status='completed', conclusion='success') + release.validate_run(run, SHA, '123') + run[field] = value + with pytest.raises(ValueError): release.validate_run(run, SHA, '123') + + +def test_github_publication_guard_cannot_be_removed(): + data = workflows.load_workflows() + data['github-release.yml']['jobs']['publish']['if'] = "inputs.mode == 'publish'" + with pytest.raises(ValueError): workflows.validate(data) + + +@pytest.mark.parametrize('mode', ['validate', 'publish']) +def test_release_execution_uses_exact_assets_and_no_dry_run_mutation(tmp_path, monkeypatch, mode): + import json + import urllib.error + event = tmp_path / 'event.json' + event.write_text(json.dumps({'repository': {'fork': False}})) + for key, value in {'GITHUB_EVENT_PATH': str(event), 'GITHUB_EVENT_NAME': 'workflow_dispatch', + 'GITHUB_REPOSITORY': gate.REPOSITORY, 'GITHUB_REF': 'refs/heads/master', + 'GITHUB_SHA': SHA, 'RELEASE_ENABLED': 'true', + 'OWNER_AUTHORIZATION': 'publish v1.0.0'}.items(): + monkeypatch.setenv(key, value) + run = dict(id=123, repository={'full_name': gate.REPOSITORY}, + path='.github/workflows/release-validation.yml', event='workflow_dispatch', + head_branch='master', head_sha=SHA, status='completed', conclusion='success') + def get(path): + if path == 'actions/runs/123': return run + if path.startswith('environments/'): return protected_environment() + if path == 'branches/master': return {'protected': True} + if path == 'git/ref/tags/v1.0.0' and commands: + return {'object': {'type': 'commit', 'sha': SHA}} + raise urllib.error.HTTPError('https://api.github.com', 404, 'absent', {}, None) + monkeypatch.setattr(release.gate, 'github_get', get) + preflights = [] + monkeypatch.setattr(release.gate, 'preflight', lambda *a: preflights.append(a)) + monkeypatch.setattr(release.gate, 'verify_artifacts', lambda *a: {'publication_enabled': False}) + commands = [] + monkeypatch.setattr(release.subprocess, 'run', lambda argv, **kw: commands.append(argv)) + monkeypatch.setattr('sys.argv', ['github_release.py', '--directory', str(tmp_path), + '--commit', SHA, '--run-id', '123', '--mode', mode, + '--output', str(tmp_path / 'report.json')]) + release.main() + assert len(preflights) == 1 + if mode == 'validate': + assert commands == [] + else: + assert len(commands) == 2 + assert commands[0][-2:] == ['-f', 'sha=' + SHA] + command = commands[-1] + assert command[:4] == ['gh', 'release', 'create', 'v1.0.0'] + assert command[command.index('--target') + 1] == SHA + assert set(command[-4:]) == {str(tmp_path / n) for n in gate.FILES | {'SHA256SUMS', 'INSTALL.md'}} + assert '--clobber' not in command and '--verify-tag' in command + + +def test_ci_scope_cannot_exempt_mathematics(tmp_path, monkeypatch): + checker = load('check_architecture_docs') + import json + (tmp_path / 'tools').mkdir() + (tmp_path / 'tools/phase5b-ci-scope.json').write_text(json.dumps( + {'base': checker.BASE, 'files': {'gem/vector.py': '0' * 64}})) + monkeypatch.setattr(checker, 'ROOT', tmp_path) + monkeypatch.setattr(checker.subprocess, 'check_output', lambda *a, **k: '') + with pytest.raises(ValueError, match='mathematics cannot be exempted'): + checker.check_scope() + + +@pytest.mark.parametrize('kind', ['commit', 'tag']) +def test_remote_release_tag_must_resolve_to_approved_commit(monkeypatch, kind): + def get(path): + return {'object': {'type': 'commit' if path.startswith('git/tags/') else kind, + 'sha': 'b' * 40}} + monkeypatch.setattr(release.gate, 'github_get', get) + with pytest.raises(ValueError, match='another commit'): + release.remote_tag_matches(SHA) + + +@pytest.mark.parametrize('case,reason', [ + ('extra_file', 'candidate bundle'), ('dirty_source', 'dirty source'), + ('wrong_commit', 'source commit'), ('wrong_tree', 'checkout/tree'), + ('wrong_version', 'version/tag'), ('digest', 'digest mismatch'), + ('checksums', 'checksum file'), ('empty_instructions', 'instructions missing')]) +def test_candidate_integrity_rejects_tampering(tmp_path, monkeypatch, case, reason): + import hashlib + import json + tree = 'c' * 40 + files = {name: hashlib.sha256(name.encode()).hexdigest() for name in gate.FILES} + manifest = dict(schema_version=1, distribution='gem', version='1.0.0', tag='v1.0.0', + source_commit=SHA, source_tree=tree, source_clean=True, files=files) + for name in files: (tmp_path / name).write_bytes(name.encode()) + (tmp_path / 'SHA256SUMS').write_text(''.join(d + ' ' + n + '\n' for n, d in sorted(files.items()))) + (tmp_path / 'INSTALL.md').write_text('Installation instructions') + if case == 'extra_file': (tmp_path / 'extra.txt').write_text('unexpected') + if case == 'dirty_source': manifest['source_clean'] = False + if case == 'wrong_commit': manifest['source_commit'] = 'b' * 40 + if case == 'wrong_tree': manifest['source_tree'] = 'b' * 40 + if case == 'wrong_version': manifest['version'] = '1.0.1' + if case == 'digest': files[next(iter(files))] = '0' * 64 + if case == 'checksums': (tmp_path / 'SHA256SUMS').write_text('wrong') + if case == 'empty_instructions': (tmp_path / 'INSTALL.md').write_text('') + (tmp_path / 'candidate-manifest.json').write_text(json.dumps(manifest)) + monkeypatch.setattr(gate.subprocess, 'check_output', + lambda argv, **kwargs: (tree if argv[-1] == 'HEAD^{tree}' else SHA) + '\n') + with pytest.raises(ValueError, match=reason): + gate.verify_artifacts(tmp_path, SHA, 'v1.0.0') diff --git a/tools/check_architecture_docs.py b/tools/check_architecture_docs.py index fc6ba2b..54e67e1 100644 --- a/tools/check_architecture_docs.py +++ b/tools/check_architecture_docs.py @@ -27,7 +27,7 @@ def declarations(): result = {} for source in sorted((ROOT/'gem').glob('*.py')): rows = [] - for node in ast.parse(source.read_text()).body: + for node in ast.parse(source.read_text(encoding='utf-8')).body: if isinstance(node, ast.FunctionDef) and not node.name.startswith('_'): rows.append(node.name+'('+ast.unparse(node.args)+')') elif isinstance(node, ast.ClassDef) and not node.name.startswith('_'): @@ -64,7 +64,7 @@ def check_scope(): Runtime mathematics remains frozen; arbitrary additions remain rejected. """ paths = subprocess.check_output(['git', 'ls-tree', '-r', '--name-only', BASE], - cwd=ROOT, text=True).splitlines() + cwd=ROOT, text=True, encoding='utf-8').splitlines() def protected(name): return name.startswith(('gem/', 'tests/', 'examples/', 'benchmarks/')) or name in ( 'setup.py', 'setup.cfg', 'MANIFEST.in', 'LICENSE', 'README.rst', @@ -72,12 +72,25 @@ def protected(name): '.travis.yml') originals = {name for name in paths if protected(name)} current = subprocess.check_output(['git', 'ls-files', '--cached', '--others', - '--exclude-standard'], cwd=ROOT, text=True).splitlines() + '--exclude-standard'], cwd=ROOT, text=True, encoding='utf-8').splitlines() additions = {name for name in current if protected(name)}-originals + ci_reviewed = {} + ci_manifest = ROOT/'tools/phase5b-ci-scope.json' + if ci_manifest.exists(): + ci_scope = json.loads(ci_manifest.read_text(encoding='utf-8')) + if ci_scope['base'] != BASE: + raise ValueError('CI scope baseline mismatch') + ci_reviewed = ci_scope['files'] + if set(ci_reviewed)-{'tests/test_documentation_overhaul.py', + 'tests/test_release_packaging.py', 'benchmarks/run_core.py'}: + raise ValueError('mathematics cannot be exempted by CI scope') + for name, digest in ci_reviewed.items(): + if hashlib.sha256((ROOT/name).read_bytes()).hexdigest() != digest: + raise ValueError('unreviewed CI portability change: '+name) reviewed = {} manifest = ROOT/'tools/phase5a-packaging-scope.json' if manifest.exists(): - scope = json.loads(manifest.read_text()) + scope = json.loads(manifest.read_text(encoding='utf-8')) if scope['base'] != BASE: raise ValueError('packaging scope baseline mismatch') reviewed = scope['files'] @@ -88,33 +101,35 @@ def protected(name): if set(reviewed)-permitted: raise ValueError('mathematics cannot be exempted by packaging scope') for name, digest in reviewed.items(): - if hashlib.sha256((ROOT/name).read_bytes()).hexdigest() != digest: + if name not in ci_reviewed and hashlib.sha256((ROOT/name).read_bytes()).hexdigest() != digest: raise ValueError('unreviewed packaging change: '+name) - allowed = {'tests/test_documentation_overhaul.py'} | set(reviewed) + allowed = {'tests/test_documentation_overhaul.py', + 'tests/test_release_workflows.py'} | set(reviewed) if additions-allowed: raise ValueError('out-of-scope addition: '+', '.join(sorted(additions-allowed))) fingerprint = hashlib.sha256() for name in sorted(originals): content = (ROOT/name).read_bytes() - if name not in reviewed and content != subprocess.check_output(['git', 'show', BASE+':'+name], cwd=ROOT): + if name not in reviewed and name not in ci_reviewed and content != subprocess.check_output(['git', 'show', BASE+':'+name], cwd=ROOT): raise ValueError('out-of-scope modification: '+name) fingerprint.update(name.encode()); fingerprint.update(content) - return {'protected_files_unchanged': len(originals-set(reviewed)), + return {'protected_files_unchanged': len(originals-set(reviewed)-set(ci_reviewed)), 'protected_sha256': fingerprint.hexdigest(), 'new_documentation_test_modules': sorted(additions), + 'reviewed_ci_files': sorted(ci_reviewed), 'reviewed_packaging_files': sorted(reviewed)} def preserve_learning_material(pages): """Keep every baseline executable block in order, permitting comments only.""" baseline = set(subprocess.check_output(['git', 'ls-tree', '-r', '--name-only', BASE], - cwd=ROOT, text=True).splitlines()) + cwd=ROOT, text=True, encoding='utf-8').splitlines()) blocks = tutorials = old_anchors = 0 for name in pages: if name not in baseline: continue - previous = subprocess.check_output(['git', 'show', BASE+':'+name], cwd=ROOT, text=True) - current = (ROOT/name).read_text() + previous = subprocess.check_output(['git', 'show', BASE+':'+name], cwd=ROOT, text=True, encoding='utf-8') + current = (ROOT/name).read_text(encoding='utf-8') required = anchors(re.sub(r'```.*?```', '', previous, flags=re.DOTALL)) present = anchors(re.sub(r'```.*?```', '', current, flags=re.DOTALL)) if required-present: @@ -140,7 +155,7 @@ def preserve_learning_material(pages): def check_api_reference(declared, runtime=False): """Check declaration-to-reference coverage, constants and installed signatures.""" - coverage = json.loads((ROOT/'docs/api/coverage.json').read_text()) + coverage = json.loads((ROOT/'docs/api/coverage.json').read_text(encoding='utf-8')) entries = coverage['declarations'] if set(entries) != set(declared) or coverage['omitted_declarations']: raise ValueError('API reference module coverage mismatch') @@ -154,7 +169,7 @@ def check_api_reference(declared, runtime=False): page = (ROOT/entry['page']).resolve() if not page.is_relative_to(ROOT/'docs/api'): raise ValueError('API coverage page escapes reference directory') - text = page.read_text() + text = page.read_text(encoding='utf-8') if text.count('| `'+signature+'` |') != 1: raise ValueError('missing/duplicate API declaration row: '+signature) description = text.split('| `'+signature+'` |', 1)[1].split('\n', 1)[0].strip(' |') @@ -182,7 +197,7 @@ def check_api_reference(declared, runtime=False): discovered = {} for source in sorted((ROOT/'gem').glob('*.py')): names = [] - for node in ast.parse(source.read_text()).body: + for node in ast.parse(source.read_text(encoding='utf-8')).body: if isinstance(node, ast.Assign): names.extend(target.id for target in node.targets if isinstance(target, ast.Name) and not target.id.startswith('_')) @@ -222,18 +237,18 @@ def check(examples=False, getting_started=False, package_root=None, api_referenc for name in ('README', 'installation', 'quick-start', 'verification')] if getting_started else []) if api_reference: - pages += [str(page.relative_to(ROOT)) for page in sorted((ROOT/'docs/api').glob('*.md'))] + pages += [page.relative_to(ROOT).as_posix() for page in sorted((ROOT/'docs/api').glob('*.md'))] if tutorials: - pages += [str(page.relative_to(ROOT)) for page in sorted((ROOT/'docs/tutorials').glob('*.md'))] + pages += [page.relative_to(ROOT).as_posix() for page in sorted((ROOT/'docs/tutorials').glob('*.md'))] if showcase: - pages += [str(page.relative_to(ROOT)) for page in sorted((ROOT/'docs/examples').rglob('*.md'))] + pages += [page.relative_to(ROOT).as_posix() for page in sorted((ROOT/'docs/examples').rglob('*.md'))] pages += ['examples/showcase/README.md'] if website: pages += ['docs/index.md', 'docs/architecture/notation.md', 'docs/wiki-migration/README.md'] pages += ['docs/development/'+name+'.md' for name in ('documentation-stack', 'contributing', 'releases', 'website', 'website-verification', 'wiki', 'visual-overhaul')] report = {'base': BASE, 'pages': pages, 'links_checked': 0, 'source_declarations_checked': 0, 'examples_executed': 0, 'examples_by_page': {}} - inventory = (ROOT/'docs/architecture/api-inventory.md').read_text() + inventory = (ROOT/'docs/architecture/api-inventory.md').read_text(encoding='utf-8') declared = declarations() for module, rows in declared.items(): heading = '### '+module+' declarations' @@ -259,7 +274,7 @@ def check(examples=False, getting_started=False, package_root=None, api_referenc report.update(preserve_learning_material(pages)) for relative in pages: page = ROOT/relative - text = page.read_text() + text = page.read_text(encoding='utf-8') # Exclude fenced code before interpreting Markdown links/headings. prose = re.sub(r'```.*?```', '', text, flags=re.DOTALL) for target in re.findall(r'!?\[[^\]]*\]\(([^)]+)\)', prose): @@ -270,7 +285,7 @@ def check(examples=False, getting_started=False, package_root=None, api_referenc if not dest.is_relative_to(ROOT) or not dest.exists(): raise ValueError(relative+': missing/internal escape link '+target) if parts.fragment: - if not dest.is_file() or unquote(parts.fragment) not in anchors(dest.read_text()): + if not dest.is_file() or unquote(parts.fragment) not in anchors(dest.read_text(encoding='utf-8')): raise ValueError(relative+': missing anchor '+target) report['links_checked'] += 1 if examples: diff --git a/tools/check_workflows.py b/tools/check_workflows.py new file mode 100644 index 0000000..1d17f1c --- /dev/null +++ b/tools/check_workflows.py @@ -0,0 +1,131 @@ +"""Validate repository-specific CI safety invariants (PyYAML is a CI-only tool).""" +import argparse +import copy +import json +from pathlib import Path +import re + +import yaml + +ROOT=Path(__file__).resolve().parents[1] + + +class WorkflowLoader(yaml.SafeLoader): + pass + + +# YAML 1.2 keeps the workflow key `on` as text, unlike PyYAML's default YAML 1.1. +WorkflowLoader.yaml_implicit_resolvers=copy.deepcopy(yaml.SafeLoader.yaml_implicit_resolvers) +for key,rules in WorkflowLoader.yaml_implicit_resolvers.items(): + WorkflowLoader.yaml_implicit_resolvers[key]=[(tag,regex) for tag,regex in rules + if tag!='tag:yaml.org,2002:bool'] +WorkflowLoader.add_implicit_resolver('tag:yaml.org,2002:bool',re.compile(r'^(?:true|false|True|False|TRUE|FALSE)$'),list('tTfF')) + + +def load_workflows(root=ROOT): + return {p.name:yaml.load(p.read_text(encoding='utf-8'),Loader=WorkflowLoader) + for p in (root/'.github/workflows').glob('*.yml')} + + +def require(condition,message): + if not condition:raise ValueError(message) + + +def validate(workflows): + expected={'package-validation.yml','documentation-artifact.yml','publish-docs.yml','release-validation.yml','github-release.yml'} + require(set(workflows)==expected,'workflow inventory differs from reviewed CI baseline') + pins=[] + for name,workflow in workflows.items(): + require(workflow.get('permissions')=={'contents':'read'},'default permissions must be read-only: '+name) + require(not {'pull_request_target','workflow_run','release'} & set(workflow['on']),'unsafe/unreviewed event: '+name) + for job_name,job in workflow['jobs'].items(): + require(not job.get('continue-on-error'),'failure gate bypass: '+name) + permissions=job.get('permissions',workflow['permissions']) + expected_permissions={'contents':'read'} + if name=='publish-docs.yml' and job_name=='deploy': + expected_permissions.update({'pages':'write','id-token':'write'}) + elif name=='release-validation.yml' and job_name=='preflight': + expected_permissions['actions']='read' + elif name=='github-release.yml': + expected_permissions['actions']='read' + if job_name=='publish':expected_permissions['contents']='write' + require(permissions==expected_permissions,'unexpected privileged permissions: '+name+'/'+job_name) + require(job.get('secrets')!='inherit','secrets inheritance is forbidden') + if 'uses' in job: + require(job['uses'] in {'./.github/workflows/package-validation.yml', + './.github/workflows/documentation-artifact.yml'},'unreviewed reusable workflow') + for step in job.get('steps',[]): + require(not step.get('continue-on-error'),'step failure bypass') + if 'uses' in step: + require(re.fullmatch(r'[\w-]+/[\w-]+@[0-9a-f]{40}',step['uses']) is not None,'actions must use full commit pins') + require('pypi-publish' not in step['uses'],'package publication must remain disabled') + pins.append(step['uses']) + if step['uses'].startswith('actions/checkout@'): + require(step.get('with',{}).get('persist-credentials') is False,'checkout credentials must not persist') + command=step.get('run','') + require(not re.search(r'twine\s+upload|gh\s+release\s+create|git\s+(?:push|tag)\b|prepare_wiki',command), + 'publication/tag/wiki mutation command in active workflow') + require('secrets.' not in str(step),'no publishing secret wiring is permitted') + package=workflows['package-validation.yml'] + require(set(package['on'])=={'pull_request','push','workflow_dispatch','workflow_call'},'package events changed') + require(package['on']['push'].get('branches')==['master'],'package push must target master') + matrix=package['jobs']['package']['strategy']['matrix'] + require(matrix=={'os':['ubuntu-latest','windows-latest','macos-latest'], + 'python':['3.10','3.11','3.12','3.13','3.14']},'complete Python/platform matrix is required') + require(package['jobs']['package']['strategy'].get('fail-fast') is False,'all platforms must report') + require(any('tools/ci_validate.py' in s.get('run','') for s in package['jobs']['package']['steps']), + 'shared packaging validation missing') + docs=workflows['documentation-artifact.yml'];steps=docs['jobs']['build']['steps'] + commands=[s.get('run','') for s in steps] + require(any('check_architecture_docs.py' in c and '--api-reference' in c and '--tutorials' in c for c in commands), + 'API/tutorial checks missing') + require(any('mkdocs build --strict' in c for c in commands) and any('check_site.py' in c for c in commands), + 'strict emitted-site checks missing') + upload=next(i for i,s in enumerate(steps) if s.get('uses','').startswith('actions/upload-pages-artifact@')) + require(all(i> 16) & 0o170000 == 0o120000: + raise ValueError('symlink in wheel: '+info.filename) + if info.is_dir(): continue + if info.filename in files: raise ValueError('duplicate wheel member: '+info.filename) + content=archive.read(info); check_member(info.filename,content) + files[info.filename]=content with tarfile.open(sdist) as archive: - source = {member.name.split('/',1)[1]: archive.extractfile(member).read() - for member in archive.getmembers() if member.isfile()} + source = {} + for member in archive.getmembers(): + check_member(member.name) + if not member.name.startswith('gem-1.0.0/') and member.name != 'gem-1.0.0': + raise ValueError('unexpected sdist root: '+member.name) + if member.isdir(): continue + if not member.isfile(): raise ValueError('link/special member in sdist: '+member.name) + name=member.name.split('/',1)[1] + if name in source: raise ValueError('duplicate source member: '+name) + content=archive.extractfile(member).read(); check_member(name,content) + source[name]=content for name, content in runtime.items(): assert files[name] == source[name] == content, name assert {n for n in files if n.startswith('gem/')} == set(runtime) + allowed_metadata={'METADATA','WHEEL','RECORD','top_level.txt','licenses/LICENSE'} + assert set(files) == set(runtime) | {'gem-1.0.0.dist-info/'+n for n in allowed_metadata} for names in (files, source): assert not any('__pycache__' in n or n.endswith(('.pyc','.pyo')) for n in names) assert not any(n.startswith(('build/', 'dist/', 'site/', '.venv', '.git/')) for n in names)