Skip to content

Add the one-time PyPI name-reservation workflow#25

Draft
mmanciop wants to merge 6 commits into
mainfrom
feat/reserve-pypi-names
Draft

Add the one-time PyPI name-reservation workflow#25
mmanciop wants to merge 6 commits into
mainfrom
feat/reserve-pypi-names

Conversation

@mmanciop

@mmanciop mmanciop commented Jul 22, 2026

Copy link
Copy Markdown
Member

Stacked on #24. Adds the one-time Reserve PyPI names workflow that defensively registers the five dash0-opentelemetry-* distribution names on public PyPI.

Why

The real packages are served from the Dash0 package index (#24), and consumers install with --extra-index-url. pip resolves the highest version across all configured indexes, so unregistered names on public PyPI could be squatted with a higher version and win that resolution (dependency confusion). PyPI name ownership is exclusive from the first upload, so owning the names closes the hole for good.

What the workflow does

  • workflow_dispatch only; generates five stub projects inline (version 0.0.0.devN — below every real version, dev-only so default resolution never selects it, N incrementable because PyPI permanently forbids filename reuse) with a README pointing at the real index and repo, builds them with uv, and publishes via trusted publishing with skip-existing: true (partial-failure re-runs are safe).
  • Runs behind the protected pypi environment; permissions: {} at workflow level, id-token: write only on the publish job.

mmanciop added 6 commits July 22, 2026 14:12
Rename the four vendored packages at the distribution-name level only:
import paths and entry-point names stay upstream-shaped so that switching
to the official opentelemetry-* packages, once upstream publishes them,
is a dependency swap rather than a migration. The distro now pins the
renamed exporters exactly like every other dependency, and all versions
move to release form (1.44.0; the distro is 0.1.0) ahead of the first
publication on the Dash0 package index.
scripts/build_simple_index.py regenerates the static package index from
all published, non-draft GitHub releases: assets are downloaded and
re-hashed, verified against the committed manifest (the immutability
guard - a filename can never change bytes once indexed), restricted to
the five expected projects and this repository's release-asset origin,
and rendered as PEP 503/592 HTML with #sha256= fragments so consumers
can install with --require-hashes.

The HTML is emitted directly with the standard library: dumb-pypi, the
planned generator, joins one base URL with each filename and cannot
express per-file release-asset URLs, which embed the release tag.
Stdlib-only also means no third-party code runs in the job that
controls what consumers' lockfiles hash-pin.

Recovery levers, both git-audited: scripts/index-excluded.toml skips a
poisoned filename past the guard; scripts/index-yanked.toml marks a
version yanked without deleting anything.
Replace PyPI/TestPyPI publishing with the self-hosted channel. Pushing a
vX.Y.Z tag builds only artifacts whose filenames are not yet in the
committed manifest (rebuilds are not byte-stable across toolchain
upgrades), attests provenance, uploads them to a draft release, and
publishes it as the final step so immutable releases always lock a
complete asset set. The index job then regenerates and deploys the PEP
503 site, committing manifest additions to main with a dedicated deploy
key. workflow_dispatch offers rebuild-index, dry-run (index artifact,
no deploy, no manifest commit), and rebuild-assets recovery modes.

Hardening: per-job least-privilege permissions, env indirection for
event fields, SHA-pinned third-party actions, pinned uv and hatchling
(build constraints), no dependency cache, queued concurrency.
RELEASING.rst is rewritten around the new pipeline: the formerly open
publishing decisions recorded as resolved, the release checklist, the
recovery runbooks (dry run, index rebuild, dropped concurrency runs,
yank, immutability-guard trips), the one-time admin setup including the
mandatory trusted-publisher removal after name reservation, and the
consumer contract (hash-pinned installs, only-binary, Renovate
registryUrls, attestation verification). READMEs and CONTRIBUTING now
explain the dash0-opentelemetry-* publication names and index-based
installation.
Address the confirmed findings from the pre-ship code review:

- Read the manifest from origin/main in the build job, not from the tag
  checkout: a tag cut before the previous release's manifest commit
  landed would rebuild already-published filenames with different bytes
  and freeze index generation on the immutability guard.
- Drop the index-time rejection of stable-versioned assets on GitHub
  pre-releases: rehearsal rc releases legitimately carry the vendored
  packages' stable versions, and any post-publish index failure on
  immutable assets is a pipeline deadlock by construction. Pre-release
  semantics live in the PEP 440 version string.
- Sort releases oldest-first before deduplication: the GitHub API
  returns newest-first, so the 'original release URL wins' intent was
  inverted in production (and the test encoded the inversion).
- Fail index generation when a yank entry matches no indexed file
  instead of silently serving the version the maintainer believes was
  withdrawn.
- Add scripts/check_version_bumped.py to CI: a package change merged
  without a version bump would be silently skipped at release time
  (its filename is already in the manifest) while the run goes green.
- Fail with explicit guidance when a release was published outside the
  pipeline (immutable and asset-less; the version must be burned), and
  document that releases are never created from the GitHub UI.
- Validate wheel/sdist metadata with twine before anything becomes
  immutable, restoring the check the PyPI upload used to provide.
- Fail fast on a missing release-tag input for mode=rebuild-assets.
- Derive the package set from the workspace (root uv.sources in the
  index generator, packages/*/pyproject.toml in the release workflow,
  --all-packages in CI) so a sixth package cannot be silently dropped
  by a stale hardcoded list; match asset names literally (grep -F).
Defensively registers the five dash0-opentelemetry-* names on public
PyPI with honest 0.0.0.devN stubs, closing the dependency-confusion hole
that --extra-index-url consumption would otherwise leave open. The
trusted publishers must be bound to this workflow and the protected
pypi environment only, and removed after a successful run.
@mmanciop

Copy link
Copy Markdown
Member Author

Merging this needs to wait for the PyPI org being approved

Base automatically changed from feat/self-hosted-package-index to main July 22, 2026 15:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant