Publishing rehearsal: rename to agentguard, TestPyPI dry run, out-of-band settings checklist - #20
Conversation
Both names are unclaimed on PyPI, and renaming is free exactly once. After 0.2.0 ships it costs a shim package, a deprecation notice, split download statistics, and two names in search results permanently. Nine sites, not the six a textual reading finds. Three of them arrived with #19 and are the ones that would have failed loudest: the importlib.metadata lookup in __init__.py, its assertion in tests/test_cli.py, and the tag check inside release.yml's verify job. Renaming without those would leave __version__ falling back to 0.0.0+unknown - so --version, the JSON tool.version and the SARIF driver version would all report a version that does not exist - and the release would fail its own tag check after the tag was pushed. The failure #19 exists to prevent, reintroduced by a rename that looked like string replacement. Verified after uninstalling the old distribution first, since a stale editable install would have hidden a broken lookup: metadata resolves to agentguard, the old name raises PackageNotFoundError, both artifacts build and pass twine check, and a clean venv reports AgentGuard 0.2.0. WORKLOG.md keeps its references on purpose. It records what was true at the time. CHANGELOG had two Changed and two Fixed sections under [Unreleased], accumulated across earlier commits; adding the rename note made it three, which is how it surfaced. Consolidated, with every relocated bullet checked to still be there. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q5nXA9BJKdmye9a9FaHdc6
workflow_dispatch now publishes to TestPyPI through the same trusted-publishing shape as production - same verify -> build gate, same OIDC permissions, differing only in repository-url and environment. It exercises what a local venv install cannot: metadata validation on receipt, the token exchange, and page rendering. Writing it exposed a hole in the existing gating. workflow_dispatch can be run against a tag ref, so a production job gated only on startsWith(github.ref, 'refs/tags/') would have been satisfied by a manual dispatch on a tag - putting the one irreversible action in this pipeline behind a dropdown. Production now also requires the push event, checked across every event and ref combination. The dispatch takes no input selecting a target. A dry run that can be aimed at production by picking the wrong entry is a worse hazard than the one it exists to remove. Two things the rehearsal forced. The tag check is split: declared-versus- installed runs on both triggers, and tag-versus-declared is tag-only, because GITHUB_REF_NAME is a branch name on a dispatch and would fail every dry run for the wrong reason. And skip-existing is set on TestPyPI only, since it accepts a version once - on production a version conflict is a real signal and must not be swallowed. The run itself is not done: it needs a TestPyPI trusted publisher and a testpypi environment. Recorded, with the residual gap stated - a green dry run validates the mechanism, not the production publisher, which has its own separate record and is verifiable only by inspection. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q5nXA9BJKdmye9a9FaHdc6
…sumer Checking each setting rather than describing it found three things wrong now. No GitHub environments exist, though release.yml names two - a publish would still work, implicitly and with no protection rules, so anyone able to push a tag can publish. The About field still advertises "vulnerable dependencies" although AG009 was deleted in #17, which is the clearest demonstration of why this checklist exists: nothing in CI renders that string, so nothing could fail. And GITHUB_SETUP.md line 6 was telling maintainers to require the `test` check - one of the three phantom contexts that blocked every merge for two sessions. The onboarding document was teaching the incident. The checklist covers six surfaces with a verify command and a recoverability verdict each. The PyPI trusted publisher is marked unrecoverable, because it fails the OIDC exchange after the tag is pushed and a spent version cannot be reused. Two DECISIONS entries. The first states plainly that an inspection checklist is a weak control - manual, staleable, run only by someone who remembers - and that it exists because the alternative is nothing; four incidents share one shape, the repository green and the fault outside it. The second records that corpus containment cannot be solved once: four readers so far, no two sharing a mechanism, and the directory irrelevant to all of them. The About field is not changed here. It is a setting, and settings are yours; the corrected wording is in GITHUB_SETUP.md to be pasted. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q5nXA9BJKdmye9a9FaHdc6
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6f1f914aed
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| There is no API to read this back. Verify by eye at | ||
| `https://pypi.org/manage/project/agentguard/settings/publishing/`, and rehearse the | ||
| mechanism first with the TestPyPI dry run (Actions → Release → Run workflow), which has |
There was a problem hiding this comment.
Use the pending-publisher form for the first release
Because this commit states that agentguard has never been published, the project-specific settings URL does not exist yet: PyPI does not create an empty project before its first upload. The initial OIDC identity must instead be registered as a pending publisher under /manage/account/publishing/; otherwise a maintainer following this pre-tag checklist cannot configure or verify the publisher, and the first release will fail authentication.
Useful? React with 👍 / 👎.
| `pypi`. A mismatch fails the OIDC exchange — and it fails *after* the tag has been pushed, | ||
| which means the version is spent. PyPI versions can be yanked but never replaced, so the | ||
| recovery is a new version number and a permanent gap in the history. |
There was a problem hiding this comment.
Permit rerunning an upload after fixing OIDC settings
When a publisher mismatch fails the OIDC exchange, no distribution has reached PyPI, so the version has not been claimed; the publisher can be corrected and the workflow rerun against the same tag and version. The prohibition on replacing files applies only after an upload succeeds, so this instruction would unnecessarily force a version bump and create the permanent history gap it warns about.
Useful? React with 👍 / 👎.
| 4. Enable Discussions categories: Announcements, Q&A, Ideas, Show and tell, and Rule proposals. | ||
| 5. Pin an introductory Discussion using `.github/DISCUSSION_TEMPLATE/welcome.yml` as the guide. | ||
| 6. Protect `main`: require pull requests, one approval, conversation resolution, and the `test`, `package`, and `CodeQL` checks; disallow force pushes and deletions. | ||
| 6. Protect `main`: require pull requests, conversation resolution, and the `ci-ok`, `build`, and `CodeQL` checks; disallow force pushes and deletions. **Pick every required context from GitHub's suggestion list, never by typing it** — `test` and `package` are not check-run names and can never report. See below. |
There was a problem hiding this comment.
Restore the required approving review
For a repository configured from these instructions, requiring pull requests without requiring any approving reviews allows a collaborator with merge access to merge their own PR as soon as the checks pass. The previous version explicitly required one approval, and this context-name correction silently removes that protection; retain the one-approval requirement while updating the required checks.
Useful? React with 👍 / 👎.
Three units. Nothing tagged, nothing published. Don't merge this yourself.
1. Distribution renamed to
agentguardNine sites, not the six a textual reading finds. Three arrived with #19 and are the ones that would have failed loudest:
Renaming without those leaves
__version__falling back to0.0.0+unknown— so--version, the JSONtool.version, and the SARIF driver version all report a version that doesn't exist — and the release fails its own tag check after the tag is pushed. Exactly the failure #19 exists to prevent, reintroduced by a rename that looks like string replacement.Verified after uninstalling the old distribution first, since a stale editable install would hide a broken lookup: metadata resolves to
agentguard, the old name raisesPackageNotFoundError, both artifacts build and passtwine check, a clean venv reportsAgentGuard 0.2.0.WORKLOG.mdkeeps its seven references deliberately — it records what was true at the time.Also found and fixed:
[Unreleased]had two### Changedand two### Fixedsections, accumulated across earlier commits. Consolidated; every relocated bullet verified still present.2. TestPyPI dry run
workflow_dispatchpublishes to TestPyPI through the same trusted-publishing shape, sameverify → buildgate, differing only inrepository-urland environment.Writing it exposed a hole in the existing gating.
workflow_dispatchcan run against a tag ref, so production gated only onstartsWith(github.ref, 'refs/tags/')would have been satisfied by a manual dispatch on a tag — the one irreversible action in this pipeline, reachable from a dropdown. Production now also requiresgithub.event_name == 'push', checked across every event/ref combination.The tag check had to split:
declared vs installedruns on both triggers,tag vs declaredis tag-only, becauseGITHUB_REF_NAMEis a branch name on a dispatch.skip-existing: trueon TestPyPI only — a version conflict there is noise, on production it's a real signal.I have not run it. It needs a TestPyPI trusted publisher and a
testpypienvironment, both yours. Residual gap stated inWORKLOG.md: a green dry run validates the mechanism, not the production publisher, which has its own separate record.3. Settings outside version control
Checking each setting rather than describing it found three things wrong right now:
[]), thoughrelease.ymlnames two. A publish would still work — implicitly, with no protection rules, so anyone able to push a tag can publish.AG009having been deleted in Make the scanner safe to run on untrusted code, and its findings measurable #17. Not fixed here — it's a setting — but corrected wording is in the doc to paste.GITHUB_SETUP.md:6was recommending thetestcheck, one of the three phantom contexts that blocked every merge for two sessions. The onboarding document was teaching the incident.Six surfaces documented with a verify command and a recoverability verdict each; the PyPI trusted publisher is marked unrecoverable. Plus a pre-tag sequence ordered so the unrecoverable item is checked last and twice.
Two
DECISIONS.mdentries: one on the settings surface — stating plainly that an inspection checklist is a weak control and exists because the alternative is nothing — and one on corpus containment being per-consumer, with four readers and no two sharing a mechanism.🤖 Generated with Claude Code
https://claude.ai/code/session_01Q5nXA9BJKdmye9a9FaHdc6