[FEATURE] Branching strategy Phase 2.B - release trigger and versioning docs - #557
Conversation
Detects an umbrella overture-schema major/minor bump on main and creates a draft GitHub Release at v<major>.<minor>.0. Publishing the draft is the only trigger for the public PyPI publish (Phase 3). Decisions locked: patch builds stay CodeArtifact-only; release notes are authored manually at release time; only the umbrella package bump cuts a release. Closes #533 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Signed-off-by: John McCall <john@overturemaps.org>
…ases Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Signed-off-by: John McCall <john@overturemaps.org>
…ft release tag Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Signed-off-by: John McCall <john@overturemaps.org>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Signed-off-by: John McCall <john@overturemaps.org>
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
…changelog - release-trigger: detect per-package major.minor bumps on main and cut a published GitHub Release per package (tag `<package>-v<major>.<minor>.0`, notes extracted from that package's CHANGELOG.md; umbrella marked Latest) - require-changelog-fragment: new PR check enforcing a changelog update on any major.minor bump (accepts a towncrier fragment or a built CHANGELOG.md) - towncrier: centralize [tool.towncrier] in root pyproject.toml; per-package changelog.d/ with a DRY README that defers to docs/versioning.md (no .gitkeep) - docs/versioning.md: Diataxis rewrite (reference/how-to/why) with a TOC - CONTRIBUTING.md: lean guiding-light rewrite (217 -> 91 lines); richer, workflow-accurate gitGraphs - publish-python-packages: add contents: read for reusable-workflow checkout Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Signed-off-by: John McCall <john@overturemaps.org>
Wrap each release path (main->patch, main->minor, vnext->major) in a collapsible <details> section with its own workflow-accurate gitGraph. Drop the branch-protection bullet and reduce the CI notes to authoritative links to the vnext-compat and pr-advisory workflows, which comment their own fix steps inline. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Signed-off-by: John McCall <john@overturemaps.org>
… a PR Add how/when each release path reaches consumers (patch rides the next release to PyPI via the umbrella; minor and major publish to PyPI on merge). Reword the Opening a PR CI notes to point at the checks without narrating. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Signed-off-by: John McCall <john@overturemaps.org>
Move the inline detect heredoc and the extract_notes heredoc out of release-trigger.yaml into .github/workflows/scripts/detect_version_bumps.py and extract_release_notes.py, matching the existing package-versions.py convention. The workflow steps now just invoke them, so the Python is lint-covered, testable, and free of heredoc quoting. Behavior is unchanged. Also vary the CONTRIBUTING release diagrams across distinct packages (places/base/transportation themes) and note that a no-bump merge still publishes a patch to internal CodeArtifact. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Signed-off-by: John McCall <john@overturemaps.org>
But they can (pretty sure), so my read is that we can skip the materialization in favor of keeping things up to date manually (modulo sanity checks). |
That's also fine w me - it would simplify. John McCall (@lowlydba) Seth Fitzsimmons (@sethfitz) |
Love it - this is exactly why I had included this callout in the latest versioning.md edit:
Especially since this cuts down on invisible/magic versioning work behind the scenes that may be non-obvious to a contributor at face value (or even future us 🙃 ) |
…erialization Consensus from PR review: maintainers keep explicit >= floors in project.dependencies by hand, uv's documented dual-declaration pattern, instead of a CI step rewriting bare names at publish time. Wheel metadata now carries the floors with no pre-build mutation, and the versioning behavior is visible in the pyproject.toml a contributor actually reads. materialize_workspace_deps.py is deleted; the major-bump cascade check stays as the sanity net. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Signed-off-by: John McCall <john@overturemaps.org>
Bare uv sync could resolve newer dependency versions than the lock the change was tested against. --locked installs exactly uv.lock and fails if it is stale, matching check-python-code. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Signed-off-by: John McCall <john@overturemaps.org>
+ Victor Schappert (@vcschapp) Done in 7842f7c. All 12 dependent packages now declare explicit |
New contributors hit the fragment check before they ever read versioning.md, so give them a 30-second copy-paste answer: quick-start section up top of the how-to, the CI failure message pointing at it, and the changelog.d keeper READMEs linking it. The old failure message also told contributors to run towncrier build, which is the release-time maintainer step, not theirs. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Signed-off-by: John McCall <john@overturemaps.org>
|
Important thread that's not surfacing on this PR because I commented directly on a commit: 7842f7c#r194263713 It's about how versioned dependencies work within a uv workspace, and the answer complicates CI. |
Its showing 0 comments on that commit for me - can you re-post in the main thread here? Seth Fitzsimmons (@sethfitz) |
( Hmm. I think this fact breaks our CI assumptions, since they run against the workspace (and therefore the tip of each package version). The "lowest-direct" CI cell is the thing we depend on here, since it verifies that floor and ceiling behavior is the same, but if it's not able to test against the floor (because it can't/won't be installed), we don't have the guardrail we expect for intra-workspace dependencies. We might be able to fix that by testing each package in isolation with the "lowest-direct" cell (so it pulls from PyPI instead of using the workspace). This all assumes that we will only ever use >= or > comparisons, since <= and < can't be fulfilled without isolated testing within the other CI cells, which might also fail because the tips of each intra-workspace dependency haven't been published (so we can't check work in progress). I may be wrong here. The answer hinges on whether foo = { workspace = true } forces the package to come from the workspace, or if the workspace is a viable source alongside PyPI. (And if there's a way to list PyPI as an alternative source to the workspace, if the former.) If the version in the workspace can't satisfy the dependency, will it fall back to PyPI for previously-published versions? Verified, unfortunately:
|
|
Copilot resolve the merge conflicts in this pull request |
Done. Merged |
I want to make sure I'm understanding this. Are you saying that if package |
Yes.
|
Cool. John McCall (@lowlydba) and I just met on this topic. The tl;dr is:
Seth Fitzsimmons (@sethfitz) What are your thoughts on the above? The best solution I can think of to make it work correctly is to have the CI machinery:
Part 2 of the above solution is a bit complicated and comes with the downside that the publish can succeed partially and fail partially, e.g., I successfully publish package This partly succeeded, partly failed, publish state is mostly fine since the packages that did succeed in being published work by definition.† But then it leaves you with the question of what do you do with the packages that we made version bump commits for but for which version bumps failed. Here's a concrete example of that situation.
How do we recover from this state? John McCall (@lowlydba) and I thought of two available manual recovery options and they're both equally valid:
Anyway, and in conclusion, it's a bit complicated and I do think it makes sense to go live without that guardrail for the time being, see how pervasive these versioning bugs are, and if necessary course correct by building the guardrail later. †: The partially failed state can also be avoided by creating another internal private CodeArtifact repo to use instead of PyPI, but again that's more complexity and probably not worth doing. |
Summary
Phase 2.B of the branching strategy (#533): release detection on
main, the changelog machinery, and the versioning docs. No publish workflows, those are Phase 3 (#509), which this unblocks.The versioning model settled during review (thanks Victor Schappert (@vcschapp), Seth Fitzsimmons (@sethfitz)): humans own the full
<major>.<minor>.<patch>per package, any bump merged tomainis a release, and CI only versions the interim internal builds between releases.What lands
Release trigger
release-trigger.yaml+detect_version_bumps.py: on push tomain, diffs everypackages/*/pyproject.tomlversion against the previous commit. Any increase (patch included) cuts one published GitHub Release per bumped package: tag<pkg>-v<version>, title`<pkg>` <version>, notes read from that package'sCHANGELOG.md. Fails loudly on version decreases and on pre-existing tags. The umbrellaoverture-schemarelease is marked Latest.release-trigger. Direct-dep checking covers longer chains, each unbumped link fails its own check.create-package-releasecomposite action with adry-runinput for testing.Changelog machinery
packages/<pkg>/changelog.d/), config centralized in the rootpyproject.toml.require-changelog-fragment.yaml: any PR touching a package must carry a fragment or a builtCHANGELOG.md. Plain git diff + grep, no action dependencies.towncrier buildso the notes are reviewed as part of the version-bump diff.Internal build versioning
compute-versionreworked: no-bump merges produce<version>.postN+main.<sha>(or+vnext.<sha>). PEP 440 post-releases order after the release, so>=X.Y.Zconsumers resolve the freshest internal build while==X.Y.Zselects the clean release; local labels are rejected by public PyPI, keeping these builds internal by construction.Nis a per-version sequence resolved from CodeArtifact: the first internal build of a version is.post0, later builds increment the highest published.postN.>never matches post-releases) is documented.mainandvnextbuilds must not share a CodeArtifact repository (local labels don't participate in ordering, so>=consumers could resolvevnextbreaking builds).vnextpublishing is blocked on the dedicated dev repo in ops-team#299.Workspace dependency floors
>=floors inproject.dependenciesalongside the[tool.uv.sources]workspace entries. Built wheels carry the floors directly, no build-time metadata rewriting. Floors are hand-maintained; the cascade guard above is the sanity net. This replaced an earlier just-in-time materialization script after review discussion.Docs
docs/versioning.md: scheme, trigger x destination table, bump rules, floor maintenance, release walkthrough, pin guidance.CONTRIBUTING.md: per-path release flows with gitGraphs, versioning quick reference, phase roadmap.Bonus
package_versions.pyreplaces the dual-checkout version diff: readspyproject.tomlblobs straight from git and derives the topological order from declared dependencies instead of a hardcoded map. The reusable check drops a checkout, twouv syncs, and Python setup on the no-change path.github-scriptJS for jq over the event payload.--lockedso it installs exactly the committeduv.lock.Testing
Detection, cascade enforcement, release-notes extraction, floor-carrying wheel metadata, and the resolver-ordering claims are tested locally against real repo history (simulated bump, downgrade, uncascaded-major, and multi-package cases). The workflows themselves can't run until they're on
main;release-triggersupportsdry-runfor a low-stakes first live pass. zizmor is clean on all touched workflows.Closes #533