The executed registry-install fixture
RegistryPackages.Install is the Store's registry lane: list the registry's catalog, fetch the
package's files, install its nodes — and then land its compiled module from the same registry's
bundle index. That last branch is the one #1585 found production had never taken; #1590 could
only pin the report's wording, because nothing in this repository could execute the lane.
Nothing could execute it for a mundane reason: no project compiled it.
Store/Publishing/Source/RegistryPackages.cs is an in-mesh NodeType source, and
src/MeshWeaver.PluginCatalog.Test compiled Store/Licensing, Store/Core, Store/Coupon,
Store/Install and two files of Store/Publishing — not this one. Adding
Store/Publishing/Source/*.cs to that project (the whole L2 library, which is how
Store/Publishing.json describes itself) compiles clean under -warnaserror, and that is the
entire enabling step.
The seam is IHttpClientFactory — there was never a missing hook
Install constructs its collaborators itself:
var bundles = new PluginBundleClient(hub, url, token); // RegistryPackages.cs:319
var feed = new RegistryPackageSource(hub, url, token) { Bundles = bundles }; // RegistryPackages.cs:320
so an IPackageSource cannot be handed in. It does not need to be. Both types take their
HttpClient from the host's IHttpClientFactory — PluginBundleClient.cs:80,
RegistryPackageSource.cs:53 — falling back to a shared client when none is registered. Registering
a stub factory on the test mesh therefore puts an in-memory serving registry under the real
lane, with no production change at all:
| route | what the fixture answers |
|---|---|
GET /api/plugins |
PluginRegistryPayloads.List([manifest]) — the catalog |
POST /api/plugins/files |
{ files: [...] } — the package's files |
GET /api/plugins/bundles/index.json |
the bundle index |
GET /api/plugins/bundles/{id}/{version} |
a real NuGetPackageWriter bundle carrying real assembly bytes |
Anything else is a 404, on purpose: a route the lane starts calling has to be staged deliberately rather than silently succeeding.
🚨 module on the index entry is load-bearing. AdoptModule selects the bundle with
!string.IsNullOrWhiteSpace(b.Module) (PluginBundleClient.cs:373-375). An index entry with
plugin/version/url and no module is the #1585 miss, not a landing — which is exactly
what BundleAdoptionMissTest stages, and why copying its Index(...) builder verbatim produces a
test that can never see a landing.
Why it could not be an in-mesh case
Store/Publishing's Tests area is the natural home and cannot host this. Substituting an
IHttpClientFactory is a host-service act, and an in-mesh Tests area has no ConfigureMesh —
the single facility Hosting/InMeshTestMigration.md records as missing. The live-case shape
(public static IObservable<Unit> …(IMessageHub hub)) gives a test the hub's services; it does not
let it change what the host registered before the hub existed.
What makes it able to fail
RegistryPackages.LandModule swallows every fault and answers 0 (RegistryPackages.cs:373-379) —
correctly, since the content half has already landed. So a lane that is absent is
indistinguishable from one that succeeded, except by two things: the landed count, and the set the
wave proposed. The test asserts both, and both were watched to fail:
control applied to RegistryPackages.cs |
result |
|---|---|
the LandModule call replaced by new RegistryInstallOutcome(content, manifest.Module, 0) — the exact pre-#1585 shape |
RED: "AdoptModule must consult the bundle index; the lane called: GET /api/plugins · POST /api/plugins/files" |
only the ProposeModuleSet call dropped, LandModule intact |
RED: "Expected value not to be <null> because the landing wave must propose the set it just created" — the adopt half stayed green |
| neither (as shipped) | 2/2 green |
The second control matters as much as the first: it proves the proposal assertion is not carried by the landing assertion, so neither half is decorative.
The failure message names the routes the lane actually called, not a bare 0 — so the next
occurrence says whether the adopt never started, or started and was refused.
The negative case is part of the fixture
ARegistryInstall_OfAPackageDeclaringNoModule_LandsNoModule_AndProposesNothing installs a package
with Module = null through the same fixture and asserts 0 landed and no proposal. Without it a
ModuleFilesLanded that was always 1 would read identically to a working lane.
Relationship to #1794 — two seams, not one harness
#1794 asks for a seam that holds a node between its write committing and its owner materialising
it. That is a mesh write-timing stand-in, on the storage/materialisation path, for the
"it worked the second time" family. This fixture is an HTTP response stand-in, on
IHttpClientFactory, for the registry lane. They share no layer, no injection point and no failure
mode, and a single harness bundling both would be two unrelated stand-ins wearing one name.
More usefully: this fixture demonstrates that #1592 never needed #1794. The seam already existed
in the platform; what was missing was a project that compiled the code under test. #1794 remains
genuinely open — nothing here reaches it.