Repository navigation
Fall back to the image-shipped copy when no landed module generation loads (#3735) - #3748
Conversation
…loads (#3735) memex.systemorph.com, 2026-09-08 (3.0.0-ci.8079): the module set pinned a two-week-old store generation of MeshWeaver.Blazor.Views, an assembly the image also ships. The link probe refused it (a type the platform has since removed), the entry held no previous generation, and the image's copy was never tried — the boot union had substituted the store entry in place of the baseline entry on the DLL's existence alone, before anything was measured. The module "contributed nothing" and shadowed the copy that loads by construction: every skinned control rendered its FallbackHtml. Rule R1, "run the newest thing that loads", where loads includes installs: the fallback order is newest → previous landed generation → the image-shipped copy → absent (reported), each step measured by the same probe and load. EffectiveModule.BaselineEntry carries the displaced baseline entry; the portal resolves it through ResolveModulePath without the module root onto ModuleInstallCandidate.ImageBaseline; InstallModules tries it last and registers a FallbackModule with RunsImageBaseline, whose running generation is the "@image" stand-in — so the adoption record, FallbackGenerations, Describe, the unloadable marker and the GC work unchanged, and the health check names it. A generation that loaded and then threw at install cannot be replaced (one assembly per simple name) and is now named as such on stderr rather than left as a bare absence. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
There was a problem hiding this comment.
🟡 Changes recommended
One newly introduced stderr diagnostic claims “no previous generation” even in cases where a previous generation existed but was tried and failed, which can mislead operators during incident diagnosis.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
This PR fixes a production module-activation failure mode where a store-landed generation could displace an image-shipped baseline module entry before loadability is measured, causing the module to become effectively absent when the landed generation is refused (no previous generation available), even though the image includes a working copy.
Changes:
- Preserve the displaced
Modules:Assembliesbaseline entry during effective-module computation and thread it through to the loader as a last-resort install candidate. - Extend
MeshBuilder.InstallModulesfallback order to: newest → previous landed generation → image-shipped baseline → incompatible (with combined reasons recorded). - Add targeted regression tests and update docs/What's New to reflect the updated fallback policy and reporting (
@imagestand-in generation).
File summaries
| File | Description |
|---|---|
| test/MeshWeaver.Compiler.Pipeline.Test/ConfiguredModuleActivationTest.cs | Adds regression + negative-control coverage for image-baseline fallback and reporting. |
| src/MeshWeaver.PluginCatalog/PendingModuleActivations.cs | Updates fallback description text to include image-baseline fallbacks. |
| src/MeshWeaver.PluginCatalog/ModuleSet.cs | Updates module-set description to explain @image as a fallback generation marker. |
| src/MeshWeaver.PluginCatalog/ModuleActivation.cs | Carries the displaced baseline entry via EffectiveModule.BaselineEntry during union computation. |
| src/MeshWeaver.Mesh.Contract/ModuleInstallCandidate.cs | Adds ImageBaseline candidate path and FallbackModule support for RunsImageBaseline / @image. |
| src/MeshWeaver.Mesh.Contract/MeshBuilder.cs | Implements the new fallback step and records combined refusal reasons. |
| src/MeshWeaver.Documentation/Data/WhatsNew/2026-09-08-a-module-the-image-ships-runs-again-when-its-installed-version-cannot-load.md | Adds a Fix entry describing the behavioral change and operator-visible reporting. |
| src/MeshWeaver.Documentation/Data/Architecture/ModuleSetConvergence.md | Documents @image in running-generation tracking and GC implications. |
| src/MeshWeaver.Documentation/Data/Architecture/Modules.md | Documents the image-baseline fallback step and how it is surfaced. |
| src/MeshWeaver.Documentation/Data/Architecture/ModulePlatformLinkGate.md | Documents the extended fallback order and the @image reporting shape. |
| src/MeshWeaver.Documentation/Data/Architecture/ModuleAdoptionPolicy.md | Updates the policy definition of “loads” and the full fallback chain including image baseline. |
| memex/Memex.Portal.Shared/MemexConfiguration.cs | Plumbs EffectiveModule.BaselineEntry into ModuleInstallCandidate.ImageBaseline using the real baseline resolver. |
Review details
- Files reviewed: 12/12 changed files
- Comments generated: 1
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| Console.Error.WriteLine( | ||
| $"[MeshWeaver.Mesh.FallbackModule] '{newest.Refused.Name}': no previous " | ||
| + "generation and no image-shipped baseline loads here — no generation of this " | ||
| + "module loads on this platform, so it is absent."); |
Summary
Live production defect, memex.systemorph.com 2026-09-08 (image
3.0.0-ci.8079): the module set pinned a two-week-old STORE generation ofMeshWeaver.Blazor.Views— an assembly the IMAGE also ships. The boot's link probe refused it correctly (requires 'MeshWeaver.Graph.AnchoredComment (MeshWeaver.Graph)', a type the platform has since removed), the entry held no previous generation, and the image's own copy was never tried. The module "contributed nothing", which is worse than absent: it SHADOWED the baseline that loads by construction, and every skinnedStackControlon the portal rendered throughFallbackHtml(ClientA saw the control'sToString).Root cause.
ModuleActivationBoot.ComputeEffectiveModuleEntriessubstitutes a landed store entry IN PLACE of the same-namedModules:Assembliesbaseline entry on the DLL's existence alone, before anything is measured — and the displaced baseline entry was dropped right there. Whether the landed generation LOADS is measured later, by the link probe insideMeshBuilder.InstallModules; by then the image copy was gone from the list, so a refusal had nothing to fall back to. The #3649 fallback (previous generation) did not help: this entry predates #3649 and holds noPreviousDirectory. The link probe was NOT the gap: the generation was landed on 2026-08-25, before #3552 existed, and the type it needs was removed from the platform afterwards — no landing-time probe can see a future removal; the boot-time probe refused it as designed, and the message in the log is the probe's own (FromLinkRefusal,RefusedBeforeLoad = true).Fix — rule R1, "run the newest thing that loads", where loads includes installs: the fallback order is now newest → previous landed generation → the image-shipped copy → absent (reported), every step measured by the same probe and load.
EffectiveModule.BaselineEntry(init) carries the displaced baseline entry; the portal (MemexConfiguration) resolves it throughMeshBuilder.ResolveModulePathWITHOUT the module root — the image's own closure, never the landed tree — ontoModuleInstallCandidate.ImageBaseline(init; a plain path, no pin — the image closure is immutable).MeshBuilder.InstallModulestries it after the previous generation; on success registers aFallbackModulewithRunsImageBaseline(init) —PreviousGenerationanswersFallbackModule.ImageBaselineGeneration(@image), so the adoption record (RunningGenerationsOf),ModuleSetIndex.FallbackGenerations,ModuleSetStore.Describe, the#3650unloadable marker and the GC reference set all work unchanged (@imageis not a directory name by construction and reclaims nothing).ModuleActivationReport.Describe, rendered by the PluginsPendingModuleActivationHealthCheck) names it:X runs the image-shipped baseline; v1.3.0 (gen B) landed but does not load here: …— present, not pending, not quarantined.Modules:Requiredclassifies itPresent.IncompatibleModule.Errorwhen nothing loads. A generation that LOADED and then threw at install (A landed module built against a newer platform aborted every new pod on the registry: optional-parameter ctor change is binary-breaking and raised no minMeshVersion floor #2234's shape) cannot be replaced by the image copy (one assembly per simple name in the default load context) — that constraint is now NAMED on stderr with the baseline path, never left as an absence that reads like "the image had nothing".Tests (
ConfiguredModuleActivationTest,#3735section — real landing, real boot composition, real loader, image copy resolved through the REAL baseline resolver)FallbackModule.RunsImageBaseline, noIncompatibleModule, marker written for the refused head@image, the sentence), not pending, not quarantined; without the loader's record the same state reads as pending (control)@image;FallbackGenerations/Describename it; the GC keeps the headPresentLocal:
Memex.Portal.Shared,MeshWeaver.Compiler.Pipeline.Test,Memex.Portal.Shared.Test,MeshWeaver.Documentation.Testalldotnet build -c Release -warnaserror→0 Error(s); test runs 32/32, 50/50 and the doc guards green with fresh.trx.Docs
ModuleAdoptionPolicy§ What "loads" means (install is part of loading; the fallback order; the shape it cannot reach; the 2026-09-08 measurement),ModulePlatformLinkGate(the fallback section, the states table, what proves it),Modules,ModuleSetConvergence. What's NewCategory: Fix.Not in scope, deliberately: the held newer generation is core #3732 (the crm publication republishing upstream modules), and deleting the stale generation is the operator's stopgap being done separately.
Pairs-with: none — additive only; no public type or member leaves
src/(new init properties onModuleInstallCandidate,FallbackModule,EffectiveModule; positional constructors unchanged).Implementers: none — no member added to a public interface or abstract class.
Mirror-sync: none — no i18n catalog change (boot-log and health-check text is operator-facing diagnostics, not viewer chrome).
🤖 Generated with Claude Code