Skip to content

fix(#3542): one total order for platform builds — the release markers were ranked by label too - #3752

Merged
meshweaver-cloud[bot] merged 1 commit into
mainfrom
fix/3542-one-total-order-for-platform-builds
Sep 8, 2026
Merged

meshweaver-cloud[bot] merged 1 commit into
mainfrom
fix/3542-one-total-order-for-platform-builds

Conversation

@rbuergi

@rbuergi rbuergi commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

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 than
by 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, -latest pointers,
git-sha tags and edge dropped). Every one of them is 3.0.0-ci.<n>; zero 3.1.0-*, zero
rc*, so zero outrank the newest sealed set (3.0.0-ci.8130) under either comparator.
🚨 That
zero 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.sh writes _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 withdrawn 3.1.0-ci.783x sets both published bakes, so both wrote
markers, 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-intent is refused). That is "could not find out", not "none", and it is not the basis of
anything below.

The three readers, and what a label does to each

Reader What it ranks What a mislabelled or retired label does
VersionSelect.PickTargets registry tags rolled both AKS portals onto a withdrawn 3.1.0 build (already fixed)
SealedPublicationIndex.ReleasesOf markers → identity → its newest version places an identity on a stale platform LINE
ShippedPrebuiltBundles.FallbackPublishedBundlesOf which sealed publication a tolerant adoption takes adopts a three-day-old bake over the newest sealed one
PrebuiltBundleStore.Plan (retention) which publications survive the sweep protects the stale publication and DELETES the newest

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 the identifiers ci and rc9 as text) outrank the later, sealed 3.0.0-ci.8130
at 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, an IComparer<string>,
is the ONE definition, and VersionSelect.PickTargets' inline banding becomes a call to it so there
is no second copy to drift from the first. Three keys, lexicographically, each a total order:

  1. the band — a version carrying a BuildOrdinal outranks one that does not;
  2. the run number, within the lineage band — the line in front of it is ignored, which is what
    makes a mislabelled line lose;
  3. SemVer, as the tie-break inside the band and as the whole of the promotion band, where the
    version string is the only key the members share.

🚨 It is a comparer where Compare cannot be. Pairwise the three members form a CYCLE: the slip
3.1.0-ci.7841 beats the promotion 3.0.0 (SemVer), the promotion beats the sealed 3.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.
PlatformReleaseOrderTest runs that cycle through every input permutation so a stable-sort
accident 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

SelectCandidates keeps its three-valued InstalledTagResolution (Resolved / Withdrawn /
Indeterminate), the availability walk still takes the first candidate with a sealed bake, and
ReleasesOf still reports an identity no marker names as ABSENT — which Family strictness declines
as "a line it cannot establish", never as "fine". A failed read is still not a negative.

🚨 The declared minMeshVersion FLOOR is deliberately NOT changed

A floor is a threshold, not an order, and it needs the OPPOSITE answer for the same two strings:
for the updater 3.0.0 must outrank 3.0.0-ci.7977 (or a Stable install can never reach the release
it waits for), while a 3.0.0 FLOOR must be satisfied by 3.0.0-ci.7977. So the shareable part is
the key (BuildOrdinal), never the predicate. Its own question was settled twice over, on
consecutive days, and both are recorded on the doc page:

  • Systemorph/MeshWeaver.Plugins#1447 (2026-09-07) took 42 packages off the retired rc line —
    they declare 3.0.0-ci.7845 now — and added check-module-floors.py, which refuses a new rc
    floor. 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/main
    does not.)
  • Module floors are advisory: a declared minMeshVersion never refuses, holds or skips — loadability is measured #3648 (2026-09-08) made the floor advisory at every runtime decision point — loadability is
    measured by the link probe, never declared.

Evidence, both directions

Falsified. With the two src/ comparer lines reverted to NuGetVersionComparer and everything
else 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 -warnaserror one project per invocation, 0 Error(s) each:
MeshWeaver.Hosting, Memex.Portal.Shared, MeshWeaver.Plugin.Packaging, and the four test
projects. Filtered runs: Hosting.Test 46/46, Graph.Test 27/27, Memex.Portal.Shared.Test
VersionSelectTest 64/64
(the "a legitimately newer set still wins" direction, including
AMislabelledLine_NeverOutranksALaterSealedSet and
ContinuousFollowsTheCiLine_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 Compare is not, and why
the 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 write events in the
issue 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.Newest is additive and
VersionSelect.Parse is 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

… 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.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 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.Newest as the shared total-order comparer for heterogeneous platform-version sets (banded by presence of BuildOrdinal, 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.

@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Test Results (shard 1)

496 tests  ±0   496 ✅ ±0   41s ⏱️ -1s
  1 suites ±0     0 💤 ±0 
  1 files   ±0     0 ❌ ±0 

Results for commit c8cf59c. ± Comparison against base commit b3e5528.

@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Test Results (shard 0)

254 tests  ±0   254 ✅ ±0   2m 17s ⏱️ +2s
  1 suites ±0     0 💤 ±0 
  1 files   ±0     0 ❌ ±0 

Results for commit c8cf59c. ± Comparison against base commit b3e5528.

@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Test Results (shard 3)

1 079 tests  ±0   1 079 ✅ ±0   1m 29s ⏱️ -4s
    2 suites ±0       0 💤 ±0 
    2 files   ±0       0 ❌ ±0 

Results for commit c8cf59c. ± Comparison against base commit b3e5528.

@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Test Results (shard 4)

1 672 tests  +3   1 478 ✅ +3   2m 9s ⏱️ -3s
    4 suites ±0     194 💤 ±0 
    4 files   ±0       0 ❌ ±0 

Results for commit c8cf59c. ± Comparison against base commit b3e5528.

@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Test Results (shard 5)

    5 files  ±0      5 suites  ±0   3m 20s ⏱️ +14s
1 764 tests ±0  1 764 ✅ ±0  0 💤 ±0  0 ❌ ±0 
1 765 runs  ±0  1 765 ✅ ±0  0 💤 ±0  0 ❌ ±0 

Results for commit c8cf59c. ± Comparison against base commit b3e5528.

@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Test Results (shard 2)

1 251 tests  +6   1 251 ✅ +6   4m 16s ⏱️ -5s
    3 suites ±0       0 💤 ±0 
    3 files   ±0       0 ❌ ±0 

Results for commit c8cf59c. ± Comparison against base commit b3e5528.

@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Test Results

   16 files  ±0     16 suites  ±0   14m 15s ⏱️ +3s
6 516 tests +9  6 322 ✅ +9  194 💤 ±0  0 ❌ ±0 
6 517 runs  +9  6 323 ✅ +9  194 💤 ±0  0 ❌ ±0 

Results for commit c8cf59c. ± Comparison against base commit b3e5528.

@meshweaver-cloud
meshweaver-cloud Bot added this pull request to the merge queue Sep 8, 2026
Merged via the queue into main with commit c6955f4 Sep 8, 2026
34 checks passed
@rbuergi
rbuergi deleted the fix/3542-one-total-order-for-platform-builds branch September 13, 2026 10:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants