Repository navigation
Registry module bundles cannot carry native runtime assets: both closure derivations drop runtimeTargets, the bundle format has no native section, and the container evaluator refuses a native-only PackageReference (measured: MeshWeaver.AppleMessages needs libe_sqlite3) #4126
Description
Measured 2026-09-12 while moving MeshWeaver.AppleMessages (the iMessage module, MeshWeaver.Plugins) off a system sqlite3 shell-out onto Microsoft.Data.Sqlite — the fleet rule being that a module brings its own dependencies (Doc/WhatsNew/2026-09-01-a-module-brings-its-own-dependencies-again), and the maintainer's addition (2026-09-12, verbatim: "must also be module … and then mounted"): the engine the module brings must travel INSIDE its sealed bundle, native runtime assets included, so a runner that mounts the sealed publication from the platform volume gets a working module with no install.
It cannot, today. The runtime half exists; the bake half does not. Every reference is origin/main at c7262a5.
What works: the runtime resolver (#1728)
src/MeshWeaver.Mesh.Contract/ModuleNativeAssets.cs:131 probes modules/<Name>/runtimes/<rid>/native/<lib> (then the flat folder) from AssemblyLoadContext.Default.ResolvingUnmanagedDll, keyed on the REQUESTING assembly's folder; MeshBuilder.InstallModules subscribes it. The landing service writes a bundle into modules/<generation>/ (src/MeshWeaver.PluginCatalog/ModuleLandingService.cs:907), whose parent is modules, so a runtimes/ tree laid out there WOULD resolve. The in-image closure lane already keeps that tree: memex/MeshModulesPublish.targets:465 (_ClosureRidNative, kept since #1728).
What strips it: the registry bundle, on both build paths
- SDK path —
src/MeshWeaver.Plugin.Build/DepsClosure.cs:149takes onlyruntimeentries ending in.dll;:152DETECTSruntimeTargetsand:184turns them into a WARNING ('{name}' declares native runtimeTargets the bundle does not carry — if the module needs them at runtime, they must ship another way). Nothing ships them another way. - Container path —
tools/MeshWeaver.PluginTester/ContainerReferenceSet.cs:307reads only theruntimesection of the image's deps.json;runtimeTargets/nativeare never read. SoPrivateClosure.Derive(tools/MeshWeaver.PluginTester/PrivateClosure.cs:113) reachesSQLitePCLRaw.lib.e_sqlite3through the bundle's dependency edge, finds no assembly for it and files it underMissing;ProjectBuild.cs:978logs that as a warning, andEmitPrivateClosure(ProjectBuild.cs:1392) copies rides FLAT beside the module — there is noruntimes/<rid>/native/layout to copy into. - The bundle format has no native section —
src/MeshWeaver.Plugin.Packaging/NuGetPackageWriter.cs:70(meshweaver/modules/<flat file>, "Module files are flat") and:79(meshweaver/moduleassets/forwwwroot/**only);ModulePackCommand.cs:667writes exactly those two plus content. The landing side mirrors it (ModuleLandingService.cs:907flat assemblies,:911wwwroot). - Worse: the container evaluator REFUSES a native-only PackageReference.
ContainerReferenceSet.cs:360Resolvematches a package by the ASSEMBLY the deps.json says it contributes (:364falls back to<id>.dll); a package that contributes only natives contributes no assembly →Supplied=false→ProjectBuild.cs:951unresolved→ RED:1 PackageReference(s) the container does not supply: SQLitePCLRaw.lib.e_sqlite3— although the image carrieslibe_sqlite3.soin/app(MeshWeaver.Hosting.Sqlite is platform-shipped). So abuild: containermodule cannot even DECLARE the CVE-patched engine (SQLitePCLRaw.lib.e_sqlite33.53.3, the GHSA-2m69-gcr7-jv3q pin every SDK build needs under-warnaserror).
No module in MeshWeaver.Plugins carries a native asset in its registry bundle today — the one native family in the fleet (SkiaSharp for MeshWeaver.Markdown.Export) is supplied by the PORTAL HOST (Memex.Portal.Gui.csproj, SkiaSharp.NativeAssets.Linux.NoDependencies), which is the same "the host happens to have it" shape the 2026-09-01 WhatsNew called a defect for managed assemblies.
What the AppleMessages bundle does meanwhile (MeshWeaver.Plugins, fix/imessage-in-process-sqlite)
The module declares Microsoft.Data.Sqlite + SQLitePCLRaw.core/provider.e_sqlite3 3.0.2 exactly as MeshWeaver.Hosting.Sqlite does, and the native pin SQLitePCLRaw.lib.e_sqlite3 3.53.3 under Condition="'$(MSBuildRuntimeType)' != ''" — set under every MSBuild build (tests, SDK path), empty in the container evaluator, so the container path is not reddened by (4). A consumer instance therefore receives the MANAGED layer in the bundle (Microsoft.Data.Sqlite.dll, SQLitePCLRaw.core.dll, SQLitePCLRaw.provider.e_sqlite3.dll, SQLitePCLRaw.batteries_v2.dll) and NO engine; the HOST supplies libe_sqlite3 (LocalMesh and the portal image both ship MeshWeaver.Hosting.Sqlite; the gate tester mw-plugin-test does not — the module says "unsupported host" on Linux before it ever opens the database, so nothing loads it there). That condition and that dependence on the host go away when this lands.
Ask
DepsClosureandPrivateClosure/ContainerReferenceSetcarryruntimeTargets(native, per RID) alongsideruntimeassemblies — loadable natives only, the*.a/*.libexclusionMeshModulesPublish.targets:465already applies.- The bundle gains a native section (
meshweaver/modulenatives/runtimes/<rid>/native/<file>or equivalent, manifest-declared likestaticAssets), the packer writes it, the landing service lays it out under the module folder, andModuleNativeAssetsthen finds it with no further change. ContainerReferenceSet.Resolvetreats a package whose only contribution is native (present in/appflat, or asruntimeTargetsin the deps.json) as SUPPLIED, so abuild: containermodule can declare its CVE pin.- The pack log's "declares native runtimeTargets the bundle does not carry" warning becomes a refusal once the bundle can carry them, so a native dependency can never again be dropped silently.
Refs: MeshWeaver.Plugins#1736 (the runner move that surfaced the sqlite3 dependency), Memex#323, #1728, #1043, MeshWeaver.Plugins#1710 (the iMessage module).
🤖 Filed from Claude Code.
https://claude.ai/code/session_01DqTDZe4Skkn81GuGkwMrXS
Activity
rbuergi commentedon Sep 12, 2026
The module-side change that measured this, with the conditional native pin and the consumer-receipt statement: Systemorph/MeshWeaver.Plugins#1757.
rbuergi commentedon Sep 13, 2026
Adjudicated 2026-09-13 against
main(cdb2878bb) — stays OPEN; every claim holds, and it is not a small fixRead-only code audit of the eight claims in the body, each re-located on today's
main:modules/<Name>/runtimes/<rid>/native/then flat, keyed on the requesting assembly;MeshBuilder.InstallModulessubscribes itsrc/MeshWeaver.Mesh.Contract/ModuleNativeAssets.cs:48,60,131,133;MeshBuilder.cs:195modules/<generation>/+wwwroot; no native sectionsrc/MeshWeaver.PluginCatalog/ModuleLandingService.cs:902,908,912— nonative/runtimesstring in the fileruntimes/*/native/**(minus*.a/*.lib)memex/MeshModulesPublish.targets:465-471DepsClosuretakes.dllruntimeentries only, detectsruntimeTargets, emits a WARNINGsrc/MeshWeaver.Plugin.Build/DepsClosure.cs:148-151, 152, 182-184ContainerReferenceSetreads onlyruntime;Resolvemarks a native-only packageSupplied=falsetools/MeshWeaver.PluginTester/ContainerReferenceSet.cs:307, 360-370PrivateClosurefiles it underMissing;ProjectBuildreds an unresolved PackageReference, warns on Missing, copies rides FLAT:979)PrivateClosure.cs:113;ProjectBuild.cs:951-958, 979, 1392meshweaver/modules/,meshweaver/moduleassets/) and the packer writes only thoseModulePackCommand.cslives undersrc/MeshWeaver.Plugin.Build/, notPlugin.Packaging)NuGetPackageWriter.cs:64-79;ModulePackCommand.cs:667-681runtimes/<rid>/nativeinto a.module.nupkgsrc/MeshWeaver.Plugin.*,PluginCatalog,PluginTester,.github/scripts,memex/*.targetsfor `modulenativesOne more that the body did not state and that makes the gap two-sided: the CONSUMERS filter to flat entries —
ServedModuleBytes.cs:204-206(meshweaver/modules/with no/in the remainder) andPublishedBundleCatalogue.cs:519-521— so an entry undermeshweaver/modules/runtimes/…written by a hand-built bundle would be silently dropped, not laid out. The bundle section has to be a declared one, as the ask says.Commits touching these areas since the issue was written (
c7262a5e8..cdb2878bb): only #4094 (adbb963eb, registry-key handling inInstanceAutoRegistrationService/RegistryUpdateReconciler) — no effect on any claim.Verdict
Open, as a feature gap, not as a triage residue. It spans four components that must land together (
DepsClosure/PrivateClosure/ContainerReferenceSetcarryingruntimeTargets; a manifest-declared native section in the bundle + packer; the landing layout;ContainerReferenceSet.Resolveaccepting a native-only package) plus the warning-to-refusal flip, and the packer/landing halves live on two sides of a registry — so it is not small and not in scope for a sweep. The module-side workaround shipped: Plugins#1757 merged 2026-09-12T20:48Z with the conditional native pin and the host-suppliedlibe_sqlite3dependence the body describes.Conserved: the gap is now stated on
Doc/Architecture/Modules→ Native assets (the section that says modules can ship natives now says which lane that is true for), in the same PR as this sweep's other records.Read-only, code at
origin/maincdb2878bb. No build, no mesh, no registry.PR: #4152
rbuergi commentedon Sep 13, 2026
Build ticket — parked on Roland's word ("never mind build tickets for now"). Today's state was already recorded above at 07:44Z (all eight claims hold on
main, not a small fix); nothing added and no work done.rbuergi commentedon Sep 13, 2026
Staged, and stage 1 is built: #4239
The prior pass's verdict ("a four-component feature, not small") holds. Re-derived, it is four
stages, and the reason is not size — each depends on what the one before establishes, and stage 4
would break a working module if landed early. Stage 1 (derive + carry) is #4239.
🚨 One thing this issue did not state, and it is the reason the measured case was invisible
The body says
DepsClosuredetectsruntimeTargetsand turns them into a warning. It does — for apackage that ALSO contributes a managed assembly. The loop short-circuited first:
SQLitePCLRaw.lib.e_sqlite3contributes no managed assembly at all. It wascontinued before thenative branch ran, so the warning could not fire for it. Measured on unmodified
main(d1556d7a37)with a probe written against main's own API:
Zero warnings, zero files. So this was not "warned about and dropped" for the case it was filed on —
it was dropped in total silence.
Stage 1 — #4239 (derive + carry)
DepsClosure.Result.Natives—NativeAsset(RelativePath, Rid, Package)fromassetType: "native"entries; the
RuntimeFiles.Count == 0short-circuit is gone.meshweaver/modulenatives/, path PRESERVED (the path ISthe contract —
ModuleNativeAssetsprobes<moduleDir>/runtimes/<rid>/native/<lib>). It cannot bemeshweaver/modules/(every consumer filters to entries with no/, so a native there is silentlyskipped, and
ModulePublish.Validatedeclines the flat spelling) and it should not bemeshweaver/moduleassets/(static WEB assets — conflating a loadable binary with a served filemakes every future rule about one apply to the other).
RID-specific MANAGED asset (
assetType: "runtime"), and.a/.lib.What each remaining stage IS, and what it depends on
ModuleLandingService.LandModule/ShelveModule/LandCoretake the natives and write them undermodules/<generation>/;PluginBundleClient.LandFromBundlepasses them.ValidateAssetPathalready accepts the shape and the write loop already handles trees — so it is signatures plusGenerationIdOf, which MUST fold them into the hash or two bundles differing only in natives collide on one generation directory.ModuleBundleSource.CollectVersionandServedModuleBytes.FromSealedcollect flat files +wwwroot/**only, so a registry re-serving a shelved module drops the section.ContainerReferenceSet.cs:307reads onlyruntime;:360-370Resolvemarks a native-only packageSupplied=false, so abuild: containermodule cannot DECLARE the CVE pin (ProjectBuild.cs:951-958reds it). PlusPrivateClosurecarrying natives andProjectBuild.EmitPrivateClosurelaying them out.🚨 Until stage 2 lands, a native rides the bundle and is not written to disk — carried, declared,
ignored. That is deliberate and is why #4239 ships no What's New entry: the user-visible capability
belongs with stage 2, when a module author can actually ship one.
MeshWeaver.AppleMessageskeeps itsconditional pin and its host-supplied
libe_sqlite3(Plugins#1757) until then.Neither
node-repo-module-pack.yml:2046-2051(--withrefuses a path component) nor:2349-2358(the jq provenance gate over
module.assemblies) is disturbed by stage 1, because the natives are aseparate manifest section rather than flat closure entries.
Conserved on
Doc/Architecture/Modules→ "The registry bundle: derived and CARRIED (#4126, stage 1)",replacing the gap statement the 2026-09-13 sweep wrote there.
Keeping this open for stages 2–5.
🤖 Generated with Claude Code
fix: the native layout is EXACT, ordinal, and enforced at the reader …
test(#4126): pin the native's FULL entry path, not merely that it is …
rbuergi commentedon Sep 14, 2026
The FORMAT question is answered, measured rather than reasoned — #4318
The one half of this issue that could be settled without touching production code: does the new
meshweaver/modulenatives/section (stage 1, #4239) need a FORMAT VERSION?🚨 A bundle is read by portals running older images — the registry serves one set of bytes to
every installation — so "the format gained a section" is only safe if an older reader ignores
it. Nothing asserted that, and it is not reachable from a producer-side test: the property belongs to
code that is not in this build.
Verdict: ADDITIVE. No format version, no manifest
schemaVersion, no dual-write.Two independent facts, now pinned by
ABundleCarryingNativesIsReadUnchangedByAPreNativesReaderrather than argued:
meshweaver/modules/with no/in the remainder (ServedModuleBytes,PublishedBundleCatalogue); the asset consumer filtersmeshweaver/moduleassets/. A native is under neither prefix, so an older reader does not skip it, mis-file it or fail on it — it never enumerates it at allBundleReaderdeserializes withJsonSerializerDefaults.Web, whoseUnmappedMemberHandlingisSkip. The test deserializes the real packed manifest intoPreNativesManifest/PreNativesModule— the shape as it stood before #4126, spelled out locally so adding a property to the realManifestcan never quietly update it — and every pre-existing field survives🚨 What WOULD have needed a version, and is exactly why the section is its own: under
meshweaver/modules/an older reader's flat filter silently drops the natives while the producerbelieves it shipped them; under
meshweaver/moduleassets/an older reader lays a loadable binaryinto
wwwrootand serves it over HTTP.The controls, each falsified
Four, and none decorative — all four driven RED:
Assert.Singleon the entry — "it ignored it" and "there was nothing to ignore" are otherwise the same greennativeAssetsDisallowcontrol — it exists to prove the JSON really carries the unmapped member, so theSkipassertion cannot pass over a document with nothing new in itmeshweaver/modules/ModuleNativeAssetscomposes its probe from exactly four segments and has no recursive walkIncidental, and worth keeping
The manifest entry is written with a UTF-8 BOM, and
Utf8JsonReaderskips one over a streambut not over a span — reading the entry's bytes directly fails with
'0xEF' is an invalid start of a value.BundleReaderuses the stream overload and so must anything else. The first draft of thistest hit it; it is on
Doc/Architecture/Modulesnow.The other half of the format question: the container derivation
Can both closure derivations keep
runtimeTargets?DepsClosure(SDK) does, since stage 1.ContainerReferenceSet(container) does not — and the reason is a policy line, not a data limit:it reads only the
runtimesection of the image's deps.json, andruntimeTargetssits in the samedocument; the refusal of a native-only
PackageReferenceisResolvematching a package by theassembly it contributes, so a package contributing only natives answers
Supplied=false. Nothingabout deps.json prevents stage 3.
Staying open
Stages 2–5 as recorded on this thread and on
Doc/Architecture/Modules— landing, registry re-serve,the container lane, warning → refusal. Until stage 2 a native still rides the bundle and is not
written to disk: carried, declared, ignored.
MeshWeaver.AppleMessageskeeps its conditional pin andits host-supplied
libe_sqlite3(Plugins#1757).Read-only against production code: this change set adds a test and a doc section and alters no
src/behaviour.🤖 Generated with Claude Code
rbuergi commentedon Sep 14, 2026
Landed —
c0fc2abb60b8a16ecddbc3e99c8fefc009df4c8e(#4318)The format half of this issue is settled: the
meshweaver/modulenatives/section added by stage 1is ADDITIVE. No format version, no manifest
schemaVersion, no dual-write. The detail is in thecomment above; this is the landing note.
What it changes for stages 2–5: nothing has to be co-ordinated with the fleet's running images.
A bundle carrying natives is read unchanged by a portal on an older image — pinned now by
ABundleCarryingNativesIsReadUnchangedByAPreNativesReader, with four anti-vacuity controls eachdriven red, including the one this review round added (the native written at a third, undeclared
prefix — "somewhere outside the other two" is not the same statement as "at the declared path").
So stage 2 (landing) can be built and shipped on its own schedule without a compatibility window,
which was the open question it would otherwise have had to answer first.
Staying open for stages 2–5, as recorded on this thread and on
Doc/Architecture/Modules:landing, registry re-serve, the container lane, and warning → refusal. Until stage 2 a native still
rides the bundle and is not written to disk — carried, declared, ignored — and
MeshWeaver.AppleMessageskeeps its conditional pin and its host-suppliedlibe_sqlite3(Plugins#1757).
🤖 Generated with Claude Code
chore: merge main — keep BOTH the native-provenance check and the per…