Skip to content

Fall back to the image-shipped copy when no landed module generation loads (#3735) - #3748

Merged
meshweaver-cloud[bot] merged 1 commit into
mainfrom
fix/module-install-fallback-image-baseline
Sep 8, 2026
Merged

meshweaver-cloud[bot] merged 1 commit into
mainfrom
fix/module-install-fallback-image-baseline

Conversation

@rbuergi

@rbuergi rbuergi commented Sep 8, 2026 •

Copy link
Copy Markdown
Contributor

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 of MeshWeaver.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 skinned StackControl on the portal rendered through FallbackHtml (ClientA saw the control's ToString).

Root cause. ModuleActivationBoot.ComputeEffectiveModuleEntries substitutes a landed store entry IN PLACE of the same-named Modules:Assemblies baseline 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 inside MeshBuilder.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 no PreviousDirectory. 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 through MeshBuilder.ResolveModulePath WITHOUT the module root — the image's own closure, never the landed tree — onto ModuleInstallCandidate.ImageBaseline (init; a plain path, no pin — the image closure is immutable).
  • MeshBuilder.InstallModules tries it after the previous generation; on success registers a FallbackModule with RunsImageBaseline (init) — PreviousGeneration answers FallbackModule.ImageBaselineGeneration (@image), so the adoption record (RunningGenerationsOf), ModuleSetIndex.FallbackGenerations, ModuleSetStore.Describe, the #3650 unloadable marker and the GC reference set all work unchanged (@image is not a directory name by construction and reclaims nothing).
  • The health check (ModuleActivationReport.Describe, rendered by the Plugins PendingModuleActivationHealthCheck) 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:Required classifies it Present.
  • Every failed step's reason lands on the IncompatibleModule.Error when 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, #3735 section — real landing, real boot composition, real loader, image copy resolved through the REAL baseline resolver)

  • the only landed generation is refused, the image ships the module ⇒ the image copy runs, FallbackModule.RunsImageBaseline, no IncompatibleModule, marker written for the refused head
  • the activation report names the row (@image, the sentence), not pending, not quarantined; without the loader's record the same state reads as pending (control)
  • the adoption records @image; FallbackGenerations / Describe name it; the GC keeps the head
  • a required module on the image baseline is Present
  • a previous landed generation is tried BEFORE the image copy
  • negative control on the branch: an image copy that does not load either ⇒ incompatible, both reasons on the record, nothing installed
  • an image entry listed but not shipped ⇒ incompatible, nothing claimed (today's behaviour preserved)
  • a generation that loaded and then threw at install ⇒ not replaced by the image copy, incompatible with the install exception on the record

Local: Memex.Portal.Shared, MeshWeaver.Compiler.Pipeline.Test, Memex.Portal.Shared.Test, MeshWeaver.Documentation.Test all dotnet 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 New Category: 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 on ModuleInstallCandidate, 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

…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>
Copilot AI lite review requested due to automatic review settings September 8, 2026 18:03
@meshweaver-cloud
meshweaver-cloud Bot enabled auto-merge September 8, 2026 18:03

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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:Assemblies baseline entry during effective-module computation and thread it through to the loader as a last-resort install candidate.
  • Extend MeshBuilder.InstallModules fallback 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 (@image stand-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.

Comment on lines +335 to +338
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.");
@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Test Results (shard 0)

254 tests  ±0   254 ✅ ±0   2m 15s ⏱️ -15s
  1 suites ±0     0 💤 ±0 
  1 files   ±0     0 ❌ ±0 

Results for commit f18eaee. ± Comparison against base commit e103981.

@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Test Results (shard 1)

496 tests  ±0   496 ✅ ±0   44s ⏱️ +5s
  1 suites ±0     0 💤 ±0 
  1 files   ±0     0 ❌ ±0 

Results for commit f18eaee. ± Comparison against base commit e103981.

@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Test Results (shard 4)

1 669 tests  ±0   1 475 ✅ ±0   2m 7s ⏱️ ±0s
    4 suites ±0     194 💤 ±0 
    4 files   ±0       0 ❌ ±0 

Results for commit f18eaee. ± Comparison against base commit e103981.

@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Test Results (shard 3)

1 087 tests  +8   1 087 ✅ +8   1m 33s ⏱️ -8s
    2 suites ±0       0 💤 ±0 
    2 files   ±0       0 ❌ ±0 

Results for commit f18eaee. ± Comparison against base commit e103981.

@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Test Results (shard 5)

    5 files  ±0      5 suites  ±0   3m 17s ⏱️ +15s
1 764 tests ±0  1 764 ✅ ±0  0 💤 ±0  0 ❌ ±0 
1 765 runs  ±0  1 765 ✅ ±0  0 💤 ±0  0 ❌ ±0 

Results for commit f18eaee. ± Comparison against base commit e103981.

@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Test Results (shard 2)

1 244 tests  ±0   1 244 ✅ ±0   4m 21s ⏱️ -2s
    3 suites ±0       0 💤 ±0 
    3 files   ±0       0 ❌ ±0 

Results for commit f18eaee. ± Comparison against base commit e103981.

@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Test Results

   16 files  ±0     16 suites  ±0   14m 20s ⏱️ -5s
6 514 tests +8  6 320 ✅ +8  194 💤 ±0  0 ❌ ±0 
6 515 runs  +8  6 321 ✅ +8  194 💤 ±0  0 ❌ ±0 

Results for commit f18eaee. ± Comparison against base commit e103981.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants