v1.0.0 Publish Execution Plan — 2026-09-05¶
Index · Publish Readiness Assessment - 2026-07-24 · V1 Release Roadmap · Publishing to PyPI · Publishing Docker Images
Purpose: The single working checklist that takes
testo-corefrom unreleased0.1.0to a published, installable v1.0.0 on PyPI, GHCR, and JFrog Artifactory. Supersedes as the action list: Publish Readiness Assessment - 2026-07-24 (that note stays as the point-in-time analysis; this note is what we execute). Method: One step at a time, top to bottom. A step is not done until its Done when line is true. Check boxes as we go and commit the doc with the change it describes.
Owner legend¶
- AI — Claude does it in-session.
- YOU — needs your credentials, 2FA, or org admin. Cannot be delegated.
- BOTH — AI drives, you approve or watch.
Step map¶
| # | Step | Owner | Est. | Status |
|---|---|---|---|---|
| 0 | Green baseline | AI | ~~0.5–2h~~ 0.3h actual | ✅ done 2026-09-05 |
| 1 | Streamlit decision | AI | 0.5h | ✅ done — option A |
| 2 | Version + CHANGELOG roll | AI | 0.25h | ✅ done |
| 3 | PyPI publish auth | YOU | 0.75h | ⬜ |
| 4 | GHCR permissions | YOU | 0.25h | ⬜ |
| 5 | Tag, release, firefight | BOTH | 1h | ⬜ |
| 6 | Verify public install | AI | 0.5h | ⬜ |
| 7 | Artifactory repos | YOU | 1h | ⬜ |
| 8 | Artifactory publish workflow | AI | 1.5h | ✅ drafted — gated, awaits Step 7 |
| 9 | Verify Artifactory install | AI | 0.5h | ⬜ |
| 10 | Exedra — scope unknown | YOU | TBD | 🚧 blocked |
| 11 | Close release gates + announce | BOTH | 0.5h | ⬜ |
Total remaining (steps 1–9, 11): 6–7 hours. Step 0 came in far under estimate — the drift risk did not materialize. Step 10 is unestimated until scoped.
Step 0 — Green baseline ✅¶
Nothing else matters until the suite is green. The tree had not been exercised since 2026-07-24; six weeks of dependency drift was the single biggest schedule risk in this plan. It did not materialize.
- [x] Full suite:
python -m pytest -q --no-cov→ 624 passed, 4 skipped, 18.8s - [x] Lint gate:
ruff check .→ 2 import-sort errors, auto-fixed, now All checks passed - [x] Type gate:
mypy testo_core→ 59 errors in 24 files (advisory only, non-blocking inci.yml) - [x] Frontend build:
npm --prefix frontend run build→ built in 1.02s - [x] Frontend tests:
npm --prefix frontend run test→ 8 files, 14 tests passed
Failures found and fixed: ruff import-organization errors in tests/contract/api/test_analytics_contract.py and tests/unit/testo_core/test_pyramid_viz.py. Fixed with ruff check . --fix; both files re-tested green (14 passed).
Carried forward, not blocking: 59 advisory mypy errors, worst offender testo_core/reporting/reporters/orchestrate.py (object has no attribute print, plus stale type: ignore codes). Belongs with Technical Debt Tracker Workstream 5 — do not expand v1.0 to fix these.
Done when: ~~all five commands exit 0~~ ✅ — four gates green, mypy advisory-only as designed.
Step 1 — Streamlit decision¶
The React frontend is the official UI (Phase 3 gates 100%). testo_ui/, the testo-ui entrypoint, and streamlit>=1.36.0 are still shipped. See Streamlit to React Migration Guide.
Pick one:
- [x] A — Deprecate (chosen 2026-09-05): emit a
DeprecationWarningontesto-uientry, note it inCHANGELOG.mdandREADME.md, schedule removal for v1.1. ~30 min. - [ ] ~~B — Remove now:~~ (deferred to v1.1) drop
testo_ui/, the entrypoint, the[ui]extra, and thestreamlitdependency. Shrinks install surface but touches packaging contract tests. ~2h.
Done when: the choice is implemented, tests still green, and CHANGELOG.md records it.
Step 2 — Version + CHANGELOG roll¶
- [x]
pyproject.toml:version = "0.1.0"→"1.0.0" - [x] Confirm
testo_core.__version__resolves to1.0.0→testo --versionprintstesto 1.0.0 - [x]
CHANGELOG.md: rolled to## [1.0.0] - 2026-09-05, fresh[Unreleased]added, compare links updated - [x]
python scripts/check_changelog_format.pypasses - [x] Contract tests that assert on version still pass → full suite 624 passed
- [x] Commit as
chore(release): bump to 1.0.0(Conventional Commits —commitlint.ymlenforces this)
Done when: testo --version prints 1.0.0 and the changelog gate passes.
Step 3 — PyPI publish auth — YOU¶
publish.yml is release-triggered and uses OIDC trusted publishing (no long-lived token). It has never fired. Without this step, the first release run fails at the publish job. See Publishing to PyPI.
- [ ] Register/claim the
testo-coreproject name on PyPI - [ ] Add a trusted publisher: repo
taltal-beep/testosterone, workflowpublish.yml, environmentpypi - [ ] Repeat on Test PyPI with environment
test-pypi - [ ] Create GitHub Actions environments
test-pypiandpypi(Settings → Environments)
Done when: both environments exist in GitHub and both trusted publishers are registered.
Name availability — checked 2026-09-05: testo-core is free on PyPI (HTTP 404, unregistered). No fallback name needed. Note that testosterone is taken, so the distribution name stays testo-core as already configured.
Step 4 — GHCR permissions — YOU¶
docker-publish.yml pushes ghcr.io/<owner>/testo-runner on release. See Publishing Docker Images.
- [ ] Confirm the release job has
packages: write - [ ] Confirm Actions is allowed to publish packages for this repo/org
- [ ] Decide package visibility (public for OSS)
Done when: repo settings permit a GHCR push from Actions.
Step 5 — Tag, release, firefight¶
This is the step that actually ships. Both publish workflows fire off the GitHub Release event.
- [ ] Create annotated tag
v1.0.0onmain - [ ] Push the tag
- [ ] Publish a GitHub Release with the
[1.0.0]changelog section as notes - [ ] Watch
publish.yml— Test PyPI dry-run → PyPI - [ ] Watch
docker-publish.yml— GHCR push - [ ] Fix whatever fails on first run and re-run
Done when: both workflows are green.
Two release-blocking workflow bugs were already found and fixed on 2026-09-05 (see Session log) — the first-run failure budget is now smaller, but not zero.
Expect one failure cycle. These workflows have literally never executed. Budget for it rather than being surprised. If a fix requires a code change, the tag must be deleted, moved, and the release re-cut.
Step 6 — Verify public install¶
- [ ]
pip install testo-core==1.0.0into a clean venv - [ ]
testo --version→1.0.0 - [ ]
testo cycles listagainst a sample repo - [ ]
docker pull ghcr.io/<owner>/testo-runner:1.0.0 - [ ]
docker run --rm ghcr.io/<owner>/testo-runner:1.0.0 run --help
Done when: a fresh machine can install and run Testo from public registries.
Step 7 — Artifactory repositories — YOU¶
Net-new: there is currently zero JFrog/Artifactory configuration in the repo or the vault.
- [ ] Create a PyPI-type local repository (e.g.
testo-pypi-local) - [ ] Create a Docker local repository (e.g.
testo-docker-local) - [ ] Choose auth: JFrog OIDC integration with GitHub Actions (preferred, no stored secret) or an identity token stored as a GitHub secret
- [ ] Record the base URL, repo keys, and auth method below
Instance URL: (fill in) Repo keys: (fill in) Auth method: (fill in)
Done when: you can pip install and docker pull from those repos manually with your own credentials.
Step 8 — Artifactory publish workflow¶
- [x] New
.github/workflows/artifactory-publish.yml, release-triggered plusworkflow_dispatch, gated onvars.ARTIFACTORY_URLso it no-ops until Step 7 lands - [x] Wheel + sdist upload via
twineagainst the Artifactory PyPI index - [x]
docker/login-action+ push to the Artifactory Docker repo - [ ] ~~Reuse the build artifacts from
publish.yml~~ — rejected: cross-workflow artifact passing adds coupling for a 40s build. Rebuilds instead. - [x] New vault note Publishing to Artifactory
- [x] Linked from Index
- [x]
CHANGELOG.mdentry
Done when: ~~exists, documented, dry run~~ — exists and documented ✅; dry run still pending Step 7 credentials, via workflow_dispatch with version 1.0.0.
Step 9 — Verify Artifactory install¶
- [ ]
pip install --index-url <artifactory-pypi-url> testo-core==1.0.0 - [ ]
docker pull <artifactory-docker-url>/testo-runner:1.0.0 - [ ] Both smoke-tested with
--version/run --help
Done when: Artifactory is a working distribution channel.
Step 10 — Exedra — 🚧 BLOCKED, needs scoping¶
"Exedra" appears nowhere in the codebase, the vault, or project memory. It cannot be planned or estimated until identified. Answer one of:
- [ ] It is a package registry (which protocol — PyPI index, Docker, generic?) → treat as a clone of Steps 7–9, +1.5–2h
- [ ] It is a deployment/runtime platform (needs Helm charts, k8s manifests, an app definition) → this is a different project, +6–10h, and should get its own plan note
- [ ] It is an internal corporate platform → needs access details and its onboarding docs before anything can be estimated
- [ ] It was a typo → strike this step
Done when: the step is either scoped into real tasks or struck from the plan.
Step 11 — Close gates and announce¶
- [ ] Check off release-time items in Release Checklist - Phase 2 CI Integrations
- [ ] Check off registry items in Release Checklist - Phase 2 Runner Image
- [ ] Move the
v1/latesttags - [ ] Add install + version badges to
README.md - [ ] Update Publish Readiness Assessment - 2026-07-24 with a "released" footer
- [ ] Update Product Roadmap and V1 Release Roadmap status
Done when: the vault reflects a shipped v1.0.0 rather than a pending one.
Explicitly out of scope for v1.0.0¶
Deferred by design — do not let these expand the release:
- Exception-handling hardening in Docker/S3/reporter paths (Technical Debt Tracker, Workstream 5)
- External action repo (
ariel-evn/uqo-action) wiring - trivy/grype image scanning
- Baseline latency metrics
- MySQL optional driver validation
- Full Streamlit removal (if Step 1 chose option A)
Session log¶
| Date | Steps completed | Notes |
|---|---|---|
| 2026-09-05 | Plan created | Steps 0–11 defined; Step 10 blocked on scoping |
| 2026-09-05 | Steps 1, 2, 8 ✅ | Streamlit option A (stderr notice, removal v1.1). Bumped to 1.0.0, changelog rolled. Artifactory workflow + guide landed, gated. Fixed 3 latent bugs in publish.yml / docker-publish.yml that would have failed the first release: testo-core==v1.0.0 invalid specifier, image refs pointing at a nonexistent v-prefixed tag, and latest gated on is_default_branch (never true on release). Remaining: Steps 3, 4, 7 (all YOU), then 5, 6, 9, 11. |
| 2026-09-05 | Step 0 ✅ | Suite green first try (624 passed). Ruff auto-fixed 2 files. Mypy 59 advisory errors deferred to tech debt. Next: Step 1 Streamlit decision. |
| 2026-09-27 | Step 5 pre-flight | Dockerfile.testo-runner ran as root, which the blocking Trivy config scan in docker-publish.yml rejects (DS002, HIGH); now runs as user testo with testo as entrypoint. [Unreleased] folded into [1.0.0] and redated, since the tag ships those changes. |