From 64020a7987159da727740215fd27db03115071a8 Mon Sep 17 00:00:00 2001
From: Alex M <9vy6rqx2p8@privaterelay.appleid.com>
Date: Sat, 10 Oct 2026 01:34:49 +0000
Subject: [PATCH 01/12] ci: validate release candidates across supported
platforms
Add guarded GitHub-first release preparation and artifact integrity checks. Keep PyPI publication disabled.
Assisted-by: OpenAI Codex
---
.github/workflows/documentation-artifact.yml | 26 ++-
.github/workflows/github-release.yml | 88 ++++++++++
.github/workflows/package-validation.yml | 61 +++++--
.github/workflows/publish-docs.yml | 36 ++--
.github/workflows/release-validation.yml | 101 +++++++++++
docs/development/ci-release.md | 120 +++++++++++++
docs/development/release-notes-1.0.0.md | 32 ++++
docs/development/releases.md | 5 +-
mkdocs.yml | 1 +
requirements-ci.txt | 5 +
tests/test_release_workflows.py | 170 +++++++++++++++++++
tools/check_architecture_docs.py | 3 +-
tools/check_workflows.py | 131 ++++++++++++++
tools/ci_validate.py | 130 ++++++++++++++
tools/github_release.py | 78 +++++++++
tools/release_gate.py | 132 ++++++++++++++
tools/verify_distribution.py | 51 +++++-
17 files changed, 1119 insertions(+), 51 deletions(-)
create mode 100644 .github/workflows/github-release.yml
create mode 100644 .github/workflows/release-validation.yml
create mode 100644 docs/development/ci-release.md
create mode 100644 docs/development/release-notes-1.0.0.md
create mode 100644 requirements-ci.txt
create mode 100644 tests/test_release_workflows.py
create mode 100644 tools/check_workflows.py
create mode 100644 tools/ci_validate.py
create mode 100644 tools/github_release.py
create mode 100644 tools/release_gate.py
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/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..848ca8a 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -134,6 +134,7 @@ 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
- 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_release_workflows.py b/tests/test_release_workflows.py
new file mode 100644
index 0000000..6d4e9f1
--- /dev/null
+++ b/tests/test_release_workflows.py
@@ -0,0 +1,170 @@
+"""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)
diff --git a/tools/check_architecture_docs.py b/tools/check_architecture_docs.py
index fc6ba2b..0a96bef 100644
--- a/tools/check_architecture_docs.py
+++ b/tools/check_architecture_docs.py
@@ -90,7 +90,8 @@ def protected(name):
for name, digest in reviewed.items():
if 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()
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)
From dc32f4c6be3b38a4794af674736f755ae820583a Mon Sep 17 00:00:00 2001
From: Alex M <9vy6rqx2p8@privaterelay.appleid.com>
Date: Sat, 10 Oct 2026 01:35:57 +0000
Subject: [PATCH 02/12] docs: record CI and GitHub-first release safeguards
Assisted-by: OpenAI Codex
---
audit/PHASE5B-CI-RELEASE.md | 41 +++++++++++++++++++++++++++++++++++++
1 file changed, 41 insertions(+)
create mode 100644 audit/PHASE5B-CI-RELEASE.md
diff --git a/audit/PHASE5B-CI-RELEASE.md b/audit/PHASE5B-CI-RELEASE.md
new file mode 100644
index 0000000..32f51c7
--- /dev/null
+++ b/audit/PHASE5B-CI-RELEASE.md
@@ -0,0 +1,41 @@
+# Phase 5B — CI and release safeguards
+
+Base: master `deea819172cf86f6f7aabeaa2693af1e33afb1b7` (PR #61).
+Branch: `release/phase5b-ci`. Verification results are recorded below when completed.
+
+## Changes
+
+Reuse Phase 5A checks through a portable harness. Expand the matrix to CPython
+3.10–3.14 on Ubuntu, Windows and macOS; retain failure diagnostics. Replace
+floating action references with reviewed full-SHA pins. Consolidate strict
+MkDocs/API/tutorial/site checking before Pages deployment, with write/OIDC
+permissions limited to the deployment job. No Wiki writes occur.
+
+The manual candidate workflow validates canonical master/SHA/version/tag,
+clean-source provenance, archive contents and hashes, and isolated installation.
+Protected review requires server-side reviewers, prevented self-review, no
+administrator bypass and protected-branch restrictions. Missing protection
+fails closed. Repository protection settings are not changed by this phase.
+
+GitHub-first release preparation is separate from PyPI authority. The GitHub
+workflow defaults to validation-only, consumes the exact previously validated
+candidate assets, and never rebuilds them. Publication additionally requires
+`GEM_RELEASE_ENABLED=true`, exact owner authorization and protected review.
+No publication mode is executed in Phase 5B. PyPI remains disabled; current
+publishing authority is unverified and blocks PyPI upload only.
+
+## Verification
+
+Full interpreter, platform, archive and documentation evidence is maintained in
+`phase5b-verification.json`. Workflow configuration alone does not establish
+platform support. Manual master-only dispatch and real protected approval cannot
+be exercised on an unmerged PR; deterministic negative tests validate those
+boundaries without creating tags, releases or changing server protections.
+
+## Release checklist
+
+See [CI and release guide](../docs/development/ci-release.md) for exact commands,
+review protection requirements, GitHub asset installation, failure handling and
+PyPI authority prerequisites. Phase 5C validates the final release commit;
+Phase 5D publication requires separate explicit owner approval. Preserve the
+verified official archives for eventual PyPI upload, without silent rebuilding.
From 375417afb457480c636961df09e437f420154a57 Mon Sep 17 00:00:00 2001
From: Alex M <9vy6rqx2p8@privaterelay.appleid.com>
Date: Sat, 10 Oct 2026 01:36:26 +0000
Subject: [PATCH 03/12] ci: support relocated interpreters in isolated installs
Include prepared release notes in strict documentation navigation.
Assisted-by: OpenAI Codex
---
mkdocs.yml | 1 +
tools/ci_validate.py | 2 +-
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/mkdocs.yml b/mkdocs.yml
index 848ca8a..17f0c07 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -135,6 +135,7 @@ nav:
- 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/tools/ci_validate.py b/tools/ci_validate.py
index 6c90856..da17820 100644
--- a/tools/ci_validate.py
+++ b/tools/ci_validate.py
@@ -69,7 +69,7 @@ def read(name):return json.loads((output_dir/name).read_text(encoding='utf-8'))
report['installed']={}
for kind,artifact in [('wheel',wheel),('sdist',sdist)]:
target=output_dir/(kind+'-environment')
- venv.EnvBuilder(with_pip=True).create(target)
+ venv.EnvBuilder(with_pip=True,symlinks=os.name!='nt').create(target)
executable=environment_python(target)
# Build tools are deliberately separate from gem's runtime dependency.
setup=[executable,'-m','pip','install','six==1.17.0','setuptools==80.9.0','wheel==0.48.0']
From 81bfcc19d7c3c1dea0da8f0ed0c5ee3ed5ccb817 Mon Sep 17 00:00:00 2001
From: Alex M <9vy6rqx2p8@privaterelay.appleid.com>
Date: Sat, 10 Oct 2026 01:39:45 +0000
Subject: [PATCH 04/12] ci: preserve portable source and benchmark checks
Use LF checkouts, POSIX Git paths, byte-exact fixtures and platform CPU identification. Preserve existing assertions and scope fingerprints.
Assisted-by: OpenAI Codex
---
.gitattributes | 9 +++++++++
benchmarks/run_core.py | 4 +++-
tests/test_documentation_overhaul.py | 6 +++---
tests/test_release_packaging.py | 5 +++--
tools/check_architecture_docs.py | 20 +++++++++++++++++---
tools/phase5b-ci-scope.json | 8 ++++++++
6 files changed, 43 insertions(+), 9 deletions(-)
create mode 100644 .gitattributes
create mode 100644 tools/phase5b-ci-scope.json
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/benchmarks/run_core.py b/benchmarks/run_core.py
index 2d5d754..b35d238 100644
--- a/benchmarks/run_core.py
+++ b/benchmarks/run_core.py
@@ -25,11 +25,13 @@
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(),
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/tools/check_architecture_docs.py b/tools/check_architecture_docs.py
index 0a96bef..9e8ac2f 100644
--- a/tools/check_architecture_docs.py
+++ b/tools/check_architecture_docs.py
@@ -74,6 +74,19 @@ def protected(name):
current = subprocess.check_output(['git', 'ls-files', '--cached', '--others',
'--exclude-standard'], cwd=ROOT, text=True).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())
+ 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():
@@ -88,7 +101,7 @@ 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',
'tests/test_release_workflows.py'} | set(reviewed)
@@ -97,12 +110,13 @@ def protected(name):
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)}
diff --git a/tools/phase5b-ci-scope.json b/tools/phase5b-ci-scope.json
new file mode 100644
index 0000000..c18760d
--- /dev/null
+++ b/tools/phase5b-ci-scope.json
@@ -0,0 +1,8 @@
+{
+ "base": "15fbce64fa87008203890142070dbf30ea802ebc",
+ "files": {
+ "tests/test_documentation_overhaul.py": "f01d58897a60bbb091c870ee4fdabf472dfece13c3dc634d37a9fc312b464504",
+ "tests/test_release_packaging.py": "ca367df6a7638b261b752d7e153caf0b49455392754533356e4dd606a631e2b2",
+ "benchmarks/run_core.py": "ed2a4d081aa655ac18565ff313880e914b13bcf41f6d17e52e8dd4da5fe5ba2f"
+ }
+}
From 119a8413f03bcc14ee4ead6b772842bc771775c2 Mon Sep 17 00:00:00 2001
From: Alex M <9vy6rqx2p8@privaterelay.appleid.com>
Date: Sat, 10 Oct 2026 01:40:35 +0000
Subject: [PATCH 05/12] test: verify dry-run and publication command boundaries
Mock release mutation while checking exact assets and commit targeting.
Assisted-by: OpenAI Codex
---
tests/test_release_workflows.py | 53 +++++++++++++++++++++++++++++++++
1 file changed, 53 insertions(+)
diff --git a/tests/test_release_workflows.py b/tests/test_release_workflows.py
index 6d4e9f1..53feae7 100644
--- a/tests/test_release_workflows.py
+++ b/tests/test_release_workflows.py
@@ -168,3 +168,56 @@ 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}
+ 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) == 1
+ command = commands[0]
+ 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
+
+
+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()
From ae01c225f79d6201d57ac8f38c7b702e6837940c Mon Sep 17 00:00:00 2001
From: Alex M <9vy6rqx2p8@privaterelay.appleid.com>
Date: Sat, 10 Oct 2026 01:41:41 +0000
Subject: [PATCH 06/12] ci: identify unsupported CPU affinity explicitly
Keep missing benchmark metadata distinct from platforms without affinity support.
Assisted-by: OpenAI Codex
---
benchmarks/run_core.py | 2 +-
tools/phase5b-ci-scope.json | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/benchmarks/run_core.py b/benchmarks/run_core.py
index b35d238..ab35fd1 100644
--- a/benchmarks/run_core.py
+++ b/benchmarks/run_core.py
@@ -38,7 +38,7 @@ def environment():
'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/tools/phase5b-ci-scope.json b/tools/phase5b-ci-scope.json
index c18760d..bc82614 100644
--- a/tools/phase5b-ci-scope.json
+++ b/tools/phase5b-ci-scope.json
@@ -3,6 +3,6 @@
"files": {
"tests/test_documentation_overhaul.py": "f01d58897a60bbb091c870ee4fdabf472dfece13c3dc634d37a9fc312b464504",
"tests/test_release_packaging.py": "ca367df6a7638b261b752d7e153caf0b49455392754533356e4dd606a631e2b2",
- "benchmarks/run_core.py": "ed2a4d081aa655ac18565ff313880e914b13bcf41f6d17e52e8dd4da5fe5ba2f"
+ "benchmarks/run_core.py": "4e751fd4ad2909f23d3257fdb8fc055c0a48494ba435f656fbdd009781d29825"
}
}
From 1e59c59199a83bc96766be53ce6abe0ba544d50c Mon Sep 17 00:00:00 2001
From: Alex M <9vy6rqx2p8@privaterelay.appleid.com>
Date: Sat, 10 Oct 2026 01:43:00 +0000
Subject: [PATCH 07/12] ci: bind remote release tags to the approved commit
Refuse mismatched remote tags and atomically create missing refs only in authorized publication mode.
Assisted-by: OpenAI Codex
---
tests/test_release_workflows.py | 19 ++++++++++++++++---
tools/github_release.py | 26 ++++++++++++++++++++++++--
2 files changed, 40 insertions(+), 5 deletions(-)
diff --git a/tests/test_release_workflows.py b/tests/test_release_workflows.py
index 53feae7..a8d5a9d 100644
--- a/tests/test_release_workflows.py
+++ b/tests/test_release_workflows.py
@@ -188,6 +188,8 @@ 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 = []
@@ -203,12 +205,13 @@ def get(path):
if mode == 'validate':
assert commands == []
else:
- assert len(commands) == 1
- command = commands[0]
+ 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
+ assert '--clobber' not in command and '--verify-tag' in command
def test_ci_scope_cannot_exempt_mathematics(tmp_path, monkeypatch):
@@ -221,3 +224,13 @@ def test_ci_scope_cannot_exempt_mathematics(tmp_path, monkeypatch):
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)
diff --git a/tools/github_release.py b/tools/github_release.py
index 4cc039d..e4e954f 100644
--- a/tools/github_release.py
+++ b/tools/github_release.py
@@ -33,6 +33,22 @@ def validate_publication(context, enabled, approved, mode):
gate.require(approved == 'publish v1.0.0', 'exact owner authorization phrase required')
+def remote_tag_matches(commit):
+ try:
+ ref = gate.github_get('git/ref/tags/' + gate.TAG)
+ except urllib.error.HTTPError as error:
+ if error.code == 404: return False
+ raise
+ obj = ref.get('object', {})
+ for _ in range(8):
+ if obj.get('type') == 'commit':
+ gate.require(obj.get('sha') == commit, 'remote release tag points to another commit')
+ return True
+ gate.require(obj.get('type') == 'tag', 'unsupported remote tag object')
+ obj = gate.github_get('git/tags/' + obj['sha']).get('object', {})
+ raise ValueError('excessive annotated-tag indirection')
+
+
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--directory', type=Path, required=True)
@@ -64,10 +80,16 @@ def main():
if error.code != 404: raise
else:
raise ValueError('release already exists; refuse to overwrite official artifacts')
+ tag_exists = remote_tag_matches(args.commit)
if args.mode == 'publish':
- # --target binds tag creation to the reviewed SHA; existing tags were checked by preflight.
+ if not tag_exists:
+ subprocess.run(['gh', 'api', 'repos/' + gate.REPOSITORY + '/git/refs',
+ '--method', 'POST', '-f', 'ref=refs/tags/' + gate.TAG,
+ '-f', 'sha=' + args.commit], check=True)
+ gate.require(remote_tag_matches(args.commit), 'release tag must exist at approved commit')
+ # Atomic ref creation refuses an existing ref; recheck the remote target before release.
subprocess.run(['gh', 'release', 'create', gate.TAG, '--repo', gate.REPOSITORY,
- '--target', args.commit, '--title', 'gem 1.0.0',
+ '--verify-tag', '--target', args.commit, '--title', 'gem 1.0.0',
'--notes-file', str(ROOT / 'docs/development/release-notes-1.0.0.md'),
*[str(args.directory / name) for name in sorted(gate.FILES | {'SHA256SUMS', 'INSTALL.md'})]],
check=True)
From fc7dc748d22126072a5ddaea7e1cb2f86d303203 Mon Sep 17 00:00:00 2001
From: Alex M <9vy6rqx2p8@privaterelay.appleid.com>
Date: Sat, 10 Oct 2026 01:45:15 +0000
Subject: [PATCH 08/12] ci: retain installation diagnostics after suite
failures
Continue independent artifact checks while keeping failed suites a hard candidate gate.
Assisted-by: OpenAI Codex
---
tools/ci_validate.py | 17 ++++++++++++++---
1 file changed, 14 insertions(+), 3 deletions(-)
diff --git a/tools/ci_validate.py b/tools/ci_validate.py
index da17820..5b162d9 100644
--- a/tools/ci_validate.py
+++ b/tools/ci_validate.py
@@ -48,9 +48,18 @@ def command(args, name, cwd=ROOT):
subprocess.run(list(map(str,args)),cwd=cwd,env=env,stdout=log,stderr=subprocess.STDOUT,check=True)
def read(name):return json.loads((output_dir/name).read_text(encoding='utf-8'))
try:
- command([sys.executable,'-m','pytest','-q','-o','junit_family=legacy',
- '--junitxml',str(output_dir/'pytest.xml')],'pytest')
- report['tests']=assert_clean_junit(output_dir/'pytest.xml')
+ pytest_error=None
+ try:
+ command([sys.executable,'-m','pytest','-q','-o','junit_family=legacy',
+ '--junitxml',str(output_dir/'pytest.xml')],'pytest')
+ report['tests']=assert_clean_junit(output_dir/'pytest.xml')
+ except (subprocess.CalledProcessError,ValueError) as error:
+ # Continue independent archive/install diagnostics, but never approve a failed suite.
+ pytest_error=error
+ suites=ET.parse(output_dir/'pytest.xml').getroot()
+ totals={name:sum(int(s.get(name,0)) for s in suites.iter('testsuite'))
+ for name in ('tests','failures','errors','skipped')}
+ report['tests']=dict(totals,passed=totals['tests']-sum(totals[n] for n in ('failures','errors','skipped')))
command([sys.executable,ROOT/'tools/check_architecture_docs.py',*DOC_FLAGS,
'--output',output_dir/'source-docs.json'],'source-docs')
report['documentation']=read('source-docs.json')
@@ -88,6 +97,8 @@ def read(name):return json.loads((output_dir/name).read_text(encoding='utf-8'))
if docs['source_declarations_checked']!=268 or docs['examples_executed']!=43:
raise ValueError('installed API/example coverage incomplete')
report['installed'][kind]={'smoke':read(kind+'-smoke.json'),'documentation':docs}
+ if pytest_error is not None:
+ raise ValueError('suite failed; archive/install diagnostics do not approve the candidate') from pytest_error
commit=subprocess.check_output(['git','rev-parse','HEAD'],cwd=ROOT,text=True).strip()
tree=subprocess.check_output(['git','rev-parse','HEAD^{tree}'],cwd=ROOT,text=True).strip()
manifest={'schema_version':1,'distribution':'gem','version':'1.0.0','tag':'v1.0.0',
From 64aa799d37c2595f7fd24b3144fe0b520c5cba63 Mon Sep 17 00:00:00 2001
From: Alex M <9vy6rqx2p8@privaterelay.appleid.com>
Date: Sat, 10 Oct 2026 01:47:42 +0000
Subject: [PATCH 09/12] ci: use UTF-8 and canonical archive paths on Windows
Preserve documentation coverage and exact package-content checks across locales.
Assisted-by: OpenAI Codex
---
tools/check_architecture_docs.py | 26 +++++++++++++-------------
tools/ci_validate.py | 2 +-
tools/verify_distribution.py | 2 +-
3 files changed, 15 insertions(+), 15 deletions(-)
diff --git a/tools/check_architecture_docs.py b/tools/check_architecture_docs.py
index 9e8ac2f..f9e22c0 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('_'):
@@ -77,7 +77,7 @@ def protected(name):
ci_reviewed = {}
ci_manifest = ROOT/'tools/phase5b-ci-scope.json'
if ci_manifest.exists():
- ci_scope = json.loads(ci_manifest.read_text())
+ 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']
@@ -90,7 +90,7 @@ def protected(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']
@@ -129,7 +129,7 @@ def preserve_learning_material(pages):
if name not in baseline:
continue
previous = subprocess.check_output(['git', 'show', BASE+':'+name], cwd=ROOT, text=True)
- current = (ROOT/name).read_text()
+ 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:
@@ -155,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')
@@ -169,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(' |')
@@ -197,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('_'))
@@ -237,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'
@@ -274,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):
@@ -285,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/ci_validate.py b/tools/ci_validate.py
index 5b162d9..8a1135e 100644
--- a/tools/ci_validate.py
+++ b/tools/ci_validate.py
@@ -41,7 +41,7 @@ def run(output_dir, artifacts_dir, offline=False, wheelhouse=None):
initially_clean=not subprocess.check_output(['git','status','--porcelain'],cwd=ROOT,text=True).strip()
report={'python':platform.python_version(),'platform':platform.platform(),'commands':[],
'status':'running','working_directory':str(ROOT)}
- env=os.environ.copy();env.pop('PYTHONPATH',None);env['PIP_DISABLE_PIP_VERSION_CHECK']='1'
+ env=os.environ.copy();env.pop('PYTHONPATH',None);env['PIP_DISABLE_PIP_VERSION_CHECK']='1';env['PYTHONUTF8']='1'
def command(args, name, cwd=ROOT):
report['commands'].append({'argv':list(map(str,args)),'cwd':str(cwd),'log':name+'.log'})
with (output_dir/(name+'.log')).open('w',encoding='utf-8') as log:
diff --git a/tools/verify_distribution.py b/tools/verify_distribution.py
index 3b946f3..e78fbaa 100644
--- a/tools/verify_distribution.py
+++ b/tools/verify_distribution.py
@@ -45,7 +45,7 @@ def check_member(name, content=b''):
def inspect_archives(wheel, sdist):
- runtime = {str(p.relative_to(ROOT)): p.read_bytes() for p in (ROOT/'gem').rglob('*.py')}
+ runtime = {p.relative_to(ROOT).as_posix(): p.read_bytes() for p in (ROOT/'gem').rglob('*.py')}
with zipfile.ZipFile(wheel) as archive:
files = {}
for info in archive.infolist():
From e273085dd451c2221c0b147c034e4883dd1775e4 Mon Sep 17 00:00:00 2001
From: Alex M <9vy6rqx2p8@privaterelay.appleid.com>
Date: Sat, 10 Oct 2026 01:50:25 +0000
Subject: [PATCH 10/12] ci: decode Git documentation references as UTF-8
Keep isolated Windows documentation checks independent of the console locale.
Assisted-by: OpenAI Codex
---
tools/check_architecture_docs.py | 8 ++++----
tools/ci_validate.py | 4 ++--
2 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/tools/check_architecture_docs.py b/tools/check_architecture_docs.py
index f9e22c0..54e67e1 100644
--- a/tools/check_architecture_docs.py
+++ b/tools/check_architecture_docs.py
@@ -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,7 +72,7 @@ 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'
@@ -123,12 +123,12 @@ def protected(name):
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)
+ 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))
diff --git a/tools/ci_validate.py b/tools/ci_validate.py
index 8a1135e..adeb7e5 100644
--- a/tools/ci_validate.py
+++ b/tools/ci_validate.py
@@ -89,9 +89,9 @@ def read(name):return json.loads((output_dir/name).read_text(encoding='utf-8'))
command(install,kind+'-install',cwd=output_dir)
site=subprocess.check_output([str(executable),'-I','-c',
'import sysconfig; print(sysconfig.get_path("purelib"))'],text=True,cwd=output_dir).strip()
- command([executable,'-I',ROOT/'tools/verify_distribution.py','--installed-root',site,
+ command([executable,'-I','-X','utf8',ROOT/'tools/verify_distribution.py','--installed-root',site,
'--output',output_dir/(kind+'-smoke.json')],kind+'-smoke',cwd=output_dir)
- command([executable,'-I',ROOT/'tools/check_architecture_docs.py',*DOC_FLAGS,'--package-root',site,
+ command([executable,'-I','-X','utf8',ROOT/'tools/check_architecture_docs.py',*DOC_FLAGS,'--package-root',site,
'--output',output_dir/(kind+'-docs.json')],kind+'-docs',cwd=output_dir)
docs=read(kind+'-docs.json')
if docs['source_declarations_checked']!=268 or docs['examples_executed']!=43:
From 0e085b4bfd532935bb1267bb3eae95f04de3ed15 Mon Sep 17 00:00:00 2001
From: Alex M <9vy6rqx2p8@privaterelay.appleid.com>
Date: Sat, 10 Oct 2026 01:53:13 +0000
Subject: [PATCH 11/12] test: reject tampered release candidate manifests and
assets
Assisted-by: OpenAI Codex
---
tests/test_release_workflows.py | 30 ++++++++++++++++++++++++++++++
1 file changed, 30 insertions(+)
diff --git a/tests/test_release_workflows.py b/tests/test_release_workflows.py
index a8d5a9d..ba5c104 100644
--- a/tests/test_release_workflows.py
+++ b/tests/test_release_workflows.py
@@ -234,3 +234,33 @@ def get(path):
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')
From fc29d993d42497adf345c6d3764e0dbf67109e77 Mon Sep 17 00:00:00 2001
From: Alex M <9vy6rqx2p8@privaterelay.appleid.com>
Date: Sat, 10 Oct 2026 02:02:57 +0000
Subject: [PATCH 12/12] docs: record CI verification and Windows release
blocker
Assisted-by: OpenAI Codex
---
audit/PHASE5B-CI-RELEASE.md | 165 +-
audit/phase5b-verification.json | 4443 +++++++++++++++++++++++++++++++
2 files changed, 4578 insertions(+), 30 deletions(-)
create mode 100644 audit/phase5b-verification.json
diff --git a/audit/PHASE5B-CI-RELEASE.md b/audit/PHASE5B-CI-RELEASE.md
index 32f51c7..250dc76 100644
--- a/audit/PHASE5B-CI-RELEASE.md
+++ b/audit/PHASE5B-CI-RELEASE.md
@@ -1,41 +1,146 @@
# Phase 5B — CI and release safeguards
Base: master `deea819172cf86f6f7aabeaa2693af1e33afb1b7` (PR #61).
-Branch: `release/phase5b-ci`. Verification results are recorded below when completed.
+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. Expand the matrix to CPython
-3.10–3.14 on Ubuntu, Windows and macOS; retain failure diagnostics. Replace
-floating action references with reviewed full-SHA pins. Consolidate strict
-MkDocs/API/tutorial/site checking before Pages deployment, with write/OIDC
-permissions limited to the deployment job. No Wiki writes occur.
-
-The manual candidate workflow validates canonical master/SHA/version/tag,
-clean-source provenance, archive contents and hashes, and isolated installation.
-Protected review requires server-side reviewers, prevented self-review, no
-administrator bypass and protected-branch restrictions. Missing protection
-fails closed. Repository protection settings are not changed by this phase.
-
-GitHub-first release preparation is separate from PyPI authority. The GitHub
-workflow defaults to validation-only, consumes the exact previously validated
-candidate assets, and never rebuilds them. Publication additionally requires
-`GEM_RELEASE_ENABLED=true`, exact owner authorization and protected review.
-No publication mode is executed in Phase 5B. PyPI remains disabled; current
-publishing authority is unverified and blocks PyPI upload only.
+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
-Full interpreter, platform, archive and documentation evidence is maintained in
-`phase5b-verification.json`. Workflow configuration alone does not establish
-platform support. Manual master-only dispatch and real protected approval cannot
-be exercised on an unmerged PR; deterministic negative tests validate those
-boundaries without creating tags, releases or changing server protections.
+[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.
-## Release checklist
+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.
-See [CI and release guide](../docs/development/ci-release.md) for exact commands,
-review protection requirements, GitHub asset installation, failure handling and
-PyPI authority prerequisites. Phase 5C validates the final release commit;
-Phase 5D publication requires separate explicit owner approval. Preserve the
-verified official archives for eventual PyPI upload, without silent rebuilding.
+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"
+ }
+}