Departed Platform Assemblies
An assembly that leaves the platform for a module breaks every OTHER module that binds it — at LOAD time, invisibly to every compile gate.
Both halves compile. The platform builds without it. The depending module builds against it — it is right there in the same repository. Nothing anywhere asks whether the assembly it moved into is in the closure of every module that binds it, and the failure surfaces as a module that loads and contributes nothing.
1. The reproducer that established it
MeshWeaver.Manufacturing's scheduled run, red every night 2026-09-04 → 09-09:
[MeshWeaver.Mesh.IncompatibleModule] Module 'MeshWeaver.AI' did not install against this platform
build and is CONTRIBUTING NOTHING. It requires
'MeshWeaver.ContentCollections.Indexing.ChunkPosition
(MeshWeaver.ContentCollections.Indexing — this deployment carries no such platform assembly)'
[MeshWeaver.Mesh.IncompatibleModule] Module 'MeshWeaver.Markdown.Collaboration' … requires
'MeshWeaver.Blazor.BlazorView`2 (MeshWeaver.Blazor — …no such platform assembly)'
Two modules, two departed assemblies, one shape — which is what makes it a rule rather than an incident. The cascade from there is worth knowing, because none of it names the cause:
| step | what is seen |
|---|---|
| AI contributes nothing | its BuiltInAgentProvider / BuiltInSkillProvider never run |
so Agent / Skill resolve to nothing |
Install of 'Store' failed: NodeType(s) not registered: Skill |
Store/Plugin is declared BY Store |
every package rooted on it fails: "NodeType 'Store/Plugin' is not registered" |
| 36 installs refused | GATE FAILED — the bake declares 103 NodeType(s), NONE of which this run installed |
A reader starting at the bottom finds a bake-consumption failure; three levels up is a module that
could not load. The first three explanations tried on that issue — missing Agent/Skill packages,
a stale catalog, an identity skew — were all wrong, and each was reached by stopping at the first
line that fit.
2. Why the compile gates cannot see it
The cross-repo pair gate covers a public type leaving src/, and #2941 ("The indexing pipeline,
the map control and Cornerstone leave the content surface") presumably satisfied it — nothing
failed to compile, because the consumer moved with the assembly. What no gate covers is the
runtime closure: whether the bundle that now owns the assembly is loaded wherever a module
binding it is loaded.
This is the tenth break shape in AGENTS.md's list in all but name, and the one whose evidence arrives latest — a green build, a green publish, and a module that silently does nothing.
3. "Those are the platform" is one answer PER HOST
DepsClosure decides what a bundle carries with this rule:
stopping at (and never bundling)
MeshWeaver.*nodes: those are the platform, they ship in the consumer's/appby construction
The exclusion list is MeshWeaver.Plugins' src/platform-shipped.txt. Both rest on "the platform"
being one thing. It is not — the two hosts that load these bundles are built from different
repositories:
| host | built from | carries Blazor / Indexing? |
|---|---|---|
memex-portal-ai |
Plugins — Memex.Portal.Gui/.Distributed/.Monolith, alongside src/MeshWeaver.Blazor* and src/MeshWeaver.ContentCollections.Indexing.* |
yes |
mw-plugin-test |
core — tools/MeshWeaver.PluginTester, whose whole closure is seven core projects, none reaching Blazor |
no — neither assembly exists in core at all |
and platform-shipped.txt's own comments record it being measured twice against the portal
(images ci.7755 / ci.7794, and the three Memex.Portal.* closures). It is right for the host it
was measured against and wrong for the other, and because they are built from different
repositories no re-measurement of a single image reconciles them.
🚨 This is determinable statically, from the two reference sets — the investigation that found it first proposed extracting both images, which was unnecessary.
4. The trap in the obvious fix
Deleting the two roster lines makes the tester work and reintroduces on the portal exactly what the roster exists to prevent. Its own comment:
omitting them is what makes the Indexing bundle carry a second copy of an assembly
/appalready has, which is the same-identity duplicate this whole file exists to prevent
— i.e. #3732's two-producer defect, whose
live heldReason reads "the loader keeps whichever it sees first, so the other's NodeTypes are
declined at adoption".
So it is a decision between two real costs, and it belongs to whoever owns the module split:
- A per-host roster — pack per consumer.
module-owned-platform.shalready takes the host as an argument (<src> [<platform-app-dir>]); what is missing is that the pack resolves ONE answer for hosts that do not agree. - Carry it and let
/appwin — which is precisely the tradeDepsClosure's diamond paragraph already argues for non-MeshWeaver.*packages: "the default load context resolves/app's copy first whenever the platform carries one … and the module's copy takes over the moment the platform stops carrying it… shedding a dependency from the platform (the very point of the module split) would silently break every landed module that relied on it." That is this failure, one assembly-name convention over. - Make the tester's closure match the portal's — which changes what the gate gates.
5. What to check when it happens again
IncompatibleModule … CONTRIBUTING NOTHINGnaming an assembly, in the boot or install log — that IS the diagnosis; everything downstream of it is cascade.- Whether the named assembly exists in the repository the LOADING host is built from.
- Whether
platform-shipped.txtnames it — if so, the bundle omitted it on purpose. - A green push run proves nothing here:
pushpins the image to the wave the modules came from, so only a release-follow poll exercises the pairing.
Related: #3221 (module closure — the sibling question), #3732 (the two-producer half), #3768, and #3583 (the same asymmetry from the source side).