Repository navigation
fix(#3542): one total order for platform builds — the release markers were ranked by label too - #3752
Merged
meshweaver-cloud[bot] merged 1 commit intoSep 8, 2026
Conversation
… ranked by label too #3551 fixed the self-updater's tag ordering on 2026-09-07. #3727 landed three MORE readers on 2026-09-08 that rank platform builds by the version string, over the same labels one layer down — the `_releases/<platform-version>` markers `publish-bake-bundles.sh` writes, one per publication, each named by exactly the string that publication's tag carried: * SealedPublicationIndex.ReleasesOf identity -> its newest published version * ShippedPrebuiltBundles.FallbackPublished which sealed publication a tolerant adoption takes * PrebuiltBundleStore.Plan (retention) which publications survive the sweep So `3.1.0-ci.7841` (the withdrawn 2026-09-05 slip) and `3.0.0-rc9.ci.7824` (the retired line, SemVer §11.4 compares `ci`/`rc9` as TEXT) outrank the later sealed `3.0.0-ci.8130` at all three. The third is the one that costs bytes rather than a listing: retention decides what to DELETE, so the stale publication is protected and the newest — the one the running platform adopts — is collected. The order is now defined ONCE, `PlatformReleaseOrder.Newest`: band by BuildOrdinal, then run number, then SemVer. VersionSelect.PickTargets' inline banding becomes a call to it, so there is no second copy to drift from the first. It is a comparer where `Compare` cannot be: pairwise the three members form a cycle (slip > promotion > sealed > slip) and a sort handed a cycle answers arbitrarily. The declared minMeshVersion FLOOR is deliberately NOT changed — it is a threshold, not an order, and needs the opposite answer for the same two strings. Its own question was settled by MeshWeaver.Plugins#1447 (42 packages off the rc line + a gate) and #3648 (advisory). Falsified: with the two src comparer lines reverted to NuGetVersionComparer, 4 of the 6 new arms fail and the 2 positive controls pass; with the fix, 6/6 and 46/46 in Hosting.Test, 27/27 in Graph.Test, 64/64 VersionSelectTest. Refs #3542, #3551, #3727, #3648.
Contributor
There was a problem hiding this comment.
🟢 Approval recommended
The change consolidates ordering into a single shared comparer, updates all known call sites consistently, and adds focused regression tests covering both failure and positive-control directions.
Pull request overview
This PR standardizes how the platform decides “newest build” across all readers by introducing a single total order (PlatformReleaseOrder.Newest) that ranks sealed publications by their CD run number (lineage) rather than by the version label (SemVer), preventing mislabelled/retired labels from permanently outranking newer sealed builds.
Changes:
- Add
PlatformReleaseOrder.Newestas the shared total-order comparer for heterogeneous platform-version sets (banded by presence ofBuildOrdinal, then run number, then SemVer). - Switch all marker/tag ranking call sites (self-update selection, release-marker indexing, fallback bundle selection, and retention planning) to use
PlatformReleaseOrder.Newest. - Add targeted regression tests and update documentation/What’s New to record the incident, rationale, and ordering rules.
File summaries
| File | Description |
|---|---|
| test/MeshWeaver.Hosting.Test/SealedPublicationLineageTest.cs | New regression tests covering release-marker ranking and retention outcomes against the incident’s mislabelled/retired labels. |
| test/MeshWeaver.Graph.Test/PlatformReleaseOrderTest.cs | Adds tests proving the new total order is transitive and matches the intended incident-driven ordering. |
| src/MeshWeaver.Plugin.Packaging/PlatformReleaseOrder.cs | Introduces PlatformReleaseOrder.Newest (total order) alongside the existing pairwise Compare/IsNewer semantics. |
| src/MeshWeaver.Hosting/ShippedPrebuiltBundles.cs | Uses the shared total order when selecting fallback published bundles. |
| src/MeshWeaver.Hosting/SealedPublicationIndex.cs | Uses the shared total order when determining “newest version per identity” from release markers. |
| src/MeshWeaver.Hosting/PrebuiltBundleRetention.cs | Uses the shared total order inside retention planning so deletion decisions aren’t driven by label mis-ordering. |
| memex/Memex.Portal.Shared/SelfUpdate/VersionSelect.cs | Replaces the prior inline banded ordering with PlatformReleaseOrder.Newest to eliminate drift across call sites. |
| src/MeshWeaver.Documentation/Data/WhatsNew/2026-09-08-a-mislabelled-build-can-no-longer-outrank-a-newer-one-anywhere.md | Adds a What’s New entry describing the fixed defect and its operational impact. |
| src/MeshWeaver.Documentation/Data/Architecture/SelfUpdateTargetSelection.md | Updates the architecture doc to reflect the four ranking readers and the single shared total order. |
Review details
- Files reviewed: 9/9 changed files
- Comments generated: 0
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Contributor
Contributor
Contributor
Contributor
Contributor
Contributor
Contributor
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.
One total order for platform builds — fixing the selector moved the trap, it did not remove it
#3551(2026-09-07) taught the self-updater to rank registry tags by the CD run number rather thanby SemVer, because a version LINE is a label a human maintains and a label can be wrong.
#3727(2026-09-08) landed three more readers that rank platform builds by the version string, over the
same labels one layer down.
The denominator, measured
Registry tags —
az acr repository show-tags -n meshweaver --repository memex-portal-ai,2026-09-08: 1403 tags → 97 survive the structural filters (per-RID images,
-latestpointers,git-sha tags and
edgedropped). Every one of them is3.0.0-ci.<n>; zero3.1.0-*, zerorc*, so zero outrank the newest sealed set (3.0.0-ci.8130) under either comparator. 🚨 Thatzero is the product of a HAND DELETION (this issue's proposal 4, done by an operator on 2026-09-07),
not of the ordering — which is exactly why "one bad tag" is the wrong reading. The property that
made the slip permanent is a standing one, and it had already recurred elsewhere.
Release markers —
publish-bake-bundles.shwrites_releases/<platform-version>on every run,one file per publication, its NAME the exact string that publication's tag carried. The retired
3.0.0-rc*.ci.*line and the withdrawn3.1.0-ci.783xsets both published bakes, so both wrotemarkers, and retention removes a pre-release marker only when the identity it names is collected —
the tag deletion did not touch them. 🚨 I could not enumerate the share to count them (this
credential has no Storage File/Blob Data role;
az storage file list --auth-mode login --backup-intentis refused). That is "could not find out", not "none", and it is not the basis ofanything below.
The three readers, and what a label does to each
VersionSelect.PickTargets3.1.0build (already fixed)SealedPublicationIndex.ReleasesOfShippedPrebuiltBundles.FallbackPublishedBundlesOfPrebuiltBundleStore.Plan(retention)3.1.0-ci.7841(the withdrawn 2026-09-05 slip) and3.0.0-rc9.ci.7824(the retired line — SemVer§11.4 compares the identifiers
ciandrc9as text) outrank the later, sealed3.0.0-ci.8130at all of them. The last row is the one that costs bytes rather than a listing: retention decides
what to delete, so the label does not merely mis-rank — it keeps the stale publication and
collects the one the running platform actually adopts.
The ordering, stated
Rank by sealed-publication lineage, and where a candidate carries none, by the version string it
does carry — banded, never mixed pairwise.
PlatformReleaseOrder.Newest, anIComparer<string>,is the ONE definition, and
VersionSelect.PickTargets' inline banding becomes a call to it so thereis no second copy to drift from the first. Three keys, lexicographically, each a total order:
BuildOrdinaloutranks one that does not;makes a mislabelled line lose;
version string is the only key the members share.
🚨 It is a comparer where
Comparecannot be. Pairwise the three members form a CYCLE: the slip3.1.0-ci.7841beats the promotion3.0.0(SemVer), the promotion beats the sealed3.0.0-ci.8130(a release outranks its pre-releases), and the sealed tag beats the slip (run number). A sort handed
a cycle answers arbitrarily — which is how a "fixed" ordering keeps picking the wrong build.
PlatformReleaseOrderTestruns that cycle through every input permutation so a stable-sortaccident cannot hide a regression.
Nothing here is a denylist, a special case or a widened comparison. A tag cannot be made to win
by editing its label, because the label is not the key.
Selectability of a withdrawn or unsealed candidate — unchanged, and already the right shape
SelectCandidateskeeps its three-valuedInstalledTagResolution(Resolved/Withdrawn/Indeterminate), the availability walk still takes the first candidate with a sealed bake, andReleasesOfstill reports an identity no marker names as ABSENT — whichFamilystrictness declinesas "a line it cannot establish", never as "fine". A failed read is still not a negative.
🚨 The declared
minMeshVersionFLOOR is deliberately NOT changedA floor is a threshold, not an order, and it needs the OPPOSITE answer for the same two strings:
for the updater
3.0.0must outrank3.0.0-ci.7977(or a Stable install can never reach the releaseit waits for), while a
3.0.0FLOOR must be satisfied by3.0.0-ci.7977. So the shareable part isthe key (
BuildOrdinal), never the predicate. Its own question was settled twice over, onconsecutive days, and both are recorded on the doc page:
they declare
3.0.0-ci.7845now — and addedcheck-module-floors.py, which refuses a new rcfloor. Both sides of every floor comparison are on one convention, where SemVer is right by
construction. (Measured: a stale local Plugins checkout still shows 41 rc floors;
origin/maindoes not.)
measured by the link probe, never declared.
Evidence, both directions
Falsified. With the two
src/comparer lines reverted toNuGetVersionComparerand everythingelse identical: 4 of the 6 new arms FAIL —
AMislabelledLine_…,ARetiredPrereleaseLabel_…,APromotionIsRankedBehindTheLineageItWasCutFrom_…,TheSweepKeepsTheNewestRun_NotTheHighestLabel—and the 2 positive controls PASS (
ALaterRunStillWins_…,TheSweepStillKeepsAGenuinelyNewer…).With the fix: 6/6.
Green locally,
-c Release -warnaserrorone project per invocation,0 Error(s)each:MeshWeaver.Hosting,Memex.Portal.Shared,MeshWeaver.Plugin.Packaging, and the four testprojects. Filtered runs: Hosting.Test 46/46, Graph.Test 27/27, Memex.Portal.Shared.Test
VersionSelectTest64/64 (the "a legitimately newer set still wins" direction, includingAMislabelledLine_NeverOutranksALaterSealedSetandContinuousFollowsTheCiLine_AndDoesNotJumpToTheRelease), Documentation.Test 358/358.check-record-signatures✓ (7 changed C# files, no binary break),check-type-forwards✓.Work product conserved
Doc/Architecture/SelfUpdateTargetSelection— §4 rewritten (the four readers, the total order, why it is a comparer and
Compareis not, and whythe floor is still not this predicate), §5 carries the re-measured tag denominator plus the marker
caveat, its stale "#3648 has not landed yet" banner is settled, and "Where it lives" now names the
Hosting readers. Plus a What's New entry (
Category: Fix).Still open on #3542 after this
The
~1300 [MergeGuard] Admin/UpdatePolicy: refused stale/reordered cross-hub writeevents in theissue body. That is a write being REFUSED, not a write destroying state (#3619 fixed that half), and
nothing here touches it.
Pairs-with: none — no public type or member is removed;
PlatformReleaseOrder.Newestis additive andVersionSelect.Parseis private.Implementers: none — no interface or abstract member is added.
Mirror-sync: none — no i18n catalog key is added, changed or re-worded.
Refs #3542, #3551, #3727, #3648, Systemorph/MeshWeaver.Plugins#1447.
🤖 Generated with Claude Code