fix(release): guard the MSI cross-release ordering hazard at a MAJOR bump - #544
Merged
Merged
Conversation
…bump scripts/package-version.sh's MINOR-overflow carry (#537, closing #521/#522) folds an overflowing MINOR into the idle MSI major field and is monotonic for the whole MAJOR==0 lifetime, but it cannot see a cross-release hazard: a carried release like 0.511.0 (MSI 1.255.0) can compare HIGHER under msiexec's numeric ordering than a later, perfectly legal 1.0.0 (MSI 1.0.0). An in-version guard can't catch this -- 1.0.0 alone is not illegal, only a specific predecessor makes it a downgrade. Add an optional second argument, PREV_VERSION (the previous stable release's bare X.Y.Z). When supplied, the script folds it with the same rule and refuses to emit a version whose MSI tuple compares LOWER than PREV_VERSION's -- turning the "pick a high-enough MAJOR-bump number" decision into a machine-checked one instead of an unchecked human call. Omitting it (every existing package.yml callsite) is byte-for-byte unchanged; ensure-version-increment.yml already checks out both the PR head and main, so a follow-up can wire main's version in at zero extra cost (sequenced separately against #542, which is adding a step to that same job). Measured (dig-node#540): MINOR has never exceeded 255 in any released dig-node version, so nothing shipped is affected today -- this closes the hazard before it can ever be reached rather than reacting once it is. Also corrects "dig_ecosystem#521/#522" references in this script/test/SPEC to "#521/#522" -- those are dig-node's own issues, not the unrelated dig_ecosystem tickets of the same numbers. Revert-proof: stripping the guard reproduces the exact hazard as an accepted exit 0 (0.511.0 -> 1.0.0), and reinstating it passes the full suite (58 ok / 0 fail). Closes #540 Co-Authored-By: Claude <noreply@anthropic.com>
Merged origin/main forward (7042f89, #535) rather than rebasing -- the queue across the five open PRs sequences this one last (#542 0.254.42, #539 0.254.43, #543 0.254.44, #536 0.254.50, this PR 0.254.51), deliberately: #542 touches ensure-version-increment.yml, which this PR's follow-up note also targets, so landing last means the final shape is on main rather than guessed at. Re-read the version from Cargo.toml on disk (not the commit log) after the merge, per the standing caution that a rebase can silently drop a bump commit as "already upstream" -- this was a merge, and the version file itself confirms 0.254.51 post-bump. Co-Authored-By: Claude <noreply@anthropic.com>
MichaelTaylor3d
added a commit
that referenced
this pull request
Sep 3, 2026
Per the main lane: main advanced to exactly 0.254.41 after #535's rebase, tying this branch's version. 0.254.43 clears main and every sibling PR in the version-bump queue (#542=0.254.42, #543=0.254.44, #536=0.254.50, #544=0.254.51). Cargo.lock re-synced with `git checkout origin/main -- Cargo.lock` followed by `cargo update -w --offline` (never hand-editing lock conflict markers), confirmed clean with `--dry-run` -> `Locking 0 packages`. Co-Authored-By: Claude <noreply@anthropic.com>
…ease-ordering # Conflicts: # Cargo.lock # Cargo.toml
MichaelTaylor3d
added a commit
that referenced
this pull request
Sep 3, 2026
#539) * fix(wallet): discipline reservation liveness against a monotonic clock Reservation deadlines (#502/#525/#528) are anchored entirely on wall-clock readings. #528 closes the case where the clock is already wrong at the moment a reservation is FIRST written. It does not close the general form (#532): a wall clock stepped FORWARD while a reservation is already live, mid-hold -- an NTP step, a VM pause/resume, an operator setting the clock -- produces no self-contradiction for #528's check to catch, yet the very next prune reads the jump as elapsed time and can retire a bundle's hold while it is still genuinely in flight, with no bound on how far forward the step goes (the #348/#497 double-spend direction). Add ClockGovernor: it disciplines every reservation-lifecycle "now" reading so it cannot advance, between two observations, faster than a monotonic clock says real time has actually elapsed. A forward wall-clock jump is absorbed rather than trusted and the disciplined clock simply runs behind until real time catches up, at which point it resumes tracking the wall clock with no special unfreeze step. A backward step is passed straight through unclamped, since it can only lengthen a hold, never shorten one -- the safe direction #502/#528 already accept elsewhere. The governor lives for the process's lifetime and is not persisted: a restart re-seeds it from the wall clock at that moment, so a clock already wrong at boot remains #528's write-time contradiction check's problem, not this one's. Closes #532 Co-Authored-By: Claude <noreply@anthropic.com> * chore(release): bump workspace version to 0.254.20 Root workspace version, per the main lane -- the minor field's version scheme is being fixed separately under #521/#522; this is the interim number to carry PR #539 (dig-node#532) through the version-increment gate. Cargo.lock refreshed in the same commit (cargo update -w --offline) so dig-node-service's locked entry matches -- every CI job runs --locked, and a manifest-only bump here fails Clippy/Test+coverage/all three package builds together on a change that cannot otherwise break a build. Co-Authored-By: Claude <noreply@anthropic.com> * chore(release): bump workspace version to 0.254.43 Per the main lane: main advanced to exactly 0.254.41 after #535's rebase, tying this branch's version. 0.254.43 clears main and every sibling PR in the version-bump queue (#542=0.254.42, #543=0.254.44, #536=0.254.50, #544=0.254.51). Cargo.lock re-synced with `git checkout origin/main -- Cargo.lock` followed by `cargo update -w --offline` (never hand-editing lock conflict markers), confirmed clean with `--dry-run` -> `Locking 0 packages`. Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
…eep 0.254.72) # Conflicts: # Cargo.lock # Cargo.toml
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.
DO NOT MERGE -- gate round in progress.
Task
Closes #540:
scripts/package-version.sh's MINOR-overflow carry (added by #537, resolving #521 and #522) ismonotonic for dig-node's whole
MAJOR==0lifetime, but the SPEC itself flagged a cross-releasehazard it cannot see: a carried release like
0.511.0(MSI1.255.0) can compare HIGHER undermsiexec's numeric ordering than a later, perfectly legal
1.0.0(MSI1.0.0).msiexecwouldeither refuse the "upgrade" (
DowngradeErrorMessage) or leave the older payload installed.Measurement first
Checked every published dig-node tag: the highest MINOR ever shipped is 252 (
0.252.11), wellunder the 255 threshold where the carry even activates. The hazard has never been reached. But
"never reached yet" is not "unreachable" -- minor climbs every feature release and is 3 bumps from
255 -- so I did not close this as not-applicable; I closed the gap before it can be reached.
What changed
scripts/package-version.shgains an optional second argument,PREV_VERSION(the previousstable release's bare
X.Y.Z). When supplied, the script folds it with the identical rule andrefuses to emit a version whose MSI tuple compares LOWER than
PREV_VERSION's -- turning "pick aMAJOR-bump number high enough to clear history" from an unchecked human decision into a machine
check. An EQUAL tuple is NOT rejected (that's the existing
AllowSameVersionUpgrades="yes"same-version-upgrade case, not a downgrade).
Omitting the argument is byte-for-byte unchanged -- all three existing
package.ymlcallsitespass a single arg today and are untouched by this PR.
Sequencing with #542
#542 (open, draft, "gate round in progress") adds a step to
ensure-version-increment.yml'srequired
Check version incrementjob that callspackage-version.shwith the head version only.That job already checks out both
mainand the PR head, so wiring this guard in is a one-linefollow-up once #542 merges: pass
main's Cargo.toml version as the second argument to the samecall. I deliberately did NOT touch
ensure-version-increment.ymlin this PR to avoid conflictingwith #542's in-flight branch on the same file -- @mt-dev/orchestrator, please sequence: land #542,
then a 1-line addition wires this guard into that job.
Also fixed (cheap, same files already open)
#521/#522were referenced asdig_ecosystem#521/dig_ecosystem#522in the script, its testfile, and SPEC.md -- those are dig-node's OWN issues (verified: "Version scheme has exhausted the
MSI minor field..." / "fix(release)!: the 0.x version scheme hits the MSI 255 ceiling..."), not the
unrelated dig_ecosystem tickets of the same numbers. Corrected to bare
#521/#522in the threefiles this PR already touches.
Verification
Test COUNTS, not exit status:
scripts/tests/package-version.test.sh: 58 ok / 0 fail (was 49 before this PR; +9 new casesfor the cross-release guard).
package-version.shtoorigin/main's version (script only, kept thenew tests) and re-ran -- 4 genuine failures, including the ticket's exact scenario:
FAIL the ticket's own example -- 0.511.0 outranks a naive 1.0.0: accepted 1.0.0 over prev 0.511.0 (exit 0). Restored the fix -> back to 58/0. The test suite demonstrably catches the defect.package-version.sh 1.0.0 0.511.0->package-version: '1.0.0' (MSI 1.0.0) compares LOWER than the previous release '0.511.0' (MSI 1.255.0) ... exit=1(the hazard, refused)package-version.sh 2.0.0 0.511.0->msi_product_version=2.0.0 exit=0(a number that clearshistory, accepted)
package-version.sh 0.511.0(no 2nd arg, an existing callsite shape) -> unchanged,msi_product_version=1.255.0 exit=0shellcheckon both touched shell files: only pre-existing SC2016 info notes on lines this PRdid not touch (deliberate single-quoted injection-test literals).
Version bump
0.254.40->0.254.51(patch -- script/test/spec hardening, no behaviour change for any existingcaller). Re-bumped once: main advanced to
0.254.41via #535 while this PR was in flight, so Imerged origin/main forward (not rebase) and re-bumped. The queue across five open PRs sequences
this one last on purpose -- #542 0.254.42, #539 0.254.43, #543 0.254.44, #536 0.254.50, this PR
0.254.51 -- so by the time this merges, #542 (touching the same
ensure-version-increment.ymlthisPR's follow-up targets) is already on
main. RootCargo.toml+cargo update -w --offline(confirmed
--dry-run->Locking 0 packagesafter the real update; version read back fromCargo.tomlon disk, not the commit log).Closes #540