feat: publish to PyPI and hand downstream bumps to Dependabot - #25
Merged
Merged
Conversation
Going open source removed the premise the previous release design was built on. Every consumer needed a credential just to resolve a private Git dependency, and opening a bump pull request across repositories needed a second one — an organization-level GitHub App or a cross-repository PAT, neither of which is available without org administration. A public PyPI package needs none of it: - Releases publish to PyPI from the tagged tree using Trusted Publishing (OIDC). No API token is stored anywhere; `id-token: write` is scoped to a separate publishing job so no build-time action can request that identity. - Consumers depend on `apodex-agent-core==0.3.0` instead of a git+ssh revision, and Dependabot opens the bump pull request. Its `uv` ecosystem updates both pyproject.toml and uv.lock, and the pull request runs the product's own CI. - The repository_dispatch + repin-script mechanism is deleted with .github/downstream/, along with every credential it needed. Because a PyPI version number can never be reclaimed — not by deleting the release, not by yanking — the release now runs `twine check` and installs the built wheel into a clean environment to import it, before publishing. A broken artifact must fail the release rather than burn the number. Version 0.3.0, not 0.2.0: 0.2.0 was never tagged or distributed, so consumers move from Git revisions straight to the first published release. Also fixes two defects in the version gate that shipped in #23: - it compared versions for inequality rather than an increase, so a pull request could move 0.2.0 back to 0.1.9 and pass; - the skip-version-bump label was inert, because the default pull_request event types exclude `labeled`, leaving a labelled pull request red forever. Packaging metadata now uses PEP 639 SPDX (`license = "Apache-2.0"` with license-files) rather than the legacy table form, which had embedded the entire Apache text into the distribution's License field. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Going open source removed the premise the previous release design rested on. Closes the need for #24 — see below.
Why this replaces the mechanism from #23
Every consumer needed a credential just to resolve a private Git dependency, and opening a bump PR across repositories needed a second one — an org-level GitHub App or a cross-repo PAT, neither available without organization administration.
A public PyPI package needs none of it:
apodex-agent-core @ git+ssh://…@<sha>apodex-agent-core==0.3.0AGENT_CORE_REPO_TOKEN/insteadOfrewritingrepository_dispatch→ repin script → PR.github/downstream/is deleted entirely.What publishing looks like
releasejob (unchanged gates, plus two new ones) →publish-pypijob withid-token: writescoped to that job alone, so no build-time action can request the identity PyPI trusts. No API token exists anywhere in the repo.Two new gates, both because a PyPI version number can never be reclaimed — not by deleting the release, not by yanking:
twine checkon both artifacts;import agent_core(the release contract in the README required this; it was not implemented).Version 0.3.0, not 0.2.0
0.2.0was never tagged or distributed — it exists only inmain's history. Consumers go from Git revisions straight to the first published release. CHANGELOG marks 0.2.0 Never published rather than pretending it was a release.Also fixes two defects that shipped in #23
0.2.0back to0.1.9and pass.skip-version-bumpwas inert. Defaultpull_requestevent types excludelabeled, so applying the label did not re-run the check — the PR stayed red forever.The one real limitation, documented
A Dependabot PR runs CI as if it came from a fork: read-only
GITHUB_TOKEN, andsecrets.*resolves against Dependabot secrets, not Actions secrets. Plain test CI is unaffected; any step consuming a secret needs that secret duplicated under Settings → Secrets → Dependabot, or a guard.docs/downstream-bump.mdspells this out. Worth the trade against a long-lived cross-repo credential in both products.Packaging metadata
PEP 639 SPDX (
license = "Apache-2.0"+license-files) instead of the legacy table form, which had embedded the entire Apache text into the distribution'sLicensefield. Plus classifiers and project URLs.Metadata-Version: 2.5,twine checkPASSED.Verification
ruffclean,pyright0 errors, 1138 passed,uv sync --frozenconsistent.twine checkPASSED on wheel and sdist; wheel confirmed to carryagent_core/py.typedand the license.user_msg("hi").role, butMessageis aTypedDict, so constructors return plain dicts.run:script passesbash -n.id-tokenmust stay scoped to the publish job,twine checkmust run,.github/downstream/must not return, the SPDX license must not gain a redundantLicense ::classifier (PyPI rejects both — after the tag exists).Not done here
Downstream PRs ApodexHarness#524 and FrontierAgentInternal#107 install the now-deleted workflow and should be closed; they need replacing with a
.github/dependabot.ymlplus the switch toapodex-agent-core==0.3.0. Neither product declares AgentCore onmainyet, so that belongs with whichever migration branch merges first.Before tagging
v0.3.0: register the PyPI pending publisher and create thepypienvironment (docs/downstream-bump.md).