Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .github/actions/prepare-release-benchmarks/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ runs:
with:
cache: false

- name: Validate benchmark inputs
shell: bash
run: just test-bench-inputs

- name: Inventory full release suites
shell: bash
run: just bench-release-inventory

- name: Validate benchmark inputs
shell: bash
run: just test-bench-inputs
4 changes: 3 additions & 1 deletion .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,9 @@ updates:

# Enable version updates for Python development dependencies locked by uv
- package-ecosystem: "uv"
directory: "/"
directories:
- "/"
- "/tooling/semgrep"
schedule:
interval: "weekly"
day: "monday"
Expand Down
99 changes: 24 additions & 75 deletions .github/workflows/release-benchmarks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,16 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 5
outputs:
release-id: ${{ steps.target.outputs.release-id }}
commit: ${{ steps.target.outputs.commit }}
target: ${{ steps.target.outputs.target }}
validated-attempt: ${{ github.run_attempt }}
steps:
- name: Install trusted package runner
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
with:
version: "0.13.1"
enable-cache: false
cache-python: false

- name: Validate draft release target
id: target
env:
Expand All @@ -44,32 +50,14 @@ jobs:
run: |
set -euo pipefail

if [[ ! "$RELEASE_TAG" =~ ^v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$ ]]; then
echo "::error::Expected an existing stable vX.Y.Z tag"
exit 1
fi
if [[ "$GITHUB_REF" != "refs/tags/$RELEASE_TAG" ]]; then
echo "::error::Dispatch with --ref $RELEASE_TAG and the matching tag input"
exit 1
fi
# The release-by-tag endpoint only guarantees published releases.
releases="$(gh api "repos/$GH_REPO/releases?per_page=100" --paginate --slurp)"
release_id="$(jq -er --arg tag "$RELEASE_TAG" '
[.[][] | select(.tag_name == $tag)]
| if length == 1 and .[0].draft == true and .[0].prerelease == false
and .[0].immutable == false and .[0].name == $tag
then .[0].id else error("Expected exactly one mutable stable draft with the tag as its title") end
' <<< "$releases")"
[[ "$release_id" =~ ^[1-9][0-9]*$ ]] || exit 1
# Fully qualify the tag to avoid a same-named branch; peel annotated tags.
commit="$(gh api "repos/$GH_REPO/commits/refs/tags/$RELEASE_TAG" --jq .sha)"
[[ "$commit" =~ ^[0-9a-f]{40}$ ]] || exit 1
if [[ "$commit" != "$GITHUB_SHA" ]]; then
echo "::error::Release tag no longer matches the workflow commit"
exit 1
fi
echo "release-id=$release_id" >> "$GITHUB_OUTPUT"
echo "commit=$commit" >> "$GITHUB_OUTPUT"
target="$(uvx --python 3.15 --from research-repo-tools==0.1.9 research-repo-tools \
--root "$RUNNER_TEMP" performance release-draft "$GH_REPO" "$RELEASE_TAG" \
--commit "$GITHUB_SHA" --expected-title "$RELEASE_TAG" | jq -c .)"
echo "target=$target" >> "$GITHUB_OUTPUT"

release-baseline:
needs: validate-release
Expand Down Expand Up @@ -182,6 +170,13 @@ jobs:
timeout-minutes: 10

steps:
- name: Install trusted package runner
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
with:
version: "0.13.1"
enable-cache: false
cache-python: false

- name: Download release baseline
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
Expand All @@ -192,62 +187,16 @@ jobs:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
RELEASE_TAG: ${{ inputs.tag }}
RELEASE_ID: ${{ needs.validate-release.outputs.release-id }}
RELEASE_COMMIT: ${{ needs.validate-release.outputs.commit }}
RELEASE_TARGET: ${{ needs.validate-release.outputs.target }}
RELEASE_ASSET: ${{ needs.release-baseline.outputs.release-asset }}
run: |
set -euo pipefail

[[ "$RELEASE_TAG" =~ ^v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$ ]] || exit 1
[[ "$RELEASE_ID" =~ ^[1-9][0-9]*$ ]] || exit 1
[[ "$RELEASE_COMMIT" =~ ^[0-9a-f]{40}$ ]] || exit 1
[[ "$RELEASE_ASSET" == "la-stack-${RELEASE_TAG}-criterion-baseline.tar.gz" ]] || exit 1
[[ -f "$RELEASE_ASSET" && ! -L "$RELEASE_ASSET" && -s "$RELEASE_ASSET" ]] || exit 1
digest="$(sha256sum "$RELEASE_ASSET")"
digest="sha256:${digest%% *}"
size="$(wc -c < "$RELEASE_ASSET")"
release_path="repos/$GH_REPO/releases/$RELEASE_ID"

check_draft() {
gh api "$release_path" | jq -e --arg tag "$RELEASE_TAG" --argjson id "$RELEASE_ID" '
.id == $id and .tag_name == $tag and .name == $tag
and .draft == true and .prerelease == false and .immutable == false
' > /dev/null
local current_commit
current_commit="$(gh api "repos/$GH_REPO/commits/refs/tags/$RELEASE_TAG" --jq .sha)"
[[ "$current_commit" == "$RELEASE_COMMIT" ]]
}
matching_assets() {
gh api "$release_path/assets?per_page=100" --paginate --slurp |
jq --arg name "$RELEASE_ASSET" '[.[][] | select(.name == $name)]'
}
verify_asset() {
jq -e --arg digest "$digest" --argjson size "$size" '
length == 1 and .[0].state == "uploaded"
and .[0].digest == $digest and .[0].size == $size
' > /dev/null
}

check_draft
assets="$(matching_assets)"
if [[ "$(jq length <<< "$assets")" == 0 ]]; then
# Address the captured release ID, never a replacement draft by tag.
gh api "https://uploads.github.com/$release_path/assets?name=$RELEASE_ASSET" \
--method POST --header 'Content-Type: application/gzip' \
--input "$RELEASE_ASSET" > /dev/null
elif ! verify_asset <<< "$assets"; then
echo "::error::Conflicting draft asset; inspect it before removing it and retrying"
exit 1
fi

# An interrupted upload can be reused only when its bytes match exactly.
# Recheck mutable state and tag after upload, before the irreversible step.
check_draft
matching_assets | verify_asset
gh api "$release_path" --method PATCH -F draft=false |
jq -e --arg tag "$RELEASE_TAG" --argjson id "$RELEASE_ID" '
.id == $id and .tag_name == $tag and .draft == false and .prerelease == false
' > /dev/null
printf '%s\n' "$RELEASE_TARGET" > "$RUNNER_TEMP/release-target.json"
uvx --python 3.15 --from research-repo-tools==0.1.9 research-repo-tools \
--root "$RUNNER_TEMP" performance release-upload "$GH_REPO" "$RELEASE_TAG" \
"$GITHUB_WORKSPACE/$RELEASE_ASSET" --target "$RUNNER_TEMP/release-target.json" --publish

- name: Release baseline summary
env:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/semgrep-sarif.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ jobs:
id: semgrep
run: |
set +e
uv run --locked semgrep \
uv run --locked --project tooling/semgrep semgrep \
--metrics off \
--error \
--strict \
Expand Down
2 changes: 1 addition & 1 deletion .python-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.14
3.15
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ Favor the invariant over a convenient edit or faster implementation.

## Python

- Support tooling targets Python 3.14. Use `uv run --locked` for all
- Support tooling targets Python 3.15. Use `uv run --locked` for all
Python scripts; never invoke `python` or `python3` directly.
- Use pytest, not unittest, and add type hints to new code.
- `just python-check` includes blocking type checking; all code must pass.
Expand Down
8 changes: 6 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ change files. Run bare `just` for the complete generated command list, including
arguments and descriptions. The [command policy](docs/dev/docs.md#command-discovery-and-placement)
owns recipe naming and documentation placement.

Setup, tool checks, updates, changelog, and opt-in CodeRabbit review use the published `research-repo-tools==0.1.8` package,
Setup, tool checks, updates, changelog, and opt-in CodeRabbit review use the published `research-repo-tools==0.1.9` package,
locked in the `tooling` dependency group and included by `dev`. Normal setup
and CI install it from PyPI through `uv sync --locked --group dev`; a sibling
checkout is unnecessary. To upgrade it deliberately, review the exact
Expand Down Expand Up @@ -100,7 +100,8 @@ adapter and authentication regressions belong to research-repo-tools. Hosted
CI runs a blocking finding check before a separate SARIF export; native SARIF
output alone does not fail on findings.

`just security` runs the shared OSV dependency audit for `uv.lock` and `Cargo.lock`,
`just security` runs the shared OSV dependency audit for `uv.lock`,
`tooling/semgrep/uv.lock` and `Cargo.lock`,
then the Gitleaks scan of reachable Git history and current tracked/nonignored
files. Run either gate separately with `just audit` or
`just security-secrets`. Setup installs both declared managed binaries. OSV needs
Expand Down Expand Up @@ -157,6 +158,9 @@ CodeRabbit's successful status on a skipped bot review is not an approval.
The [rollout guide](docs/dev/MANAGING_CHANGES.md#dependabot-approval-rollout)
owns GitHub settings and post-merge verification. The workflow never executes
PR code; dependency eligibility remains in `.github/dependabot.yml`.
Dependabot also monitors `tooling/semgrep/`; those scanner updates require manual
review because the trusted uv approval allowlist covers only the root manifest
and lockfile.

### Rust Toolchain Policy

Expand Down
Loading
Loading