MEP-71 Phase 11. Trusted publish to PyPI
Status: LANDED (pending merge) as of 2026-05-30 00:36 (GMT+7). Implements the orchestration layer of mochi pkg publish --to=pypi: OIDC token minting, PEP 740 in-toto SLSA Provenance v1 attestation construction, and the per-target uploader driver. The actual Sigstore signing + live PyPI / TestPyPI HTTP calls are sub-phases 11.1 and 11.2.
Gate
The umbrella sentinel TestPhase11TrustedPublish in package3/python/publish/phase11_test.go is green. The sentinel:
- Builds a
PublishRequestwith two artifact pairs (sdist + wheel) targeting TestPyPI. - Runs the orchestrator in
DryRun=truemode so the OIDC -> mint -> upload chain executes without touching the network (default uploader isRecordingUploader; the test also injects afakeUploaderthat returns syntheticPublishedArtifactURLs). - Asserts the orchestrator emits a PEP 740 in-toto v1 statement with
_type = "https://in-toto.io/Statement/v1",predicateType = "https://slsa.dev/provenance/v1", sorted subjects covering every (sdist, wheel) pair with sha256 digests, and arunDetails.builder.idcarrying the configured builder identity. - Asserts every artifact in the result lands with the registry-shaped URL (
https://test.pypi.org/project/<name>/<ver>/) and the AttestationURL field round-trips through the uploader. - Asserts dry-run propagates an empty API token to the uploader (no minted credential leaves the orchestrator without an explicit non-dry-run pass).
Plus 38 unit tests (go test ./package3/python/publish/... -count=1) covering:
RegistryKindString / URL / OIDCAudience for PyPI + TestPyPI + unknown fallback.PublishRequest.Validaterejecting unknown registry, empty targets, empty Distribution / Version / artifact path / sha256 / size, non-.tar.gzsdist, non-.whlwheel, and missing OIDCProvider when DryRun is false. Dry-run validates without an OIDC provider.GitHubActionsProvider.Token: missing env vars, happy-path token fetch from a mock httptest server, HTTP 403 propagation, and empty-token refusal.StaticProvider: returns the stored token; refuses empty TokenValue.MintAPIToken: rejects empty OIDC token, happy path against a mock httptest server (verifies the POST body carries{"token": <oidc>}and the response's expires field is RFC 3339 parsed), 401 propagation, and refusal when the response'ssuccessflag is false.BuildStatement: subjects are sorted by name; every subject carries a sha256 digest; subject filenames follow the PEP 491 / PEP 625 shape (<dist>-<ver>.tar.gzfor sdists,<dist>-<ver>-py3-none-any.whlfor wheels); builder id falls back toAttestationBuilderIDwhen unset and is overridable; invocation id defaults to"auto";finishedOnis in a recent UTC window whenBuildTimeis zero.EncodeStatementround-trips throughjson.Marshaldeterministically (same input -> same bytes).RecordingUploader.Uploadcaptures the call and returns the registry-shaped URL withSkipped=true.UVUploader.Upload: invokesuvwithpublish --trusted-publishing always --publish-url <registry>/legacy/ <sdist> <wheel>andUV_PUBLISH_TOKEN=<minted>in env; surfaces subprocess errors with the captured stderr; honoursBinaryoverride.Orchestrator.Publish: rejects invalid requests; dry-run skips OIDC and usesRecordingUploaderby default; propagates OIDC errors; embeds every target in the attestation; surfaces uploader errors; propagates the configured Builder id into the attestation predicate.
Files
package3/python/publish/doc.go— package overview.package3/python/publish/request.go—PublishRequest,PublishTarget,RegistryKind,Validate.package3/python/publish/oidc.go—OIDCProviderinterface,GitHubActionsProvider,StaticProvider,MintAPIToken,MintedToken.package3/python/publish/attestation.go—Statement,Subject,BuildStatement,EncodeStatement+ constants for SLSA Provenance v1 predicate type, in-toto Statement v1 type uri, and the Mochi builder identity.package3/python/publish/uploader.go—Uploaderinterface,UploadCall,UploadResult,UVUploader(default),RecordingUploader(dry-run + tests).package3/python/publish/orchestrator.go—Orchestrator.Publishglue (validate -> attest -> mint -> upload -> result aggregation).package3/python/publish/request_test.go— request + registry validation (8 cases).package3/python/publish/oidc_test.go— OIDC provider + mint endpoint (10 cases).package3/python/publish/attestation_test.go— attestation statement shape + encoding determinism (9 cases).package3/python/publish/uploader_test.go— UVUploader + RecordingUploader (4 cases).package3/python/publish/orchestrator_test.go— orchestrator chain (7 cases).package3/python/publish/phase11_test.go— Phase 11 umbrella sentinel.
Sub-phase decomposition
Phase 11 ships the orchestration layer with mockable subprocess + HTTP boundaries. The live integrations stay out of the umbrella gate so the test suite remains offline and deterministic:
| Sub-phase | Title | Status | Notes |
|---|---|---|---|
| 11 | Publish orchestrator (OIDC + attestation + uploader + dry-run) | LANDED (pending merge) | This PR. |
| 11.1 | Sigstore live signing harness (sigstore-python shell-out + Rekor log fetch) | NOT STARTED | Wraps the in-toto statement in a Sigstore bundle. |
| 11.2 | Live PyPI / TestPyPI HTTP (mint endpoint contract test against Warehouse mock) | NOT STARTED | Replaces the test-only redirect transport with real pypi.org calls. |
| 11.3 | mochi pkg publish --to=pypi [--testpypi] [--dry-run] CLI verb | NOT STARTED | Wires the orchestrator behind the unified mochi pkg CLI. |
Fixtures
Phase 11 is offline; the fixture corpus is not exercised. Sub-phase 11.2 will gate the live mint endpoint against a frozen _/oidc/mint-token/ contract response taken from PyPI's Warehouse repo.
Skip count
N/A. Phase 11 has no SkipReport surface; non-publishable requests are rejected by PublishRequest.Validate() before any external call.
Cross-references
- MEP-71 spec §6 "Sigstore-keyless OIDC trusted publishing" for the normative flow.
- Phase 10 for the sdist + wheel emit Phase 11 consumes.
- Phase 15 for the install-time PEP 740 attestation verifier that mirrors the publish-time signer.
- Phase 18 for the abi2026 transition that controls which wheel ABI tag Phase 11 publishes.
- Trusted Publishing background (PyPI Warehouse) and PEP 740 for the attestation envelope spec.