Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
9384d6a
feat(coverage): add bounded PyO3 peer-evidence gate
seonghobae Aug 5, 2026
27affda
test(coverage): prove PyO3 peer gate fail-closed
seonghobae Aug 5, 2026
da6c909
docs(coverage): record PyO3 peer-evidence boundary
seonghobae Aug 5, 2026
68e8b16
docs(changelog): record PyO3 peer-evidence gate
seonghobae Aug 5, 2026
6c4be5d
test(coverage): require repo-root-aware nested PyO3 trust boundaries
seonghobae Aug 6, 2026
8a1570e
fix(coverage): bind nested PyO3 trust paths to repository root
seonghobae Aug 6, 2026
55faf74
test(coverage): require PyO3 deferral integration and exact-head peer…
seonghobae Aug 6, 2026
e389628
ci(coverage): add permanent PyO3 peer-gate quality matrix
seonghobae Aug 6, 2026
5bc9ba4
test(coverage): cover repo-root and default-source fail-closed branches
seonghobae Aug 6, 2026
870174b
ci(coverage): verify exact-head PyO3 peer gate
seonghobae Aug 7, 2026
195c0db
ci: add temporary read-only source snapshot
seonghobae Aug 7, 2026
39e644d
test(coverage): expose peer-gate file race boundaries
seonghobae Aug 7, 2026
d321ae4
docs(doctoring): record descriptor-safe peer evidence reads
seonghobae Aug 7, 2026
9878fe3
test(coverage): block requirements-directory peer deferral
seonghobae Aug 7, 2026
e92ed9f
chore: add one-shot PyO3 workflow integration patcher
seonghobae Aug 7, 2026
6dc45b3
chore(coverage): remove unnecessary pull-request source snapshot
seonghobae Aug 7, 2026
e9640e3
chore(coverage): remove PR-controlled PyO3 integration patcher
seonghobae Aug 7, 2026
0948976
chore(coverage): remove duplicate PyO3 peer-gate quality workflow
seonghobae Aug 7, 2026
6e723a2
chore(coverage): stage PyO3 peer workflow integration
seonghobae Aug 7, 2026
247ebc6
chore(coverage): remove staged PyO3 integration patch artifact
seonghobae Aug 7, 2026
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
180 changes: 180 additions & 0 deletions .github/workflows/python-native-extension-peer-gate-quality-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
name: Python Native Extension Peer Gate Quality CI

on:
pull_request:
branches: [main]
paths:
- ".github/workflows/opencode-review-dispatch.yml"
- ".github/workflows/python-native-extension-peer-gate-quality-ci.yml"
- "scripts/ci/python_native_extension_peer_gate.py"
- "tests/test_python_native_extension_peer_gate.py"
- "tests/test_python_native_extension_peer_gate_nested_project.py"
- "tests/test_python_native_extension_peer_gate_workflow_contract.py"
- "docs/doctoring/python-native-extension-peer-evidence.md"
- "requirements-opencode-review-ci-hashes.txt"
- "CHANGELOG.md"
push:
branches: [main]
paths:
- ".github/workflows/opencode-review-dispatch.yml"
- ".github/workflows/python-native-extension-peer-gate-quality-ci.yml"
- "scripts/ci/python_native_extension_peer_gate.py"
- "tests/test_python_native_extension_peer_gate.py"
- "tests/test_python_native_extension_peer_gate_nested_project.py"
- "tests/test_python_native_extension_peer_gate_workflow_contract.py"
- "docs/doctoring/python-native-extension-peer-evidence.md"
- "requirements-opencode-review-ci-hashes.txt"
- "CHANGELOG.md"

concurrency:
group: python-native-extension-peer-gate-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

permissions:
contents: read

env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true

jobs:
minimum-python-contract:
name: Python 3.10 compatibility contract
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- name: Harden runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
with:
egress-policy: audit

- name: Checkout exact source revision
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
ref: ${{ github.event.pull_request.head.sha || github.sha }}

- name: Set up minimum supported Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.10"

- name: Compile production and tests on Python 3.10
run: |
python -m compileall -q \
scripts/ci/python_native_extension_peer_gate.py \
tests/test_python_native_extension_peer_gate.py \
tests/test_python_native_extension_peer_gate_nested_project.py \
tests/test_python_native_extension_peer_gate_workflow_contract.py

- name: Exercise the conditional tomli import
run: |
python - <<'PY'
import sys
import tempfile
from pathlib import Path

stub_root = Path(tempfile.mkdtemp(prefix="pyo3-peer-gate-tomli-stub-"))
(stub_root / "tomli.py").write_text(
"class TOMLDecodeError(ValueError):\n"
" pass\n"
"def loads(_value):\n"
" return {}\n",
encoding="utf-8",
)
sys.path.insert(0, str(stub_root))
from scripts.ci import python_native_extension_peer_gate as gate

assert gate.tomllib.__name__ == "tomli"
PY

full-quality-gate:
name: Python 3.14 full quality gate
runs-on: ubuntu-24.04
timeout-minutes: 25
steps:
- name: Harden runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
with:
egress-policy: audit

- name: Checkout exact source revision
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
ref: ${{ github.event.pull_request.head.sha || github.sha }}

- name: Set up current stable Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.14"
cache: pip
cache-dependency-path: requirements-opencode-review-ci-hashes.txt

- name: Install hash-locked quality tooling
run: >-
python -m pip install --disable-pip-version-check --require-hashes
-r requirements-opencode-review-ci-hashes.txt

- name: Run focused peer-gate tests with complete branch coverage
run: |
cat >"${RUNNER_TEMP}/python-native-peer-coveragerc" <<'EOF'
[run]
branch = True
include =
scripts/ci/python_native_extension_peer_gate.py

[report]
fail_under = 100
show_missing = True
EOF
export COVERAGE_RCFILE="${RUNNER_TEMP}/python-native-peer-coveragerc"
python -m coverage erase
python -m coverage run -m pytest \
tests/test_python_native_extension_peer_gate.py \
tests/test_python_native_extension_peer_gate_nested_project.py \
tests/test_python_native_extension_peer_gate_workflow_contract.py \
-q
python -m coverage report

- name: Run complete central test and branch coverage gate
run: |
unset COVERAGE_RCFILE
python -m coverage erase
python -m coverage run -m pytest tests -q
python -m coverage report

- name: Enforce complete production docstrings
run: >-
python -m interrogate --fail-under 100
scripts/ci/python_native_extension_peer_gate.py

- name: Compile production and quality contracts
run: |
python -m compileall -q \
scripts/ci/python_native_extension_peer_gate.py \
tests/test_python_native_extension_peer_gate.py \
tests/test_python_native_extension_peer_gate_nested_project.py \
tests/test_python_native_extension_peer_gate_workflow_contract.py

- name: Install checksum-pinned actionlint
env:
ACTIONLINT_VERSION: "1.7.12"
ACTIONLINT_SHA256: "8aca8db96f1b94770f1b0d72b6dddcb1ebb8123cb3712530b08cc387b349a3d8"
run: |
set -euo pipefail
archive="${RUNNER_TEMP}/actionlint.tar.gz"
curl --fail --location --proto '=https' --tlsv1.2 \
--output "$archive" \
"https://github.com/rhysd/actionlint/releases/download/v${ACTIONLINT_VERSION}/actionlint_${ACTIONLINT_VERSION}_linux_amd64.tar.gz"
printf '%s %s\n' "$ACTIONLINT_SHA256" "$archive" | sha256sum --check --strict
tar --extract --gzip --file "$archive" --directory "$RUNNER_TEMP" actionlint
test -x "${RUNNER_TEMP}/actionlint"

- name: Validate protected workflow syntax with actionlint
run: |
"${RUNNER_TEMP}/actionlint" \
.github/workflows/opencode-review-dispatch.yml \
.github/workflows/python-native-extension-peer-gate-quality-ci.yml

- name: Verify clean patches
run: git diff --check
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Semantic Versioning where the repository publishes a release.
### Added

- Added exact-base `uv.lock` materialization that reconstructs standalone nested projects with a checksum-pinned official `uv` exporter, isolated frozen/offline execution, strict exact-pin and SHA-256 output validation, and complete Python 3.10/3.14 quality evidence.
- Added a bounded PyO3/maturin pytest-failure classifier and exact-head native peer-check verifier so source-only OpenCode sandboxes can distinguish one unchanged-extension collection limitation from product failures without skipping tests, executing pull-request build hooks, or weakening Rust ownership.

### Fixed

Expand Down
175 changes: 175 additions & 0 deletions docs/doctoring/python-native-extension-peer-evidence.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
# Doctoring record: Python native-extension peer evidence

## Purpose

The central OpenCode coverage sandbox executes pull-request tests without a
repository credential, package-index access, or permission to run
pull-request-selected build/install hooks. That isolation is intentional, but a
mixed Rust/Python project can require a compiled PyO3 extension during pytest
collection. A plain source checkout then raises `ModuleNotFoundError` before any
Python test is collected even when the exact pull-request head has already built,
installed, and tested the extension in trusted repository jobs.

This record defines a bounded classifier and exact-head peer-evidence gate. The
classifier does **not** convert a missing extension into passing test evidence.
It can only identify one narrow execution-environment limitation and defer the
final decision to separately successful native build and test checks on the same
commit.

## Observed failure

`ContextualWisdomLab/fast-mlsirm#546` uses the maturin mixed-project layout:

```toml
[build-system]
build-backend = "maturin"

[tool.maturin]
bindings = "pyo3"
manifest-path = "crates/fast-mlsirm-py/Cargo.toml"
module-name = "fast_mlsirm._core"
python-source = "python"
```

The repository CI first builds and installs the native module and then runs
pytest. The isolated central source sandbox deliberately does not perform that
build, so collection stops at:

```text
ModuleNotFoundError: No module named 'fast_mlsirm._core'
```

Maturin documents that `module-name` places the compiled extension inside the
configured Python source tree and that `maturin develop` or an installation step
materializes the shared library. PyO3 likewise documents that a native module
must be compiled and exposed with the matching module name before Python can
import it. The source checkout alone is therefore not equivalent to the
installed package.

## Classification contract

`scripts/ci/python_native_extension_peer_gate.py classify-pytest` accepts a
failure only when every condition below is true:

1. `pyproject.toml` is a bounded, regular, non-symlink UTF-8 file.
2. The build backend is exactly `maturin` and bindings are exactly `pyo3`.
3. `module-name`, `manifest-path`, and `python-source` are safe relative values.
4. The pytest log is bounded, complete, and contains only collection errors.
5. Every terminal exception is `ModuleNotFoundError` for the declared module.
6. Every collection-error block contains a direct import of that module.
7. The interruption count, collection-block count, and missing-module count
agree exactly.
8. There is no failure, setup/teardown error, internal pytest error, crash,
segmentation fault, or truncation marker.
9. The changed-file list is bounded, unique, and traversal-free.
10. The pull request does not change Rust source, Cargo metadata, native stubs,
maturin metadata, dependency locks, requirements, packaging files, GitHub
workflows/actions, or any file under the native crate directory.

A rejected classification remains an ordinary blocking test failure.

## Exact-head peer evidence

A successful classification is not approval. Before the central workflow may
accept it, `require-checks` must receive normalized `CheckRun` records for the
exact 40-character pull-request head and prove all trusted requirements. The
initial `fast-mlsirm` contract requires:

```text
CI::python
CI::rust
CI::package
```

Every matching check must be a GitHub `CheckRun`, belong to the trusted workflow,
carry the exact head SHA, have status `COMPLETED`, and conclusion `SUCCESS`.
Missing, pending, failed, cancelled, neutral, skipped-required, stale-head,
status-only, or lookalike check records fail closed. The workflow and check names
must be supplied by trusted central or protected-base configuration, not by pull
request prose.

GPU and fuzz evidence remain independent repository gates. The peer gate neither
removes nor reinterprets them.

## Change-sensitive boundary

The deferral exists only for an unchanged native/package trust boundary. Any
change to the extension implementation, Cargo manifests or lock, maturin
configuration, native stub, packaging metadata, dependency locks, or CI workflow
requires a direct trusted native build path. This prevents a pull request from
changing the thing being imported while asking the central sandbox to trust an
older binary or a weakly named passing check.

Python business or reporting code and its tests may use the deferral when the
native boundary is unchanged, but the current-head repository Python job must
still execute the complete suite against the built extension.

## Security and privacy boundary

The helper reads only bounded regular files and performs no network access,
subprocess execution, package installation, token access, or mutation. It does
not load the target project as Python code. TOML and JSON are parsed as data.
Repository paths reject absolute paths, parent traversal, current-directory
aliases, Windows separators, NUL, and duplicates.

The classifier does not make arbitrary `ModuleNotFoundError` safe. Missing
third-party dependencies, syntax/import defects in Python modules, mixed
exceptions, runtime crashes, and ordinary test failures remain blocking.

## Testing evidence

The focused suite includes the exact `fast_mlsirm._core` collection shape plus
adversarial cases for:

- wrong and mixed missing modules;
- inconsistent collection counts;
- failed tests and setup/teardown errors;
- internal pytest errors, crashes, and truncated output;
- malformed TOML and unsafe paths;
- changed Rust, Cargo, packaging, dependency, workflow, and native-stub inputs;
- stale, pending, failed, status-only, wrong-workflow, and misleading checks;
- malformed SHAs, duplicate requirements, unsafe JSON, and missing files;
- flat and GraphQL-shaped workflow metadata;
- both CLI success and fail-closed paths.

Local verification before publication reported 81 tests passing with 220/220
production statements and 98/98 production branches covered. Permanent central
quality and security workflows remain authoritative after the branch is pushed.

## Interpretation limits

This gate establishes neither product correctness nor scientific validity. It
only prevents a known source-only sandbox limitation from being confused with a
Python defect while preserving exact-head native evidence. Parameter recovery,
CPU/GPU parity, psychometric validity, fairness, and release readiness remain
separate product obligations.

## Rollback

Rollback removes the helper, tests, and workflow integration. The prior behavior
is fail-closed: any missing native module causes central coverage failure. No
rollback requires weakening branch protection, deleting repository tests, or
introducing a Python substitute for Rust arithmetic.

## References

GitHub. (2026). *REST API endpoints for workflow runs*. GitHub Docs.
https://docs.github.com/en/rest/actions/workflow-runs

Maturin contributors. (2026). *Bindings*. Maturin user guide.
https://www.maturin.rs/bindings

Maturin contributors. (2026). *Configuration*. Maturin user guide.
https://www.maturin.rs/config

Maturin contributors. (2026). *Introduction: Mixed Rust/Python projects*.
Maturin user guide. https://www.maturin.rs/

Python Software Foundation. (2026). *The import system*. Python documentation.
https://docs.python.org/3/reference/import.html

PyO3 Project and Contributors. (2026). *Building and distribution*. PyO3 user
guide. https://pyo3.rs/main/building-and-distribution

PyO3 Project and Contributors. (2026). *Python modules*. PyO3 user guide.
https://pyo3.rs/main/module
Loading
Loading