Repository navigation
Move Cosmos + Snowflake (source, tests, releases) out of the platform: ship released binaries, run their tests only when they change #1752
Description
Maintainer-directed:
"can we move cosmos and snowflake to plugins?" … "or just create the releases and the binaries?" … "and move the tests over" … "then we don't run tests" … "and tests are run only when we touch the plugins" … "or update the platform"
Why this works (and why an earlier objection of mine does not)
Both backends are ALREADY free of compiled coupling. Each is a boot-pack module whose
MeshNodeProviderAttribute registers a keyed IStorageAdapterFactory; the Cosmos one states it
outright — "everything a deployment needs for Graph:Storage:Type = Cosmos to resolve, with no
compiled reference from the portal". Nothing in src/ or memex/ references either project;
only their own test projects do.
I had objected that a lagging build would be declined by the identity gate. That is wrong, and
the distinction matters: there are two gates, not one.
- Content bundles (baked NodeType assemblies) are declined on STRICT framework identity —
PrebuiltAssemblySeeder.DeclineReason(FrameworkMvid)inPluginBundleClient. - Compiled modules gate on a
MinMeshVersionFLOOR (PluginCatalogContent.MinMeshVersion,
PluginManifest.MinMeshVersion), with MVID diagnostic-only (feat(modules): bundle build + Store funnel + auto-update — #1664 Slices B+C #1673).
So a module binary built against platform N−1 loads into N by design, provided the floor is
satisfied. The residual risk is ordinary .NET binary compatibility, not an identity decline.
The shape
- Source + tests move to a plugin repo.
- That repo builds and publishes versioned releases: the binaries, with a
MinMeshVersion
floor. (Releases can be modelled asReleasenodes carrying the assembly link — see A module must always carry a resolvable link to its shipped DLL (per framework identity, per arch) — routable via the registry, so nothing compiles #1751.) - The platform image consumes a pinned released artifact. No build-time source coupling, and
no cross-repo build cycle: the platform does not trigger or wait for the plugin build. - Platform CI stops running the Cosmos/Snowflake suites. They run in the plugin repo, when
that code changes. This is the actual prize —MeshWeaver.Hosting.Cosmos.Testneeds the Cosmos
emulator via Testcontainers, which is expensive to stand up on every platform commit for code
that changes rarely. - If the platform changes the storage contract, the plugin is updated (or the platform re-releases
it) — the maintainer's "or update the platform".
🚨 The one thing that must NOT be lost
These are bootstrap-tier modules: the mesh cannot read itself without a storage backend, so
they must be PRESENT IN THE IMAGE and can never be Store-installed at runtime (the Store's catalog
lives behind the storage you would be installing). Delivery is "pinned released binary baked into
the image", not "installable package". A deployment that could delist its storage backend would be
unbootable, not degraded.
And because the platform would no longer compile or test them, a framework change that breaks
IStorageAdapterFactory / IPartitionStorageProvider becomes invisible until a portal fails to
boot. That is the same class as the in-mesh-source hazard AGENTS.md warns about, so moving the
suites out must be paired with one cheap contract gate on the platform side — load the pinned
binaries and assert the factories resolve. Not the full emulator suite; just proof the seam still
binds. Without it this trade buys CI minutes and pays for them in an outage.
Related: #1727 (Cosmos+Snowflake reached the closure lane), Systemorph/MeshWeaver.Plugins#1140 (Cosmos capability matrix —
🚨 no access control in its query path; not production-ready regardless of where it lives),
#1751 (modules carry a resolvable DLL link), #1673 (module bundles, floor-based gate).
Activity
rbuergi commentedon Aug 17, 2026
Worked this end to end. The gate is shipped (#1759). The move is blocked, and I did not tag to get around it. Also: the CI prize is far smaller than it looks, and I think that changes the decision.
1. The measured CI delta — it is 0.4 seconds
Measured on green main run
32031030133(2026-08-17), not estimated.Hosting.Cosmos.TestHosting.Snowflake.TestOf Cosmos's 25.2 s, 16 s is pulling and creating the emulator container and ~7 s is readiness — the assertions are ~2 s. And Snowflake's emulator classes skip wholesale in CI (no
SNOWFLAKE_CONNECTION/LOCALSTACK_AUTH_TOKEN), so 74% of that suite has never run here.What a move would genuinely free, honestly stated:
Microsoft.Azure.CosmosandSnowflake.Dataare each referenced by exactly one project, andApache.Arrow/AWSSDK.Core/AWSSDK.S3/Google.Cloud.Storage.V1appear in no csproj at all (transitive from Snowflake.Data only). ~13 MB of nupkg restore, ~25 MB of publish output, plus 4 assemblies off the critical-pathBuild solution (once)job.So the prize is code volume and dependency closure. It is not CI minutes on the critical path, and I would not spend an outage-class risk to buy 0.4 s.
2. Repo choice: MeshWeaver.Plugins — if the move happens
Not because it is comfortable, but because three things it needs already exist there and nowhere else:
src/MeshWeaver.Approvals+.Testbuild-c Release -warnaserrorand rundotnet test --logger trx. (SocialMedia has the module job but no tests at all; Reinsurance/Manufacturing are content-only — Reinsurance'slegacy/*.csprojexplicitly does not compile.)ubuntu-latest), so the Cosmos emulator fixture ports as-is — it already uses the genericTestcontainersbuilder with the arm64vnext-previewimage, notTestcontainers.CosmosDb.repository_dispatch: [meshweaver-framework-released]is already wired. That is literally your "or update the platform" trigger, and a new repo would have to re-create it.What it costs, stated plainly: it is a repo whose README says "no rebuild, no NuGet — the node is the manifest", so compiled infrastructure sits against its grain. It needs a top-level
test/tree, new.gitignorerules, and central package management (it has none — Approvals hard-pins versions inline). And theMW_PLATFORM_REFpin, today covering one platform project for Approvals, would cover eight for Cosmos.Test — the staleness hazard its own CI header documents at length grows with that surface.A dedicated repo is cleaner in isolation and worse in total: it re-creates preflight, ACR creds, the dispatch wiring and a second pin to forget, and it does not solve §3.
3. The blocker: there is no channel that can deliver a bootstrap-tier module
The chain is short and every link is checked:
MeshModulesPublish.targetshas no non-ProjectReference lane. Build-time is ProjectReference only; the runtime writer (ModuleLandingService) is the Store lane, which bootstrap tier forbids by definition. A third lane would have to be built.NUGET_PAT+ a version ⇒ a release ⇒ held; GitHub Packages is worse than held — the org is at its billing limit, which is whyplugin-publish.ymlis called withdry-run: true(a push returnspermission_denied/403 and reds main), and the source was deliberately removed fromnuget.config; a GitHub Release asset needs a tag ⇒ held; cross-runactions/download-artifactwas explicitly ripped out ofmain-cd.ymlin Platform content bake publishes under a DIFFERENT framework identity than the runtime image resolves — no pod can ever adopt it #1725 and artifacts expire — not "pinned released".Worth knowing regardless of the hold: the existing satellite module lane also stops at a run artifact today — "runtime distribution ... task #74 wires satellite-built bundles into that funnel" — and it is the Store lane anyway.
So: every clean mechanism needs a version tag or a NuGet publish. Per the hold I stopped rather than tagged. Un-blocking it is a scope call: either lift the hold for a satellite
v*tag, or fund the third lane (my pick if it goes ahead: aMeshWeaver.*package on nuget.org, consumed via aPackageReference+ a package-sourced row in the closure lane —nuget.config's own comment pre-authorizes re-adding a Systemorph source with a matching packageSourceMapping entry, and nuget.org needs no read credential, which GitHub Packages does).4. What shipped instead — #1759
The contract gate this issue asks for, which turns out to be needed today, not only after a move. #1727 landed the closure lane ~20 minutes before this issue was filed, and nothing verifies the folder it lays out is loadable — nor would anything, since neither backend has a compiled reference in the tree.
Two things that look like coverage are not: the compiler proves the source binds but says nothing about the publish layout (where these two are fragile — their drivers exist nowhere else in the image, so a prune that took one away lands exactly the failure the targets file warns about), and the emulator suites green-SKIP, so they can pass by not running.
StorageModuleLayoutTestwalks the seam a portal walks:ResolveModulePathlands insidemodules/<Name>/rather than the app-folder fallback, the private driver survived the prune and loads,InstallAssembliesfolds theMeshNodeProviderAttribute, and the keyedIStorageAdapterFactorycomes from that DLL. No emulator, no network, ~40 ms.I verified it can fail, twice: deleting
Microsoft.Azure.Cosmos.Client.dllreds both Cosmos tests (the seam one surfacing the realFileNotFoundExceptionfrom JITtingCreate); deletingmodules/reds all four. Host publish is unchanged — 6 thin + 14 closure, 99 files, both storage folders intact.And it is exactly the gate a move would need: point it at a pinned released binary instead of the in-tree build and it answers the question a moved backend raises. Whatever is decided in §3, this had to exist first.
5. Deferred
The move itself — source, tests, and the release lane — pending the §3 scope call. Nothing in #1759 presumes either outcome. Systemorph/MeshWeaver.Plugins#1140 (Cosmos has no access control in its query path) is untouched and still gates production readiness independently.
rbuergi commentedon Aug 21, 2026
Closing as superseded — not because the move happened, but because the thing this called "the actual prize" was measured and priced by someone else after this was filed, and it is 31 seconds.
The prize, measured
.github/scripts/shard-assign.shweights every test project in seconds, heaviest first:30 s + 1 s, LPT-balanced across shards. And the "expensive to stand up" premise was investigated and fixed head-on — the same file records the decision, taken after this issue:
So the suite this proposes to stop running costs half a minute and, unlike a moved suite, actually exercises the backend against the platform it ships with.
# EVERY test project runs — there is no exclusion list.is a deliberate policy, not an oversight.What DID land, and it is the part that mattered
#1759 shipped the contract gate this issue said must be paired with the move —
test/Memex.Portal.Shared.Test/StorageModuleLayoutTest.cs. With no emulator, no network, in milliseconds, it asserts thatResolveModulePathfinds each backend undermodules/, that its private driver survived the closure prune, thatInstallAssembliesfolds the assembly'sMeshNodeProviderAttribute, and that the keyedIStorageAdapterFactorywhichGraph:Storage:Typeresolves comes from that DLL — from a project holding no compiled reference to either backend. Its own header names it as the prerequisite for a satellite move:That gate is the durable value here, and it stands whether or not the source ever moves. (While auditing I also found the closure lane was deleting both backends' native payloads —
libsf_mini_core.*,libMono.Unix.*, Cosmos'ServiceInterop— soGraph:Storage:Type = Snowflakeon a shipped image would have failed at its first native call; fixed in #1972, with the layout now asserted by that same test.)Where the rest of the shape stands
MeshWeaver.Plugins/src/(33 dirs) nor in its 16-entry module matrixMinMeshVersionfloorMeshModulesPublish.targets:104-105names the in-tree csprojsSteps 1–3 are still a legitimate ownership preference, and they remain available: the bits are already module-only (no host
ProjectReferenceanywhere; only the two test csprojs and the closure lane reference them), so the move is a relocation, not a decoupling.But they can never be Store packages — these are bootstrap tier, as
StorageModuleLayoutTest.cs:15-18puts it: "the mesh cannot read itself without a storage backend, so they can never be Store-installed (the Store's catalog lives behind the very storage you would be installing)". Delivery would stay "pinned released binary baked into the image". And the cost this issue itself names is unchanged: a framework change that breaksIStorageAdapterFactorybecomes invisible until a portal fails to boot — traded now for 31 seconds rather than for the minutes the argument assumed.Reopen if the ownership half is still wanted on its own merits. The CI-minutes argument is spent.
Measured 2026-08-21 against
origin/main@ 3d85424 while sweeping the #1660 programme.