tommyslate 0.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tommyslate-0.4.0/.github/workflows/ci.yml +107 -0
- tommyslate-0.4.0/.github/workflows/safe-slate-release.yml +597 -0
- tommyslate-0.4.0/.gitignore +11 -0
- tommyslate-0.4.0/CHANGELOG.md +233 -0
- tommyslate-0.4.0/CITATION.cff +14 -0
- tommyslate-0.4.0/Cargo.lock +1342 -0
- tommyslate-0.4.0/Cargo.toml +39 -0
- tommyslate-0.4.0/Dockerfile.sidecar +16 -0
- tommyslate-0.4.0/LICENSE +201 -0
- tommyslate-0.4.0/LICENSING.md +16 -0
- tommyslate-0.4.0/PKG-INFO +1480 -0
- tommyslate-0.4.0/PYPI_READINESS.md +60 -0
- tommyslate-0.4.0/README.md +1454 -0
- tommyslate-0.4.0/RELEASE_CHECKLIST.md +60 -0
- tommyslate-0.4.0/SECURITY.md +15 -0
- tommyslate-0.4.0/SOFTWARE_PROVENANCE.md +32 -0
- tommyslate-0.4.0/benchmarks/benchmark_candidate_pool.py +254 -0
- tommyslate-0.4.0/benchmarks/benchmark_guarded_rerank.py +256 -0
- tommyslate-0.4.0/build.rs +13 -0
- tommyslate-0.4.0/docs/api-reference.md +77 -0
- tommyslate-0.4.0/docs/compatibility.md +46 -0
- tommyslate-0.4.0/docs/quickstart.md +85 -0
- tommyslate-0.4.0/docs/research-integration.md +91 -0
- tommyslate-0.4.0/docs/research-lineage.json +223 -0
- tommyslate-0.4.0/docs/sidecar.md +28 -0
- tommyslate-0.4.0/external-inputs.template.json +104 -0
- tommyslate-0.4.0/library.json +41 -0
- tommyslate-0.4.0/proto/safe_slate_reranker.proto +55 -0
- tommyslate-0.4.0/pyproject.toml +61 -0
- tommyslate-0.4.0/python/safe_slate/__init__.py +291 -0
- tommyslate-0.4.0/python/safe_slate/_core.pyi +205 -0
- tommyslate-0.4.0/python/safe_slate/calibration.py +99 -0
- tommyslate-0.4.0/python/safe_slate/contract.py +574 -0
- tommyslate-0.4.0/python/safe_slate/estimation.py +2189 -0
- tommyslate-0.4.0/python/safe_slate/evidence.py +695 -0
- tommyslate-0.4.0/python/safe_slate/external_intake.py +322 -0
- tommyslate-0.4.0/python/safe_slate/feedback.py +250 -0
- tommyslate-0.4.0/python/safe_slate/gates.py +159 -0
- tommyslate-0.4.0/python/safe_slate/input_manifest.py +669 -0
- tommyslate-0.4.0/python/safe_slate/log_io.py +341 -0
- tommyslate-0.4.0/python/safe_slate/online.py +505 -0
- tommyslate-0.4.0/python/safe_slate/ope.py +1278 -0
- tommyslate-0.4.0/python/safe_slate/population_positivity.py +505 -0
- tommyslate-0.4.0/python/safe_slate/production.py +904 -0
- tommyslate-0.4.0/python/safe_slate/production_intake.py +287 -0
- tommyslate-0.4.0/python/safe_slate/py.typed +1 -0
- tommyslate-0.4.0/python/safe_slate/runner.py +469 -0
- tommyslate-0.4.0/python/safe_slate/service_trace.py +703 -0
- tommyslate-0.4.0/python/safe_slate/shadow.py +559 -0
- tommyslate-0.4.0/python/safe_slate/shadow_contract.py +305 -0
- tommyslate-0.4.0/python/safe_slate/shadow_evidence.py +715 -0
- tommyslate-0.4.0/python/safe_slate/uncertainty_binding.py +299 -0
- tommyslate-0.4.0/python/safe_slate/uncertainty_utils.py +107 -0
- tommyslate-0.4.0/schemas/model_bundle.schema.json +52 -0
- tommyslate-0.4.0/schemas/production_grpc_envelope.schema.json +72 -0
- tommyslate-0.4.0/schemas/service_trace_completion.schema.json +109 -0
- tommyslate-0.4.0/scripts/check_coverage.py +198 -0
- tommyslate-0.4.0/scripts/verify_release_artifacts.py +217 -0
- tommyslate-0.4.0/src/bin/safe_slate_sidecar.rs +29 -0
- tommyslate-0.4.0/src/constraints.rs +235 -0
- tommyslate-0.4.0/src/lib.rs +1798 -0
- tommyslate-0.4.0/src/oracle.rs +964 -0
- tommyslate-0.4.0/src/sidecar.rs +1156 -0
- tommyslate-0.4.0/src/uncertainty.rs +275 -0
- tommyslate-0.4.0/tests/test_api.py +223 -0
- tommyslate-0.4.0/tests/test_benchmark_cli.py +94 -0
- tommyslate-0.4.0/tests/test_calibration.py +101 -0
- tommyslate-0.4.0/tests/test_candidate_pool_benchmark.py +116 -0
- tommyslate-0.4.0/tests/test_contract.py +486 -0
- tommyslate-0.4.0/tests/test_coverage_gate.py +172 -0
- tommyslate-0.4.0/tests/test_distribution_migration.py +45 -0
- tommyslate-0.4.0/tests/test_estimation.py +1302 -0
- tommyslate-0.4.0/tests/test_evidence.py +1101 -0
- tommyslate-0.4.0/tests/test_external_intake.py +594 -0
- tommyslate-0.4.0/tests/test_feedback.py +252 -0
- tommyslate-0.4.0/tests/test_gates.py +134 -0
- tommyslate-0.4.0/tests/test_input_manifest.py +584 -0
- tommyslate-0.4.0/tests/test_log_io.py +208 -0
- tommyslate-0.4.0/tests/test_manifest_bound_ope.py +676 -0
- tommyslate-0.4.0/tests/test_matrix_digest.py +61 -0
- tommyslate-0.4.0/tests/test_native_error_contracts.py +251 -0
- tommyslate-0.4.0/tests/test_online.py +275 -0
- tommyslate-0.4.0/tests/test_ope.py +492 -0
- tommyslate-0.4.0/tests/test_population_positivity.py +308 -0
- tommyslate-0.4.0/tests/test_production.py +491 -0
- tommyslate-0.4.0/tests/test_production_intake.py +215 -0
- tommyslate-0.4.0/tests/test_public_contract.py +163 -0
- tommyslate-0.4.0/tests/test_release_artifacts.py +105 -0
- tommyslate-0.4.0/tests/test_replacements.py +260 -0
- tommyslate-0.4.0/tests/test_research_lineage.py +145 -0
- tommyslate-0.4.0/tests/test_runner.py +342 -0
- tommyslate-0.4.0/tests/test_service_trace.py +752 -0
- tommyslate-0.4.0/tests/test_shadow.py +400 -0
- tommyslate-0.4.0/tests/test_shadow_contract.py +141 -0
- tommyslate-0.4.0/tests/test_shadow_evidence.py +867 -0
- tommyslate-0.4.0/tests/test_uncertainty_binding.py +464 -0
- tommyslate-0.4.0/tests/test_uncertainty_utils.py +42 -0
- tommyslate-0.4.0/uv.lock +302 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
name: Verify library
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
|
|
11
|
+
env:
|
|
12
|
+
PYTHON_VERSION: "3.15"
|
|
13
|
+
RUST_VERSION: "1.99.0"
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
verify:
|
|
17
|
+
runs-on: ubuntu-24.04
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
20
|
+
with:
|
|
21
|
+
persist-credentials: false
|
|
22
|
+
- uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
|
|
23
|
+
with:
|
|
24
|
+
python-version: ${{ env.PYTHON_VERSION }}
|
|
25
|
+
- name: Install fixed toolchains
|
|
26
|
+
run: |
|
|
27
|
+
rustup toolchain install "$RUST_VERSION" --profile minimal --component clippy
|
|
28
|
+
python -m pip install --disable-pip-version-check uv==0.13.1
|
|
29
|
+
- name: Install locked dependencies and native extension
|
|
30
|
+
run: |
|
|
31
|
+
uv sync --frozen
|
|
32
|
+
uv run maturin develop --release
|
|
33
|
+
- name: Verify Python API and types
|
|
34
|
+
run: |
|
|
35
|
+
uv run pytest
|
|
36
|
+
uv run mypy python/safe_slate --check-untyped-defs
|
|
37
|
+
uv run python -m mypy.stubtest safe_slate._core
|
|
38
|
+
uv run python -m compileall -q python/safe_slate scripts tests
|
|
39
|
+
- name: Verify Rust backend
|
|
40
|
+
run: |
|
|
41
|
+
cargo +"$RUST_VERSION" test --locked --all-targets
|
|
42
|
+
cargo +"$RUST_VERSION" clippy --locked --all-targets -- -D warnings
|
|
43
|
+
uv run python -c "import safe_slate._core as core; assert not hasattr(core, '_run_coverage_suite')"
|
|
44
|
+
- name: Verify sidecar container definition
|
|
45
|
+
run: docker build --check -f Dockerfile.sidecar .
|
|
46
|
+
- name: Verify local distributions
|
|
47
|
+
run: |
|
|
48
|
+
uv run maturin build --release --locked --compatibility pypi -i python3.15 --out dist
|
|
49
|
+
uv run maturin sdist --out dist
|
|
50
|
+
uvx --from twine==6.2.0 twine check dist/*
|
|
51
|
+
uv venv "$RUNNER_TEMP/wheel-check" --python python3.15
|
|
52
|
+
uv pip install --python "$RUNNER_TEMP/wheel-check/bin/python" --no-deps dist/*.whl
|
|
53
|
+
"$RUNNER_TEMP/wheel-check/bin/python" -c "import safe_slate._core as core; assert not hasattr(core, '_run_coverage_suite')"
|
|
54
|
+
|
|
55
|
+
coverage:
|
|
56
|
+
runs-on: ubuntu-24.04
|
|
57
|
+
env:
|
|
58
|
+
COVERAGE_FILE: coverage/.coverage
|
|
59
|
+
RUSTUP_TOOLCHAIN: "1.99.0"
|
|
60
|
+
steps:
|
|
61
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
62
|
+
with:
|
|
63
|
+
persist-credentials: false
|
|
64
|
+
- uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
|
|
65
|
+
with:
|
|
66
|
+
python-version: ${{ env.PYTHON_VERSION }}
|
|
67
|
+
- name: Install fixed coverage toolchains
|
|
68
|
+
run: |
|
|
69
|
+
rustup toolchain install "$RUST_VERSION" --profile minimal --component clippy
|
|
70
|
+
rustup component add llvm-tools-preview --toolchain "$RUST_VERSION"
|
|
71
|
+
python -m pip install --disable-pip-version-check uv==0.13.1
|
|
72
|
+
cargo +"$RUST_VERSION" install cargo-llvm-cov --version 0.9.1 --locked
|
|
73
|
+
uv sync --frozen --no-install-project
|
|
74
|
+
- name: Collect Python and Rust coverage
|
|
75
|
+
shell: bash
|
|
76
|
+
run: |
|
|
77
|
+
set -euo pipefail
|
|
78
|
+
mkdir -p coverage
|
|
79
|
+
cargo llvm-cov clean --workspace
|
|
80
|
+
eval "$(cargo llvm-cov show-env --sh)"
|
|
81
|
+
uv run maturin develop
|
|
82
|
+
uv run coverage run -m pytest
|
|
83
|
+
uv run python -c "import safe_slate._core as core; core._run_coverage_suite()"
|
|
84
|
+
uv run coverage report --show-missing | tee coverage/python.txt
|
|
85
|
+
uv run coverage json -o coverage/python.json
|
|
86
|
+
uv run coverage xml -o coverage/python.xml
|
|
87
|
+
uv run coverage html -d coverage/python-html
|
|
88
|
+
cargo llvm-cov report --show-missing-lines 2>&1 | tee coverage/rust.txt
|
|
89
|
+
if grep -q "mismatched data" coverage/rust.txt; then
|
|
90
|
+
echo "Rust coverage profiles are incompatible" >&2
|
|
91
|
+
exit 1
|
|
92
|
+
fi
|
|
93
|
+
cargo llvm-cov report --json --summary-only --output-path coverage/rust.json
|
|
94
|
+
cargo llvm-cov report --html --output-dir coverage/rust-html
|
|
95
|
+
- name: Enforce coverage contract
|
|
96
|
+
run: >-
|
|
97
|
+
uv run python scripts/check_coverage.py
|
|
98
|
+
--python-json coverage/python.json
|
|
99
|
+
--rust-json coverage/rust.json
|
|
100
|
+
- name: Upload coverage reports
|
|
101
|
+
if: always()
|
|
102
|
+
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
|
103
|
+
with:
|
|
104
|
+
name: coverage-reports
|
|
105
|
+
path: coverage
|
|
106
|
+
if-no-files-found: warn
|
|
107
|
+
retention-days: 14
|
|
@@ -0,0 +1,597 @@
|
|
|
1
|
+
name: Qualify and publish TommySlate
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
inputs:
|
|
6
|
+
source_sha:
|
|
7
|
+
description: Exact 40-character source commit SHA to build
|
|
8
|
+
required: true
|
|
9
|
+
type: string
|
|
10
|
+
publish_testpypi:
|
|
11
|
+
description: Publish the verified artifacts to TestPyPI
|
|
12
|
+
required: true
|
|
13
|
+
default: false
|
|
14
|
+
type: boolean
|
|
15
|
+
publish_pypi:
|
|
16
|
+
description: Publish the same TestPyPI-verified artifacts to production PyPI
|
|
17
|
+
required: true
|
|
18
|
+
default: false
|
|
19
|
+
type: boolean
|
|
20
|
+
|
|
21
|
+
permissions:
|
|
22
|
+
contents: read
|
|
23
|
+
|
|
24
|
+
env:
|
|
25
|
+
PYTHON_VERSION: "3.15"
|
|
26
|
+
RUST_VERSION: "1.99.0"
|
|
27
|
+
MATURIN_VERSION: "v1.16.0"
|
|
28
|
+
PACKAGE_NAME: "tommyslate"
|
|
29
|
+
PACKAGE_VERSION: "0.4.0"
|
|
30
|
+
|
|
31
|
+
jobs:
|
|
32
|
+
validate-source:
|
|
33
|
+
runs-on: ubuntu-24.04
|
|
34
|
+
steps:
|
|
35
|
+
- name: Validate source SHA format
|
|
36
|
+
env:
|
|
37
|
+
SOURCE_SHA: ${{ inputs.source_sha }}
|
|
38
|
+
PUBLISH_TESTPYPI: ${{ inputs.publish_testpypi }}
|
|
39
|
+
PUBLISH_PYPI: ${{ inputs.publish_pypi }}
|
|
40
|
+
run: |
|
|
41
|
+
if [[ ! "$SOURCE_SHA" =~ ^[0-9a-f]{40}$ ]]; then
|
|
42
|
+
echo "source_sha must be a lowercase 40-character commit SHA" >&2
|
|
43
|
+
exit 1
|
|
44
|
+
fi
|
|
45
|
+
if [[ "$PUBLISH_PYPI" == "true" && "$PUBLISH_TESTPYPI" != "true" ]]; then
|
|
46
|
+
echo "production PyPI publication requires the same run to publish and verify TestPyPI first" >&2
|
|
47
|
+
exit 1
|
|
48
|
+
fi
|
|
49
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
50
|
+
with:
|
|
51
|
+
ref: ${{ inputs.source_sha }}
|
|
52
|
+
fetch-depth: 1
|
|
53
|
+
persist-credentials: false
|
|
54
|
+
- name: Confirm source and package contract
|
|
55
|
+
env:
|
|
56
|
+
SOURCE_SHA: ${{ inputs.source_sha }}
|
|
57
|
+
run: |
|
|
58
|
+
test "$(git rev-parse HEAD)" = "$SOURCE_SHA"
|
|
59
|
+
python3 - <<'PY'
|
|
60
|
+
import tomllib
|
|
61
|
+
import os
|
|
62
|
+
from pathlib import Path
|
|
63
|
+
|
|
64
|
+
pyproject = tomllib.loads(Path("pyproject.toml").read_text())
|
|
65
|
+
cargo = tomllib.loads(Path("Cargo.toml").read_text())
|
|
66
|
+
expected = os.environ["PACKAGE_VERSION"]
|
|
67
|
+
assert pyproject["project"]["version"] == expected
|
|
68
|
+
assert cargo["package"]["version"] == expected
|
|
69
|
+
assert pyproject["project"]["name"] == os.environ["PACKAGE_NAME"]
|
|
70
|
+
assert cargo["package"]["name"] == os.environ["PACKAGE_NAME"]
|
|
71
|
+
assert pyproject["tool"]["maturin"]["module-name"] == "safe_slate._core"
|
|
72
|
+
assert pyproject["project"]["license"] == "Apache-2.0"
|
|
73
|
+
assert cargo["package"]["license"] == "Apache-2.0"
|
|
74
|
+
PY
|
|
75
|
+
|
|
76
|
+
build-wheels:
|
|
77
|
+
name: Build and smoke ${{ matrix.platform.name }}
|
|
78
|
+
needs: validate-source
|
|
79
|
+
strategy:
|
|
80
|
+
fail-fast: false
|
|
81
|
+
matrix:
|
|
82
|
+
platform:
|
|
83
|
+
- name: linux-x86_64
|
|
84
|
+
os: ubuntu-24.04
|
|
85
|
+
target: x86_64-unknown-linux-gnu
|
|
86
|
+
manylinux: "2_28"
|
|
87
|
+
deployment-target: ""
|
|
88
|
+
- name: linux-aarch64
|
|
89
|
+
os: ubuntu-24.04-arm
|
|
90
|
+
target: aarch64-unknown-linux-gnu
|
|
91
|
+
manylinux: "2_28"
|
|
92
|
+
deployment-target: ""
|
|
93
|
+
- name: macos-x86_64
|
|
94
|
+
os: macos-15-intel
|
|
95
|
+
target: x86_64-apple-darwin
|
|
96
|
+
manylinux: ""
|
|
97
|
+
deployment-target: "11.0"
|
|
98
|
+
- name: macos-arm64
|
|
99
|
+
os: macos-15
|
|
100
|
+
target: aarch64-apple-darwin
|
|
101
|
+
manylinux: ""
|
|
102
|
+
deployment-target: "11.0"
|
|
103
|
+
- name: windows-x86_64
|
|
104
|
+
os: windows-2025
|
|
105
|
+
target: x86_64-pc-windows-msvc
|
|
106
|
+
manylinux: ""
|
|
107
|
+
deployment-target: ""
|
|
108
|
+
runs-on: ${{ matrix.platform.os }}
|
|
109
|
+
env:
|
|
110
|
+
MACOSX_DEPLOYMENT_TARGET: ${{ matrix.platform.deployment-target }}
|
|
111
|
+
steps:
|
|
112
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
113
|
+
with:
|
|
114
|
+
ref: ${{ inputs.source_sha }}
|
|
115
|
+
fetch-depth: 1
|
|
116
|
+
persist-credentials: false
|
|
117
|
+
- uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
|
|
118
|
+
with:
|
|
119
|
+
python-version: ${{ env.PYTHON_VERSION }}
|
|
120
|
+
- name: Build wheel
|
|
121
|
+
uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1.51.0
|
|
122
|
+
with:
|
|
123
|
+
command: build
|
|
124
|
+
maturin-version: ${{ env.MATURIN_VERSION }}
|
|
125
|
+
rust-toolchain: ${{ env.RUST_VERSION }}
|
|
126
|
+
target: ${{ matrix.platform.target }}
|
|
127
|
+
manylinux: ${{ matrix.platform.manylinux }}
|
|
128
|
+
args: >-
|
|
129
|
+
--release --locked --compatibility pypi
|
|
130
|
+
-i python3.15 --out dist
|
|
131
|
+
- name: Install and smoke-test local wheel
|
|
132
|
+
shell: bash
|
|
133
|
+
run: |
|
|
134
|
+
python - <<'PY'
|
|
135
|
+
import os
|
|
136
|
+
from pathlib import Path
|
|
137
|
+
import subprocess
|
|
138
|
+
import tempfile
|
|
139
|
+
import venv
|
|
140
|
+
|
|
141
|
+
wheels = list(Path("dist").resolve().glob("*.whl"))
|
|
142
|
+
assert len(wheels) == 1, wheels
|
|
143
|
+
environment = Path(tempfile.mkdtemp(prefix="tommyslate-wheel-"))
|
|
144
|
+
venv.EnvBuilder(with_pip=True).create(environment)
|
|
145
|
+
python = environment / ("Scripts/python.exe" if os.name == "nt" else "bin/python")
|
|
146
|
+
subprocess.run(
|
|
147
|
+
[python, "-m", "pip", "install", "--no-index", str(wheels[0])],
|
|
148
|
+
check=True,
|
|
149
|
+
)
|
|
150
|
+
subprocess.run([python, "-m", "pip", "check"], check=True)
|
|
151
|
+
smoke = """
|
|
152
|
+
from safe_slate import optimize_slate
|
|
153
|
+
kwargs = dict(
|
|
154
|
+
rewards=[2.0, 1.0],
|
|
155
|
+
safety_means=[1.0, 1.0],
|
|
156
|
+
safety_variances=[0.0, 0.0],
|
|
157
|
+
max_items=1,
|
|
158
|
+
)
|
|
159
|
+
first = optimize_slate(**kwargs)
|
|
160
|
+
second = optimize_slate(**kwargs)
|
|
161
|
+
assert first.selected_indices == second.selected_indices == [0]
|
|
162
|
+
assert first.rechecked and second.rechecked
|
|
163
|
+
"""
|
|
164
|
+
subprocess.run([python, "-c", smoke], cwd=environment, check=True)
|
|
165
|
+
PY
|
|
166
|
+
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
|
167
|
+
with:
|
|
168
|
+
name: wheel-${{ matrix.platform.name }}
|
|
169
|
+
path: dist/*.whl
|
|
170
|
+
if-no-files-found: error
|
|
171
|
+
retention-days: 14
|
|
172
|
+
|
|
173
|
+
build-sdist:
|
|
174
|
+
runs-on: ubuntu-24.04
|
|
175
|
+
needs: validate-source
|
|
176
|
+
steps:
|
|
177
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
178
|
+
with:
|
|
179
|
+
ref: ${{ inputs.source_sha }}
|
|
180
|
+
fetch-depth: 1
|
|
181
|
+
persist-credentials: false
|
|
182
|
+
- name: Build sdist
|
|
183
|
+
uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1.51.0
|
|
184
|
+
with:
|
|
185
|
+
command: sdist
|
|
186
|
+
maturin-version: ${{ env.MATURIN_VERSION }}
|
|
187
|
+
rust-toolchain: ${{ env.RUST_VERSION }}
|
|
188
|
+
args: --out dist
|
|
189
|
+
- name: Confirm locked Rust graph
|
|
190
|
+
run: cargo +${{ env.RUST_VERSION }} metadata --locked --format-version 1 > /dev/null
|
|
191
|
+
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
|
192
|
+
with:
|
|
193
|
+
name: sdist
|
|
194
|
+
path: dist/*.tar.gz
|
|
195
|
+
if-no-files-found: error
|
|
196
|
+
retention-days: 14
|
|
197
|
+
|
|
198
|
+
aggregate:
|
|
199
|
+
name: Verify complete release set
|
|
200
|
+
needs: [build-wheels, build-sdist]
|
|
201
|
+
runs-on: ubuntu-24.04
|
|
202
|
+
steps:
|
|
203
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
204
|
+
with:
|
|
205
|
+
ref: ${{ inputs.source_sha }}
|
|
206
|
+
fetch-depth: 1
|
|
207
|
+
persist-credentials: false
|
|
208
|
+
- uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
|
|
209
|
+
with:
|
|
210
|
+
python-version: ${{ env.PYTHON_VERSION }}
|
|
211
|
+
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
|
|
212
|
+
with:
|
|
213
|
+
pattern: wheel-*
|
|
214
|
+
path: incoming
|
|
215
|
+
merge-multiple: true
|
|
216
|
+
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
|
|
217
|
+
with:
|
|
218
|
+
name: sdist
|
|
219
|
+
path: incoming
|
|
220
|
+
- name: Check metadata and generate immutable inventory
|
|
221
|
+
env:
|
|
222
|
+
SOURCE_SHA: ${{ inputs.source_sha }}
|
|
223
|
+
run: |
|
|
224
|
+
python -m pip install --disable-pip-version-check twine==6.2.0
|
|
225
|
+
python -m twine check incoming/*
|
|
226
|
+
mkdir -p release/packages
|
|
227
|
+
cp incoming/*.whl incoming/*.tar.gz release/packages/
|
|
228
|
+
python scripts/verify_release_artifacts.py \
|
|
229
|
+
incoming \
|
|
230
|
+
--source-sha "$SOURCE_SHA" \
|
|
231
|
+
--version "$PACKAGE_VERSION" \
|
|
232
|
+
--output-dir release
|
|
233
|
+
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
|
234
|
+
with:
|
|
235
|
+
name: qualified-release-${{ inputs.source_sha }}
|
|
236
|
+
path: release
|
|
237
|
+
if-no-files-found: error
|
|
238
|
+
retention-days: 14
|
|
239
|
+
|
|
240
|
+
testpypi-preflight:
|
|
241
|
+
name: Check TestPyPI retry safety
|
|
242
|
+
if: inputs.publish_testpypi == true
|
|
243
|
+
needs: aggregate
|
|
244
|
+
runs-on: ubuntu-24.04
|
|
245
|
+
steps:
|
|
246
|
+
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
|
|
247
|
+
with:
|
|
248
|
+
name: qualified-release-${{ inputs.source_sha }}
|
|
249
|
+
path: release
|
|
250
|
+
- name: Reject conflicting existing files
|
|
251
|
+
run: |
|
|
252
|
+
python - <<'PY'
|
|
253
|
+
import json
|
|
254
|
+
import os
|
|
255
|
+
from pathlib import Path
|
|
256
|
+
from urllib.error import HTTPError
|
|
257
|
+
from urllib.request import urlopen
|
|
258
|
+
|
|
259
|
+
manifest = json.loads(Path("release/release-manifest.json").read_text())
|
|
260
|
+
expected = {item["filename"]: item["sha256"] for item in manifest["files"]}
|
|
261
|
+
version = os.environ["PACKAGE_VERSION"]
|
|
262
|
+
project = os.environ["PACKAGE_NAME"]
|
|
263
|
+
url = f"https://test.pypi.org/pypi/{project}/{version}/json"
|
|
264
|
+
try:
|
|
265
|
+
with urlopen(url, timeout=30) as response:
|
|
266
|
+
payload = json.load(response)
|
|
267
|
+
except HTTPError as error:
|
|
268
|
+
if error.code != 404:
|
|
269
|
+
raise
|
|
270
|
+
payload = {"urls": []}
|
|
271
|
+
for item in payload["urls"]:
|
|
272
|
+
filename = item["filename"]
|
|
273
|
+
remote_hash = item["digests"]["sha256"]
|
|
274
|
+
if filename not in expected or expected[filename] != remote_hash:
|
|
275
|
+
raise SystemExit(f"conflicting TestPyPI file: {filename}")
|
|
276
|
+
print(f"preflight accepted {len(payload['urls'])} existing file(s)")
|
|
277
|
+
PY
|
|
278
|
+
|
|
279
|
+
publish-testpypi:
|
|
280
|
+
name: Publish to TestPyPI with OIDC
|
|
281
|
+
if: inputs.publish_testpypi == true
|
|
282
|
+
needs: testpypi-preflight
|
|
283
|
+
runs-on: ubuntu-24.04
|
|
284
|
+
environment:
|
|
285
|
+
name: testpypi
|
|
286
|
+
url: https://test.pypi.org/p/tommyslate/
|
|
287
|
+
permissions:
|
|
288
|
+
contents: read
|
|
289
|
+
id-token: write
|
|
290
|
+
steps:
|
|
291
|
+
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
|
|
292
|
+
with:
|
|
293
|
+
name: qualified-release-${{ inputs.source_sha }}
|
|
294
|
+
path: release
|
|
295
|
+
- name: Publish immutable packages
|
|
296
|
+
uses: pypa/gh-action-pypi-publish@7245906fb61fa33a13554f9d5fb856fe61758086 # v1.14.3
|
|
297
|
+
with:
|
|
298
|
+
repository-url: https://test.pypi.org/legacy/
|
|
299
|
+
packages-dir: release/packages
|
|
300
|
+
skip-existing: true
|
|
301
|
+
verify-metadata: true
|
|
302
|
+
print-hash: true
|
|
303
|
+
attestations: true
|
|
304
|
+
|
|
305
|
+
verify-testpypi:
|
|
306
|
+
name: Match TestPyPI hashes
|
|
307
|
+
if: inputs.publish_testpypi == true
|
|
308
|
+
needs: publish-testpypi
|
|
309
|
+
runs-on: ubuntu-24.04
|
|
310
|
+
steps:
|
|
311
|
+
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
|
|
312
|
+
with:
|
|
313
|
+
name: qualified-release-${{ inputs.source_sha }}
|
|
314
|
+
path: release
|
|
315
|
+
- name: Compare remote files with manifest
|
|
316
|
+
run: |
|
|
317
|
+
python - <<'PY'
|
|
318
|
+
import json
|
|
319
|
+
import os
|
|
320
|
+
from pathlib import Path
|
|
321
|
+
import time
|
|
322
|
+
from urllib.error import HTTPError
|
|
323
|
+
from urllib.request import urlopen
|
|
324
|
+
|
|
325
|
+
manifest = json.loads(Path("release/release-manifest.json").read_text())
|
|
326
|
+
expected = {item["filename"]: item["sha256"] for item in manifest["files"]}
|
|
327
|
+
version = os.environ["PACKAGE_VERSION"]
|
|
328
|
+
project = os.environ["PACKAGE_NAME"]
|
|
329
|
+
url = f"https://test.pypi.org/pypi/{project}/{version}/json"
|
|
330
|
+
remote = None
|
|
331
|
+
for attempt in range(12):
|
|
332
|
+
try:
|
|
333
|
+
with urlopen(url, timeout=30) as response:
|
|
334
|
+
payload = json.load(response)
|
|
335
|
+
remote = {
|
|
336
|
+
item["filename"]: item["digests"]["sha256"]
|
|
337
|
+
for item in payload["urls"]
|
|
338
|
+
}
|
|
339
|
+
except HTTPError as error:
|
|
340
|
+
if error.code != 404:
|
|
341
|
+
raise
|
|
342
|
+
if remote == expected:
|
|
343
|
+
break
|
|
344
|
+
if attempt < 11:
|
|
345
|
+
time.sleep(10)
|
|
346
|
+
if remote != expected:
|
|
347
|
+
raise SystemExit(
|
|
348
|
+
f"TestPyPI files differ from manifest: expected={expected}, remote={remote}"
|
|
349
|
+
)
|
|
350
|
+
print("TestPyPI filenames and SHA-256 values match the release manifest")
|
|
351
|
+
PY
|
|
352
|
+
|
|
353
|
+
smoke-test-testpypi:
|
|
354
|
+
name: Install from TestPyPI on ${{ matrix.platform.name }}
|
|
355
|
+
if: inputs.publish_testpypi == true
|
|
356
|
+
needs: verify-testpypi
|
|
357
|
+
strategy:
|
|
358
|
+
fail-fast: false
|
|
359
|
+
matrix:
|
|
360
|
+
platform:
|
|
361
|
+
- name: linux-x86_64
|
|
362
|
+
os: ubuntu-24.04
|
|
363
|
+
- name: linux-aarch64
|
|
364
|
+
os: ubuntu-24.04-arm
|
|
365
|
+
- name: macos-x86_64
|
|
366
|
+
os: macos-15-intel
|
|
367
|
+
- name: macos-arm64
|
|
368
|
+
os: macos-15
|
|
369
|
+
- name: windows-x86_64
|
|
370
|
+
os: windows-2025
|
|
371
|
+
runs-on: ${{ matrix.platform.os }}
|
|
372
|
+
steps:
|
|
373
|
+
- uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
|
|
374
|
+
with:
|
|
375
|
+
python-version: ${{ env.PYTHON_VERSION }}
|
|
376
|
+
- name: Install exact TestPyPI version and smoke-test
|
|
377
|
+
shell: bash
|
|
378
|
+
run: |
|
|
379
|
+
python - <<'PY'
|
|
380
|
+
import os
|
|
381
|
+
from pathlib import Path
|
|
382
|
+
import subprocess
|
|
383
|
+
import tempfile
|
|
384
|
+
import venv
|
|
385
|
+
|
|
386
|
+
environment = Path(tempfile.mkdtemp(prefix="tommyslate-testpypi-"))
|
|
387
|
+
venv.EnvBuilder(with_pip=True).create(environment)
|
|
388
|
+
python = environment / ("Scripts/python.exe" if os.name == "nt" else "bin/python")
|
|
389
|
+
subprocess.run(
|
|
390
|
+
[
|
|
391
|
+
python,
|
|
392
|
+
"-m",
|
|
393
|
+
"pip",
|
|
394
|
+
"install",
|
|
395
|
+
"--index-url",
|
|
396
|
+
"https://test.pypi.org/simple/",
|
|
397
|
+
"--no-deps",
|
|
398
|
+
f"{os.environ['PACKAGE_NAME']}=={os.environ['PACKAGE_VERSION']}",
|
|
399
|
+
],
|
|
400
|
+
check=True,
|
|
401
|
+
)
|
|
402
|
+
subprocess.run([python, "-m", "pip", "check"], check=True)
|
|
403
|
+
smoke = """
|
|
404
|
+
from safe_slate import optimize_slate
|
|
405
|
+
kwargs = dict(
|
|
406
|
+
rewards=[2.0, 1.0],
|
|
407
|
+
safety_means=[1.0, 1.0],
|
|
408
|
+
safety_variances=[0.0, 0.0],
|
|
409
|
+
max_items=1,
|
|
410
|
+
)
|
|
411
|
+
first = optimize_slate(**kwargs)
|
|
412
|
+
second = optimize_slate(**kwargs)
|
|
413
|
+
assert first.selected_indices == second.selected_indices == [0]
|
|
414
|
+
assert first.rechecked and second.rechecked
|
|
415
|
+
"""
|
|
416
|
+
subprocess.run([python, "-c", smoke], cwd=environment, check=True)
|
|
417
|
+
PY
|
|
418
|
+
|
|
419
|
+
pypi-preflight:
|
|
420
|
+
name: Check production PyPI retry safety
|
|
421
|
+
if: inputs.publish_pypi == true
|
|
422
|
+
needs: smoke-test-testpypi
|
|
423
|
+
runs-on: ubuntu-24.04
|
|
424
|
+
steps:
|
|
425
|
+
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
|
|
426
|
+
with:
|
|
427
|
+
name: qualified-release-${{ inputs.source_sha }}
|
|
428
|
+
path: release
|
|
429
|
+
- name: Reject conflicting existing production files
|
|
430
|
+
run: |
|
|
431
|
+
python - <<'PY'
|
|
432
|
+
import json
|
|
433
|
+
import os
|
|
434
|
+
from pathlib import Path
|
|
435
|
+
from urllib.error import HTTPError
|
|
436
|
+
from urllib.request import urlopen
|
|
437
|
+
|
|
438
|
+
manifest = json.loads(Path("release/release-manifest.json").read_text())
|
|
439
|
+
expected = {item["filename"]: item["sha256"] for item in manifest["files"]}
|
|
440
|
+
version = os.environ["PACKAGE_VERSION"]
|
|
441
|
+
project = os.environ["PACKAGE_NAME"]
|
|
442
|
+
url = f"https://pypi.org/pypi/{project}/{version}/json"
|
|
443
|
+
try:
|
|
444
|
+
with urlopen(url, timeout=30) as response:
|
|
445
|
+
payload = json.load(response)
|
|
446
|
+
except HTTPError as error:
|
|
447
|
+
if error.code != 404:
|
|
448
|
+
raise
|
|
449
|
+
payload = {"urls": []}
|
|
450
|
+
for item in payload["urls"]:
|
|
451
|
+
filename = item["filename"]
|
|
452
|
+
remote_hash = item["digests"]["sha256"]
|
|
453
|
+
if filename not in expected or expected[filename] != remote_hash:
|
|
454
|
+
raise SystemExit(f"conflicting production PyPI file: {filename}")
|
|
455
|
+
print(f"production preflight accepted {len(payload['urls'])} existing file(s)")
|
|
456
|
+
PY
|
|
457
|
+
|
|
458
|
+
publish-pypi:
|
|
459
|
+
name: Publish to production PyPI with OIDC
|
|
460
|
+
if: inputs.publish_pypi == true
|
|
461
|
+
needs: pypi-preflight
|
|
462
|
+
runs-on: ubuntu-24.04
|
|
463
|
+
environment:
|
|
464
|
+
name: pypi
|
|
465
|
+
url: https://pypi.org/p/tommyslate/
|
|
466
|
+
permissions:
|
|
467
|
+
contents: read
|
|
468
|
+
id-token: write
|
|
469
|
+
steps:
|
|
470
|
+
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
|
|
471
|
+
with:
|
|
472
|
+
name: qualified-release-${{ inputs.source_sha }}
|
|
473
|
+
path: release
|
|
474
|
+
- name: Publish immutable production packages
|
|
475
|
+
uses: pypa/gh-action-pypi-publish@7245906fb61fa33a13554f9d5fb856fe61758086 # v1.14.3
|
|
476
|
+
with:
|
|
477
|
+
packages-dir: release/packages
|
|
478
|
+
skip-existing: true
|
|
479
|
+
verify-metadata: true
|
|
480
|
+
print-hash: true
|
|
481
|
+
attestations: true
|
|
482
|
+
|
|
483
|
+
verify-pypi:
|
|
484
|
+
name: Match production PyPI hashes
|
|
485
|
+
if: inputs.publish_pypi == true
|
|
486
|
+
needs: publish-pypi
|
|
487
|
+
runs-on: ubuntu-24.04
|
|
488
|
+
steps:
|
|
489
|
+
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
|
|
490
|
+
with:
|
|
491
|
+
name: qualified-release-${{ inputs.source_sha }}
|
|
492
|
+
path: release
|
|
493
|
+
- name: Compare production files with manifest
|
|
494
|
+
run: |
|
|
495
|
+
python - <<'PY'
|
|
496
|
+
import json
|
|
497
|
+
import os
|
|
498
|
+
from pathlib import Path
|
|
499
|
+
import time
|
|
500
|
+
from urllib.error import HTTPError
|
|
501
|
+
from urllib.request import urlopen
|
|
502
|
+
|
|
503
|
+
manifest = json.loads(Path("release/release-manifest.json").read_text())
|
|
504
|
+
expected = {item["filename"]: item["sha256"] for item in manifest["files"]}
|
|
505
|
+
version = os.environ["PACKAGE_VERSION"]
|
|
506
|
+
project = os.environ["PACKAGE_NAME"]
|
|
507
|
+
url = f"https://pypi.org/pypi/{project}/{version}/json"
|
|
508
|
+
remote = None
|
|
509
|
+
for attempt in range(12):
|
|
510
|
+
try:
|
|
511
|
+
with urlopen(url, timeout=30) as response:
|
|
512
|
+
payload = json.load(response)
|
|
513
|
+
remote = {
|
|
514
|
+
item["filename"]: item["digests"]["sha256"]
|
|
515
|
+
for item in payload["urls"]
|
|
516
|
+
}
|
|
517
|
+
except HTTPError as error:
|
|
518
|
+
if error.code != 404:
|
|
519
|
+
raise
|
|
520
|
+
if remote == expected:
|
|
521
|
+
break
|
|
522
|
+
if attempt < 11:
|
|
523
|
+
time.sleep(10)
|
|
524
|
+
if remote != expected:
|
|
525
|
+
raise SystemExit(
|
|
526
|
+
f"production PyPI files differ from manifest: expected={expected}, remote={remote}"
|
|
527
|
+
)
|
|
528
|
+
print("production PyPI filenames and SHA-256 values match the release manifest")
|
|
529
|
+
PY
|
|
530
|
+
|
|
531
|
+
smoke-test-pypi:
|
|
532
|
+
name: Install from production PyPI on ${{ matrix.platform.name }}
|
|
533
|
+
if: inputs.publish_pypi == true
|
|
534
|
+
needs: verify-pypi
|
|
535
|
+
strategy:
|
|
536
|
+
fail-fast: false
|
|
537
|
+
matrix:
|
|
538
|
+
platform:
|
|
539
|
+
- name: linux-x86_64
|
|
540
|
+
os: ubuntu-24.04
|
|
541
|
+
- name: linux-aarch64
|
|
542
|
+
os: ubuntu-24.04-arm
|
|
543
|
+
- name: macos-x86_64
|
|
544
|
+
os: macos-15-intel
|
|
545
|
+
- name: macos-arm64
|
|
546
|
+
os: macos-15
|
|
547
|
+
- name: windows-x86_64
|
|
548
|
+
os: windows-2025
|
|
549
|
+
runs-on: ${{ matrix.platform.os }}
|
|
550
|
+
steps:
|
|
551
|
+
- uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
|
|
552
|
+
with:
|
|
553
|
+
python-version: ${{ env.PYTHON_VERSION }}
|
|
554
|
+
- name: Install exact production version and smoke-test
|
|
555
|
+
shell: bash
|
|
556
|
+
run: |
|
|
557
|
+
python - <<'PY'
|
|
558
|
+
import os
|
|
559
|
+
from pathlib import Path
|
|
560
|
+
import subprocess
|
|
561
|
+
import tempfile
|
|
562
|
+
import venv
|
|
563
|
+
|
|
564
|
+
environment = Path(tempfile.mkdtemp(prefix="tommyslate-pypi-"))
|
|
565
|
+
venv.EnvBuilder(with_pip=True).create(environment)
|
|
566
|
+
python = environment / ("Scripts/python.exe" if os.name == "nt" else "bin/python")
|
|
567
|
+
subprocess.run(
|
|
568
|
+
[
|
|
569
|
+
python,
|
|
570
|
+
"-m",
|
|
571
|
+
"pip",
|
|
572
|
+
"install",
|
|
573
|
+
"--index-url",
|
|
574
|
+
"https://pypi.org/simple/",
|
|
575
|
+
"--retries",
|
|
576
|
+
"10",
|
|
577
|
+
"--no-deps",
|
|
578
|
+
f"{os.environ['PACKAGE_NAME']}=={os.environ['PACKAGE_VERSION']}",
|
|
579
|
+
],
|
|
580
|
+
check=True,
|
|
581
|
+
)
|
|
582
|
+
subprocess.run([python, "-m", "pip", "check"], check=True)
|
|
583
|
+
smoke = """
|
|
584
|
+
from safe_slate import optimize_slate
|
|
585
|
+
kwargs = dict(
|
|
586
|
+
rewards=[2.0, 1.0],
|
|
587
|
+
safety_means=[1.0, 1.0],
|
|
588
|
+
safety_variances=[0.0, 0.0],
|
|
589
|
+
max_items=1,
|
|
590
|
+
)
|
|
591
|
+
first = optimize_slate(**kwargs)
|
|
592
|
+
second = optimize_slate(**kwargs)
|
|
593
|
+
assert first.selected_indices == second.selected_indices == [0]
|
|
594
|
+
assert first.rechecked and second.rechecked
|
|
595
|
+
"""
|
|
596
|
+
subprocess.run([python, "-c", smoke], cwd=environment, check=True)
|
|
597
|
+
PY
|