diff --git a/.github/workflows/release-preflight.yml b/.github/workflows/release-preflight.yml deleted file mode 100644 index a30825c..0000000 --- a/.github/workflows/release-preflight.yml +++ /dev/null @@ -1,88 +0,0 @@ -# SPDX-FileCopyrightText: 2026 bartzbeielstein -# -# SPDX-License-Identifier: AGPL-3.0-or-later -# -# ============================================================================= -# .github/workflows/release-preflight.yml — Release Permission Preflight -# ============================================================================= - -name: Release Preflight - -on: - workflow_dispatch: - push: - branches: - - main - -# Only one preflight at a time per branch -concurrency: - group: release-preflight-${{ github.ref }} - cancel-in-progress: true - -permissions: - contents: read - -jobs: - preflight: - name: Validate release permissions - runs-on: ubuntu-latest - timeout-minutes: 5 - permissions: - contents: write - - steps: - - name: Checkout code - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - fetch-depth: 0 - persist-credentials: false - - - name: Set up uv - uses: astral-sh/setup-uv@e06108dd0aef18192324c70427afc47652e63a82 # v7.5.0 - - - name: Verify SEMANTIC_RELEASE_TOKEN is configured - env: - RELEASE_TOKEN: ${{ secrets.SEMANTIC_RELEASE_TOKEN }} - run: | - set -euo pipefail - if [ -z "${RELEASE_TOKEN:-}" ]; then - echo "::error title=Missing secret::SEMANTIC_RELEASE_TOKEN is not set." - echo "::error::Create a fine-grained PAT (or classic PAT with 'repo' scope) and add it" - echo "::error::as a repository secret named SEMANTIC_RELEASE_TOKEN:" - echo "::error:: Settings → Secrets and variables → Actions → New repository secret" - echo "::error::The token needs Contents: Read and write on this repository." - echo "::error::github.token cannot be used here — org-level Actions permissions restrict it to read-only." - exit 1 - fi - - - name: Configure Git credentials - env: - RELEASE_TOKEN: ${{ secrets.SEMANTIC_RELEASE_TOKEN }} - run: | - set -euo pipefail - # Disable any credential helpers the runner may have configured - # (gh CLI, credential-manager, etc.) that can intercept push auth. - git config --local --unset-all credential.helper || true - git config --local credential.helper '' - # Use the extraheader method — the same mechanism actions/checkout - # uses internally. Works reliably for both fetch and push. - BASIC=$(echo -n "x-access-token:${RELEASE_TOKEN}" | base64 -w 0) - git config --local http.https://github.com/.extraheader "AUTHORIZATION: basic ${BASIC}" - - - name: Preflight token and push checks - run: | - set -euo pipefail - - echo "Checking authenticated read access to ${GITHUB_REPOSITORY}..." - git ls-remote --exit-code origin HEAD >/dev/null - - echo "Checking write access via dry-run push to unprotected ref..." - test_ref="refs/preflight/sr-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}" - if ! git push --dry-run origin "HEAD:${test_ref}" 2>&1; then - echo "::error::SEMANTIC_RELEASE_TOKEN cannot push to ${GITHUB_REPOSITORY}." - echo "::error::Ensure the PAT has Contents: Read and write (fine-grained) or 'repo' scope (classic)." - echo "::error::For org repos, verify the fine-grained PAT was approved in org settings." - exit 1 - fi - - echo "Preflight passed: token and push permissions are sufficient for semantic-release." diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8c5e459..3f07703 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -3,30 +3,27 @@ # SPDX-License-Identifier: AGPL-3.0-or-later # # ============================================================================= -# .github/workflows/release.yml — Automatic Semantic Releases (Optimised v2) +# .github/workflows/release.yml — Semantic Release on push to main # ============================================================================= # -# Triggered by the Release Preflight workflow succeeding on main. -# Steps: -# 1. Checkout main (SHA-pinned, not PR SHA — see security note) -# 2. Verify checkout integrity -# 3. Semantic Release (version bump, changelog, GitHub Release tag) -# 4. Build wheel + sdist -# 5. Publish to PyPI via Trusted Publisher (OIDC) -# 6. Verify PyPI availability +# Triggered on every push to main (typically via merged PR from develop). +# semantic-release inspects commit messages and only creates a release when +# it finds feat:, fix:, perf:, or other configured types. +# +# Requirements: +# - Repository secret SEMANTIC_RELEASE_TOKEN: a PAT with Contents: Read +# and write on this repository. Passed to actions/checkout so git push +# and the GitHub API both work automatically. +# - PyPI Trusted Publisher configured for this repository (OIDC, no token). # ============================================================================= name: Release on: - workflow_run: - workflows: - - Release Preflight - types: - - completed + push: + branches: [main] workflow_dispatch: -# Only one release at a time concurrency: group: release cancel-in-progress: false @@ -36,17 +33,11 @@ permissions: jobs: release: - name: Create Release + name: Semantic Release runs-on: ubuntu-latest timeout-minutes: 15 - if: > - github.event_name == 'workflow_dispatch' || - ( - github.event_name == 'workflow_run' && - github.event.workflow_run.conclusion == 'success' && - github.event.workflow_run.head_branch == 'main' && - !contains(github.event.workflow_run.head_commit.message, 'chore(release)') - ) + # Skip release commits to prevent infinite loops + if: "!contains(github.event.head_commit.message, 'chore(release)')" permissions: contents: write issues: write @@ -54,44 +45,32 @@ jobs: id-token: write steps: - - name: Checkout main - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + # The token: parameter does two things: + # 1. Configures git credentials so semantic-release can push tags/commits + # 2. Authenticates the GitHub API calls semantic-release makes + # This is the standard, battle-tested approach — no manual credential setup. + - name: Checkout + uses: actions/checkout@v4 with: - ref: refs/heads/main fetch-depth: 0 - persist-credentials: false - - - name: Verify checkout integrity - run: | - ACTUAL_SHA=$(git rev-parse HEAD) - if [ "${{ github.event_name }}" = "workflow_run" ]; then - EXPECTED_SHA="${{ github.event.workflow_run.head_sha }}" - if [ "${ACTUAL_SHA}" != "${EXPECTED_SHA}" ]; then - echo "::error::HEAD SHA (${ACTUAL_SHA}) does not match triggering SHA (${EXPECTED_SHA}). Aborting." - exit 1 - fi - fi - echo "Checkout integrity verified: ${ACTUAL_SHA}" + token: ${{ secrets.SEMANTIC_RELEASE_TOKEN }} - name: Set up Python - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + uses: actions/setup-python@v5 with: python-version: "3.13" - name: Set up uv - uses: astral-sh/setup-uv@e06108dd0aef18192324c70427afc47652e63a82 # v7.5.0 + uses: astral-sh/setup-uv@v5 with: enable-cache: true cache-dependency-glob: "uv.lock" - # Release only needs `uv build` (run by semantic-release's prepareCmd). - # uv build does not require the runtime deps (torch, xgboost, etc.), - # so skip --all-extras to avoid a 2–3 GB download on every release. - name: Install build tools run: uv sync --no-install-project --group dev - name: Set up Node.js - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + uses: actions/setup-node@v4 with: node-version: 20 @@ -103,34 +82,24 @@ jobs: @semantic-release/exec@6 \ conventional-changelog-conventionalcommits@7 - - name: Configure Git credentials - env: - RELEASE_TOKEN: ${{ secrets.SEMANTIC_RELEASE_TOKEN || github.token }} - run: | - set -euo pipefail - git config --local --unset-all credential.helper || true - git config --local credential.helper '' - BASIC=$(echo -n "x-access-token:${RELEASE_TOKEN}" | base64 -w 0) - git config --local http.https://github.com/.extraheader "AUTHORIZATION: basic ${BASIC}" - - - name: Semantic Release + - name: Run semantic-release env: - GH_TOKEN: ${{ secrets.SEMANTIC_RELEASE_TOKEN || github.token }} - GITHUB_TOKEN: ${{ secrets.SEMANTIC_RELEASE_TOKEN || github.token }} + GITHUB_TOKEN: ${{ secrets.SEMANTIC_RELEASE_TOKEN }} + GH_TOKEN: ${{ secrets.SEMANTIC_RELEASE_TOKEN }} run: npx semantic-release - name: Check if release generated dist/ id: check_dist run: | if [ -d "dist" ]; then - echo "exists=true" >> $GITHUB_OUTPUT + echo "exists=true" >> "$GITHUB_OUTPUT" else - echo "exists=false" >> $GITHUB_OUTPUT + echo "exists=false" >> "$GITHUB_OUTPUT" fi - name: Publish to PyPI if: steps.check_dist.outputs.exists == 'true' - uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # release/v1 + uses: pypa/gh-action-pypi-publish@release/v1 with: packages-dir: dist/ skip-existing: true diff --git a/docs/spotoptim_class.qmd b/docs/spotoptim_class.qmd index 8eba515..0474180 100644 --- a/docs/spotoptim_class.qmd +++ b/docs/spotoptim_class.qmd @@ -106,11 +106,15 @@ description: "Structure of the Methods" * get_ocba() * get_ocba_X() -### TASK_SELECT: +### TASK_SUBSET: * select_distant_points() * select_best_cluster() * _selection_dispatcher() + + +### TASK_SELECT: + * select_new() * suggest_next_infill_point() diff --git a/src/spotoptim/SpotOptim.py b/src/spotoptim/SpotOptim.py index 0ff54bf..c90b5ee 100644 --- a/src/spotoptim/SpotOptim.py +++ b/src/spotoptim/SpotOptim.py @@ -5497,12 +5497,10 @@ def get_ocba_X( return None # ==================== - # TASK_SELECT: + # TASK_SUBSET: # * select_distant_points() # * select_best_cluster() # * _selection_dispatcher() - # * select_new() - # * suggest_next_infill_point() # ==================== def select_distant_points( @@ -5647,6 +5645,13 @@ def _selection_dispatcher( # If no valid selection method, return all points return X, y + # ==================== + # TASK_SELECT: + # * select_new() + # * suggest_next_infill_point() + # ==================== + + def select_new( self, A: np.ndarray, X: np.ndarray, tolerance: float = 0 ) -> Tuple[np.ndarray, np.ndarray]: