The Platform-Shipped Witness

A module bundle carries its entry assembly plus a private closure. Which MeshWeaver.* siblings may ride that closure has exactly one correct answer, and it is a property of the platform host the bundle is packed against, not of the repository the module lives in:

A MeshWeaver.* sibling rides a bundle if and only if the platform host does not already ship it.

Both directions of that rule are load-bearing, and each has cost a fleet-wide incident:

What used to decide it, and why it drifted

Until #3732 the split came from a hand-maintained file in each node repository, src/platform-shipped.txt: every MeshWeaver.* project directory in src/ except the names that file lists. .github/scripts/module-owned-platform.sh computed the complement, and the same set reached three consumers — the container build's closure copy, the packer's --own-platform, and the bundle inspection's assertion.

A declaration is not a measurement, and this one went stale in both directions inside one week:

when what moved what the list did what it cost
Plugins#1023 MeshWeaver.AI left the image the line was correctly removed
MeshWeaver#3335 five assemblies were in /app and unlisted corrected by hand after a manual re-measurement every bundle referencing one carried a duplicate
MeshWeaver#3335 MeshWeaver.Maps had left the image the line lingered a name that reached a mesh from nowhere at all
Plugins#1515 four modules returned under Modules:Assemblies the list did not move see the measurement below

Measured on MeshWeaver.Plugins main, 2026-09-08 — every packed module's transitive in-repo ProjectReference closure, minus src/platform-shipped.txt, against the seeded-module rows in both portal hosts' csproj:

module bundles the repository packs 37
bundles riding at least one MeshWeaver.* sibling 18 (34 riding slots)
bundles riding a sibling the image also ships 14 (27 slots)
the names MeshWeaver.Markdown.Collaboration (14 bundles), MeshWeaver.AI (13)

Every one of those 27 slots is a second build of an assembly a portal already has, and none of them was visible to the witness that was supposed to prevent exactly that.

The three witnesses

The reason the list could not simply be corrected once more is that an image ships an assembly three ways, and the previous reading knew about one of them.

# Where What puts it there
1 <app>/<Name>.dll the application closure — a ProjectReference from a portal host
2 <app>/meshweaver-surface.manifest the host's own record of its MeshWeaver.* compile references (MeshWeaverSurfaceManifest.targets)
3 <app>/modules/<Name>/<Name>.dll the seeded-module lane — @(MeshModuleClosure) in memex/MeshModulesPublish.targets, which MeshBuilder.ResolveModulePath probes before the app directory

🚨 A MeshModuleClosure row touches neither (1) nor (2) — the portal hosts' own csproj comments say so in as many words. So a witness that read /app alone answered "not shipped" for every seeded module, which is precisely the 27 slots above.

PlatformShippedAssemblies.Read(appDirectory) (in MeshWeaver.Compiler) reads all three and returns each name with the provenance and the evidence path that answered, so a strip decision in a pack log names a file rather than an opinion.

Readings 1 and 3 are the LOADER's own two probe locations

This is not a parallel definition of "the platform ships it" — it is the runtime's. MeshBuilder.ResolveModulePath answers where does this deployment's copy of module X live by probing <app>/modules/<name>/<name>.dll first and <app>/<name>.dll second, which are readings (3) and (1) in the resolver's own order. So the packer that strips a riding copy and the loader that would otherwise have had to choose between two of them mean the same thing by the same test — and it is the very call #3735/#3748's image-baseline fallback resolves through.

PlatformShippedAssembliesTest.TheWitnessProbesTheSamePlacesTheRuntimeResolverDoes pins the correspondence: a resolver that grows a third probe location, or reorders the two, fails there and names the reading the witness is missing. Reading (2) has no runtime counterpart by design — it is a compile-time fact about the host, and a manifest name is in the app closure by construction.

🚨 #3748 recovers from this condition; it does not remove it — and it masks the symptom

The image-baseline fallback (#3735, merged as #3748) makes a portal survive the condition: when no landed generation of a module loads, it falls back to the copy the image ships, and the module runs. That is the right behaviour and it is why memex.systemorph.com's controls render again.

It does not make shipping two builds correct, and three things follow:

It refuses rather than answering nothing

A directory with no surface manifest and no MeshWeaver.* assembly at its root is not a platform application directory, and the reader returns a problem for it. Answering "this host ships nothing" would turn every caller's strip into a silent no-op that logs exactly like a clean measurement — the gate-that-cannot-fail shape CI forbids. A portal /app carries 200+ MeshWeaver.* assemblies and the tester image 88; zero is a wrong directory, never an answer.

Where the measurement is applied

Two places, deliberately, and they answer different questions.

The classificationmodule-owned-platform.sh <src> [<platform-app>] — decides what the deps walk treats as module-owned, which matters beyond the MeshWeaver.* names themselves: walking into a platform-shipped project also drags that project's private package dependencies into the bundle. With an app directory it measures; without one it falls back to the declared list and says so on stderr, because the fallback lane is structurally different (see the blind spot below), not because the input happened to be missing. When both are available the image decides and the list decides nothing — the disagreement is printed on stderr as a DRIFT line naming the exact entry to add or drop, so the file converges and can eventually be deleted. Plain stderr, not ::warning::: the script runs three times per matrix entry (111 times on Plugins' 37), and annotating a diagnostic about a file that decides nothing would bury the annotations that do decide something.

The enforcementmodule-pack --platform-app <dir> — is a second, independent reading over the bundle's actual file list. Everything upstream composes the closure from declarations (--with from the lane's classification, --deps-closure from a graph walk seeded by --own-platform), and those are shell-string sets matched with grep -qF; this one is a typed measurement taken at the moment the bytes are written. Every MeshWeaver.* file in the closure is measured against the host and dropped when the host already has it, with one printed line each naming the evidence.

🚨 On a lane where the classification is also measured, the drop count should be ZERO — and that makes it a control, not decoration. Both readings answer the same question from the same image, so a non-zero platform-shipped: … dropped line in a pack log means the two disagree, and the disagreement is worth reading. It also means a caller that drives module-pack directly — a new lane, a hand invocation, a satellite that has not adopted the shared workflow — gets the protection without having to know about the classification at all. What it deliberately does NOT do is refuse: shipping a correct bundle with a loud line beats reddening a delivery lane over a disagreement whose resolution is already in hand.

The lane passes the raw image copy ($RUNNER_TEMP/platform-refs), never the supersede-pruned reference set: superseded-image-assemblies drops an assembly from the compile set precisely because the image still carries it, so measuring the pruned copy would answer "not shipped" for a name that is right there in /app.

What the witness does NOT judge

The entry assembly. A module the image seeds under modules/<Name>/ is meant to be superseded by a landed bundle of itself — a usable persisted entry overrides the same-named baseline in place — so dropping the entry would delete the module from its own bundle. The host-versus-entry case is a different defect with a different remedy, and it already has a gate: BakeHost.ShippedByHostProblem refuses a module composed with --module that the platform host also carries, at the bake, where both provenances are in one hand.

Third-party assemblies. A diamond rides by design and versions independently; it does not collapse to one identity. Judging it would hold every bundle in the fleet for a property nobody claimed. See Module Closure Accounting for why "the image has it" is never on its own a reason to leave a package assembly out.

What stops riding, and why that is safe

The measurement removes MeshWeaver.AI from 13 bundles and MeshWeaver.Markdown.Collaboration from 14. Those rides were insurance for the standalone installModule-Owned Siblings Ride §3: with the sibling excluded, a bundle installed on its own would land without an assembly nothing supplies, and throw ReflectionTypeLoadException on first touch.

That argument was always conditional on the image, and the image moved. Plugins#1515 seeds and activates both assemblies in every portal host, so the copy is there before any dependent module loads — and because MeshWeaver.* collapses to one identity, an already-loaded copy is what a later Assembly.LoadFrom binds to. The ride is therefore redundant and harmful today, which is exactly what the measurement says.

🚨 The failure this trades against is a host that SEEDS an assembly under modules/<Name>/ but never activates it — a seed nobody asks for is, in Plugins#1515's own words, "not a third category; it is nothing". Modules:Assemblies is deployment configuration, not part of the image, so two deployments of one image can differ here. If that shape ever appears, the symptom is a ReflectionTypeLoadException naming the seeded assembly, and the fix is to activate it — not to re-add the ride, which would put the two-builds condition back.

The self-correcting property is the point: the day the image stops shipping one of these, the measurement says so and the ride returns on the next pack, with nobody editing a list.

Which repositories this changes

src/platform-shipped.txt exists in exactly one repository of the fleet — MeshWeaver.Plugins, where it names 17 assemblies. In the other five node repos it is absent, which the script reads (and still reads) as "the platform ships nothing": every MeshWeaver.* project in their src/ is classified module-owned and rides. That is right today only because those repos own few such projects (MeshWeaver.SocialMedia has 2; the rest have none) — it was never a measurement, and it becomes one for all six with no per-repo file to write.

The blind spot, named

A lane that pins no platform image has nothing to measure. Core's own CD packs the four bundles its bake composes with platform-ref (a source build), not platform-image-digest, so there is no /app in that job at all and the classification falls back to the declared list. That fallback is stated on stderr on every run rather than passing silently, and the enforcement step is simply not armed there.

That is a real gap and not a trapdoor: it is one structurally different lane, named in the log, not an if: that asks whether an input happens to be present. Closing it means giving that job the promoted portal image the same run already resolves.

Reconnecting…
The server was updated. Reloading the page to pick up the latest version.