fix: stop the cross-era upgrade guard and version tracking from mishandling brew --HEAD stamps - #5625
fix: stop the cross-era upgrade guard and version tracking from mishandling brew --HEAD stamps#5625anisoptera wants to merge 6 commits into
Conversation
Homebrew --HEAD installs of the core beads formula stamp main.Version as HEAD-<shortsha>, and bd writes that string verbatim into .local_version. currentVersionWitness demanded a strict x.y.z with major >= 1, so every server-mode workspace with a local dolt root whose last run was a brew --HEAD build was refused as a legacy Dolt server workspace. The same parse gap refused real pre-release witnesses such as 1.1.0-rc.1. Accepting HEAD-<hex> is not fail-open: the legacy-era tap formula (0.55-0.62) shipped prebuilt GoReleaser tarballs with baked-in semver and had no head spec, and make build / go install only override main.Build, so no legacy-era channel could ever have written HEAD-<sha> into the witness file. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G95EoTDVi5EdEFk4wJgRuG
Code review of the witness fix surfaced one serious interaction and several hardening gaps, applied here: - Gate the destructive pre-v56 recovery path on IsValidSemver: a HEAD-<sha> witness compares as version 0, which previously routed a current workspace into RecoverPreV56DoltDir and os.RemoveAll(.dolt). - Route legacyVersionMinor through the same suffix-stripping versionCore as currentVersionWitness, so legacy snapshot stamps like 0.62.1-next are still classified (and refused) as legacy rather than neither era. - Accept the full set of real Homebrew HEAD stamps (bare HEAD and HEAD-<sha>_<revision>), delegate hex checking to isHexObjectID, and bound the sha to 7-40 chars. - Update the cross-era decision table in upgrading.md to the actual admit rule, since the refusal error string sends operators there. - Replace duplicated guard subtests with a table covering both admit and refusal boundaries, and add predicate-level tests. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G95EoTDVi5EdEFk4wJgRuG
CompareVersions reads a HEAD-<sha> stamp as 0.0.0, so a brew --HEAD reinstall (HEAD-423afdc -> HEAD-f925f3f) or a release-to-HEAD move never set versionUpgradeDetected, and the one-shot post-upgrade reconciliation — eager migrate-before-open, workspace version markers, the proxied reconcile, the upgrade notification — silently skipped for every --HEAD install. Treat any changed witness with a HEAD stamp on either side as an upgrade: brew reinstalls only move forward, and if that assumption is ever wrong the workspace-side reconciler refuses to move the markers and the schema forward-drift guard fails the open. The pre-v56 recovery path stays unreachable from a HEAD stamp because it is gated on IsValidSemver(previousVersion). Actual schema migrations were never broken here — MigrateUp keys off schema_migrations on every writable open — this restores the marker bookkeeping and eager reconciliation around them. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G95EoTDVi5EdEFk4wJgRuG
bd doctor's version-tracking check called any non-semver .local_version
malformed, so every --HEAD install warned permanently — with fix advice
("run any bd command") that just rewrites the same stamp. A Homebrew
HEAD stamp is a healthy current-era marker; report it as OK, noting the
staleness heuristic cannot apply to an unordered stamp.
The predicate moves to doctor.IsBrewHeadVersion as the canonical
definition and the legacy upgrade guard delegates to it, so the guard's
admit rule and the doctor's health rule cannot drift apart.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G95EoTDVi5EdEFk4wJgRuG
…hanges Parse the major version once in currentVersionWitness and drop the negative-part guard made unreachable by versionCore's suffix truncation; replace the manual hex loop in IsBrewHeadVersion with strings.Trim; share one trackingActive DoctorCheck across the three identical OK returns in CheckMetadataVersionTracking. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G95EoTDVi5EdEFk4wJgRuG
…and CLI version check From /code-review: getVersionsSince returns nothing for a HEAD-stamp previous version instead of the whole changelog; upgrade banners render HEAD stamps verbatim via displayVersion instead of a malformed v prefix; CheckCLIVersion skips the release comparison for HEAD builds rather than warning forever; the HEAD _<revision> parse is digits-only so signed forms no longer pass the legacy guard's witness check; the pre-v56 recovery gate normalizes through versionCore so v-prefixed or suffixed legacy stamps still recover. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G95EoTDVi5EdEFk4wJgRuG
|
Not superseding this — flagging an overlap and a suggestion, from an independent hit on the same defect. I chased the identical refusal in production this week from a different writer: a Go pseudo-version, I opened #5650, which overlaps yours on
Both are still worth landing, and I did not duplicate yours. Under the unknown era a brew Suggestion, entirely yours to take or leave: if both land, the guard side of this PR could shrink — with the unknown era in place 🤖 Generated with Claude Code |
Closes #5603.
A Homebrew
--HEADinstall stampsHEAD-<shortsha>(or bareHEAD, optionally with a_<revision>suffix) intomain.Version, which is not semver. That shape leaked into every version-string consumer with a different failure in each:currentVersionWitnessrejected the stamp, so an explicit-server workspace with a local Dolt root was refused as a legacy schema.doctor.IsBrewHeadVersionis now the canonical recognizer for the stamp (bounded to git's 7–40-char hex abbreviation range, digits-only revision suffix), and the guard delegates to it.versionCorealso strips pre-release/build suffixes uniformly so a suffixed stamp cannot land outside both era buckets.CompareVersionsreads HEAD stamps as 0.0.0, so a changed stamp never registered as an upgrade; a changed stamp with a HEAD build on either side is now treated as one. The pre-v56 recovery path (which deletes.dolt) is gated on an actual semver predecessor (normalized throughversionCore) so a HEAD stamp can't read as "pre-0.56".bd doctor: the metadata tracking check reported the stamp as a permanently-unfixable invalid format, andCheckCLIVersionwarned "update available: brew upgrade beads" forever (which would move the user off HEAD). Both now treat HEAD builds as healthy.bd upgrade status/review: a HEAD-stamp previous version matched no changelog entry and dumped the entire release history, with a malformedvHEAD-<sha>banner label. HEAD stamps now yield no changelog delta and render verbatim.The docs table in
docs/getting-started/upgrading.mdis updated to describe the current-era witness shapes the guard admits.Table-driven tests cover the witness predicates, the stamp recognizer (including signed-revision and dirty-suffix rejections), HEAD-stamp upgrade detection, and the guard's admit/refuse matrix. Reviewed with /simplify and /code-review (fixes from both applied as their own commits).
🤖 Generated with Claude Code
https://claude.ai/code/session_01G95EoTDVi5EdEFk4wJgRuG