Repository navigation
A declared minMeshVersion floor is never checked against a platform build that actually exists — an rc floor is unsatisfiable for ever #3554
Description
The gap
A module declares content.minMeshVersion — a platform FLOOR — and ModulePlatformFloor.DeclineReason
refuses to land the module until the running platform satisfies it. Nothing checks that the declared
floor is satisfiable by a platform build that actually exists, so a floor can be declared that no
published image can ever meet. The module then holds for ever, and the hold message names two versions
that look like they are in the right order.
Measured, 2026-09-07
ModulePlatformFloor compares with NuGetVersionComparer (SemVer). SemVer §11.4 compares pre-release
identifiers as text, so "ci" < "rc". Against the real comparer (Compare(running, floor) < 0
⇒ DECLINED):
| running \ floor | 3.0.0-rc4 |
3.0.0-rc8 |
3.0.0-rc9 |
3.0.0 |
|---|---|---|---|---|
3.0.0-ci.1 |
DECLINED | DECLINED | DECLINED | DECLINED |
3.0.0-ci.7989 |
DECLINED | DECLINED | DECLINED | DECLINED |
3.0.0-ci.999999999 |
DECLINED | DECLINED | DECLINED | DECLINED |
3.0.0 |
ok | ok | ok | ok |
3.1.0-ci.1 |
ok | ok | ok | ok |
Observed on both AKS portals at ~08:20Z while they ran 3.0.0-ci.7981:
HOLDING 3.0.0-ci.7989 — AI: the module requires platform 3.0.0-rc8 or newer
but this deployment runs 3.0.0-ci.7989
And the registry has nothing that could satisfy those floors:
az acr repository show-tags -n meshweaver --repository memex-portal-ai → 1268 tags — 798
staging-*, 48 3.0.0-ci.*, ZERO 3.1.0-*, ZERO rc*. Every rc image is untagged, so an rc
floor is unsatisfiable in the strongest sense: there is no such build to run.
The values themselves are being corrected in Systemorph/MeshWeaver.Plugins#1447. This issue is about
the class: nothing stops the next one.
Why it belongs on a CORE lane, not the Plugins gate
The obvious home — the Plugins module gate — deliberately cannot host it. That gate runs on forks and
on Dependabot, where reading ACR needs a credential, and gating the check on whether that credential
is present is exactly the skip-trapdoor AGENTS.md forbids: GitHub paints a skipped job the same colour
as a passed one, so "the gate never ran" and "the gate passed" become indistinguishable.
A core lane that already holds ACR credentials — main-cd's bake — needs no such exemption. The
check is cheap there: list the portal repository once (already done for other steps), and assert that
every minMeshVersion declared by a package in the publication is satisfied by at least one listed
platform tag. Fail RED naming the package, the floor and the newest tag that does not reach it.
Related
- MeshWeaver#3542 / fix: self-update ranks candidates by sealed-publication lineage, and tells a strand from being current (#3542, #3543) #3551 — the sibling defect at the other call site: the self-updater ordered
candidates by SemVer, so a mislabelled3.1.0-ci.7841outranked every later sealed3.0.0-ci.79xx
set for ever. Fixed by ranking on the sealed-publication lineage. The shared root is that neither
call site may compareci.<n>againstrc<m>by SemVer. Doc/Architecture/SelfUpdateTargetSelection§4 — why "newer than" and "satisfies the floor of" are
different predicates over the same key, so the floor cannot simply reuse the updater's
comparison: for an updater3.0.0must outrank3.0.0-ci.7977; for a floor the same two strings
must give the opposite answer.- fix: a module is adopted on what this platform can LOAD, not on a version string (#3538, #3518) #3552 does not cover this: its link probe reads
ModulePlatformFloor.DeclineReason(minMeshVersion) ?? LinkHoldReason(), so it short-circuits
behind the declared floor and never runs unless that already passes.
Activity
review(#3554): a source-build lane is measured, never exempted
review(#3554): the script agrees with int.TryParse on OVERFLOW, and t…
rbuergi commentedon Sep 7, 2026
Closing — and the gate landed in a DIFFERENT place than this issue proposed, deliberately
Verified on
origin/main(fbf9e9be1) at 2026-09-07T12:25Z. The merge queue does not fireCloseslinks, so this is measurement, not the PR's word.
Merged: #3577, merge commit
8fcc9f9cd— "Merge pull request #3577 fromSystemorph/fix/3554-module-floor-must-be-satisfiable".
Why
main-cdwas the wrong home, measuredThis issue proposed the check on
main-cd's bake. Core declares no floors at all, so that gatewould have had an empty denominator for ever — a gate that cannot fail, which is the thing this
repo's rules forbid most explicitly:
The floors live in the satellites, and every satellite packs its modules through
.github/workflows/node-repo-module-pack.yml(on: workflow_call, line 233). That is where thedeclaration passes through, so that is where it is now checked.
The check, and why it has no skip-trapdoor
pack(node-repo-module-pack.yml, the step at the diff's+1385) runs.github/scripts/check-module-platform-floor.py --package … --floor … --platform-version …unconditionally — no
if:on a secret, nothingcontinue-on-error. The denominator is read inpreparefrom the platform image's ownMESHWEAVER_PLATFORM_VERSION(onedocker image inspect,no registry trip), and an image that cannot say what version it is fails RED there. Where no
platform image is pinned,
packfalls back to the platform checkout's$(PlatformVersion)and isRED when neither can be established — so "the floor could not be checked" is never
indistinguishable from "checked and fine".
It compares a stronger question than this issue asked for
Not "does some tag somewhere satisfy this" but "does the platform this bundle is built against
satisfy it". The script's own rationale gives the argument: a consumer only adopts a bundle whose
framework identity matches its own platform, so a floor above the build platform is unsatisfiable by
construction — there is no deployment that both runs a satisfying platform and would accept this
bundle. That subsumes the ACR-listing formulation and needs no credential.
Pinned against drift
test/Memex.Portal.Shared.Test/ModulePlatformFloorScriptParityTest.cs— a 25-case[Theory]driving the script and
ModulePlatformFloorover the same inputs, including this issue's wholemeasured table (
3.0.0-rc4/rc8/rc9/3.0.0againstci.1/ci.7989/ci.999999999), plusAnRcFloorIsUnreachableFromTheCiLine_ForEveryBuildNumber. The CI gate and the runtime landing gatetherefore agree by construction rather than by two lists staying in step.
The class this issue is about is closed: a floor no platform build can meet can no longer be
packed.
Measured 2026-09-07T12:25Z on
origin/mainfbf9e9be1: merge commit viagit log --merges --grep, workflow diff viagit diff 8fcc9f9cd^1 8fcc9f9cd, the zero-declarationsweep by parsing
.content.minMeshVersionout of every*.jsonin the tree (denominator 605).docs: a floor is an AUTHORING claim — reconcile the ordering rule with …