Skip to content

Reusable lanes land a module bundle's DLLs but not its meshweaver/moduleassets/wwwroot — a browser-driven mesh 404s every _content/<Module>/ asset #3514

Description

@rbuergi

The reusable lanes land a module bundle's DLLs, not its browser assets — a browser-driven mesh 404s every _content/<Module>/… the module ships

node-repo-gate.yml (~L909) and node-repo-publish-bake.yml (~L949) compose an external module with

cp -R "$u"/meshweaver/modules/. "$EXT/$name/"
MODULE_ARGS="$MODULE_ARGS --module /ext/$name/$name.dll"

A module bundle carries its static web assets separately, under meshweaver/moduleassets/wwwroot/… (NuGetPackageWriter.ModuleAssetFolder; ModulePackCommand lays a Razor project's wwwroot/ there, dependencies namespaced under wwwroot/_content/<Dep>/). The portal serves a landed module's assets from modules/<Name>/wwwroot (MeshModuleStaticAssetExtensions) and logs only at Debug when there is nothing there — so a module landed this way loads fine and then 404s every asset in the browser. The runtime consumer (ServedModuleBytes) handles the prefix correctly; production is unaffected (both portals serve …/_content/MeshWeaver.Markdown.Collaboration/Components/collaborativeMarkdownView.js with 200).

Where it costs today. For the lanes' own use the gap is latent: the bake is mesh-free and the gate mesh runs no browser. It bites the moment a browser renders against a mesh whose modules were landed by this pattern. MeshWeaver.Education's e2e/mesh/fetch-upstream.sh (a local copy of this landing block) is exactly that: since MeshWeaver.Plugins#1268 moved the collaboration views INTO MeshWeaver.Markdown.Collaboration (2026-09-04 12:48Z), every Education mesh run has thrown

Unhandled exception rendering component: Failed to fetch dynamically imported module:
http://localhost:8323/_content/MeshWeaver.Markdown.Collaboration/Components/collaborativeMarkdownView.js
  at MeshWeaver.Blazor.Components.CollaborativeMarkdownView.OnAfterRenderAsync

and every exercise page rendered its brief and then a bare Code node card where the workbench belongs (Playwright screenshot in run 34070110529's e2e-install-report) — 4 × 180 s per course. essentials.module.nupkg ships the JS; the landing never copied it. The last green Education run (09-04 12:16Z) predates #1268 by thirty minutes.

Fix pattern (Education #274, fetch-upstream.sh), for the two lane sites:

if [ -d "$u/meshweaver/moduleassets/wwwroot" ]; then
  mkdir -p "$EXT/$name/wwwroot"
  cp -R "$u"/meshweaver/moduleassets/wwwroot/. "$EXT/$name/wwwroot/"
  # fail CLOSED: shipped vs landed asset counts must match
fi

Simulated against the run's own set: Markdown.Collaboration lands 270 asset(s) (its JS/CSS plus the namespaced dependency assets), the EntityViews/Graph view packs their scoped CSS. Low urgency for the lanes as they stand; filed so the next browser-driven consumer of these blocks (or the next satellite that copies them) does not re-derive it. Related: #2384 (the same class for the OpenStreetMap module's .razor.js), Plugins#808 (scoped CSS shipped in neither image nor module).

Activity

  1. added 2 commits that reference this issue on Sep 7, 2026
    6a45d59
    2fe5484
  2. rbuergi commented on Sep 7, 2026

    @rbuergi
    ContributorAuthor

    Fixed in MeshWeaver#3535 (merged as d2a564c7c278dcb73f8c8231bb784d695a3bf470).

    Which half was wrong: the lane. The premise holds — a module legitimately carries static web
    assets and every hop of the platform's lane is deliberate (ModulePackCommand →
    meshweaver/moduleassets/ → ModuleLandingService writes them module-relative beside the entry
    assembly → MeshModuleStaticAssetExtensions serves them at _content/<Name>/…). Nothing is
    producing assets a module should not have; the two lane sites were simply landing half the bundle.
    The recorded "modules cannot carry native or static assets" constraint is a different claim wearing
    the same words: native payloads genuinely cannot ride (#1728), static web assets can, and
    that half was corrected twice already (most recently #2221).

    Two refinements over the pattern in the description, both from reading the packer's contract
    rather than today's output:

    • the copy is meshweaver/moduleassets/. → <module dir>/, module-relative, not
      moduleassets/wwwroot/. → <module dir>/wwwroot/. wwwroot/ is the only root the packer emits
      today, but ModuleAssetEntryPathFor/ValidateAssetPath are specified on a relative path, so
      the wwwroot-specific form is correct now and silently lossy the day a second root appears;
    • the check prints a denominator, not just a count: 30 composed, 6 of them carrying static web assets, 294 asset file(s) landed. 24 of 30 real modules legitimately ship none, so an uncounted
      zero looked exactly like the healthy case — which is what let this ride.

    Denominator, on real bundles. The extracted lane step run against all 30 module bundles of
    MeshWeaver.Plugins run
    34086016940: 6 carry
    assets
    (Blazor.Chat 283, Blazor.OpenStreetMap 7, Blazor.Analysis/.EntityViews/.Graph/
    .Radzen 1 each), 294 asset files shipped, 294 landed byte-identical. Pre-fix: 0 of 294, at
    exit code 0.

    Both falsification arms, watched. Real bundle → the extracted lane step → a real
    WebApplication with AddMeshModuleStaticAssets()/UseMeshModuleStaticAssets() over the landed
    tree → an actual fetch:

    request with the fix pre-fix
    _content/MeshWeaver.Markdown.Collaboration/Components/collaborativeMarkdownView.js — this issue's literal stack trace 200, text/javascript, 12,391 B, byte-identical 404
    _content/MeshWeaver.Blazor.Chat/chatResizer.js 200, 4,273 B, byte-identical 404
    _content/MeshWeaver.Blazor.Chat/ChatMessageList.razor.js 200, 22,739 B, byte-identical 404
    the same URL with Accept-Encoding: br 200 + Content-Encoding: br, byte-identical to the landed .br sibling 404
    …/does-not-exist.js — negative control 404 404

    Plus, in CI: .github/scripts/test-module-asset-landing.py extracts BOTH lanes' step by id: and
    executes it, reads the verdict off the landed bytes' sha256, and carries two arms that mutate the
    real step (pre-fix body ⇒ the positive case reds; cp removed ⇒ the lane's own fail-closed check
    must fire). Removing the id: reds it too.

    Two corrections to the description's premise:

    1. Education feat(mcp): Space instance-sync MCP tool #274 is OPEN, not landed. Its branch does carry the moduleassets copy, but the
      PR is a different change (ci: fetch the upstream once, gate the bake in parallel…) and is
      mergeable_state: blocked; MeshWeaver.Education's main still has the unfixed block, so its
      mesh e2e is still 404ing. That half stays theirs.
    2. There is a third copy, not two: MeshWeaver.Plugins/scripts/mesh-test.py hands the mesh
      --module …/meshweaver/modules/<Name>.dll, so the assets it needs at
      …/meshweaver/modules/wwwroot sit at …/meshweaver/moduleassets/wwwroot. Same shape, latent
      only because nothing points a browser at it — filed as Systemorph/MeshWeaver.Plugins#1440.

    One nuance worth pinning, because a fix written to the description's wording would miss the lane:
    the portal serves a landed module's assets from the directory of the assembly it actually
    loaded
    , not from modules/<Name>/wwwroot — that path is only the legacy fallback for a module
    riding the app closure (ModuleWwwrootPath, #2509/#2562). For the lanes the directory is
    /ext/<Name>/, which is why the copy has to target the module directory rather than a
    modules/-shaped path.

    Work product: Doc/Architecture/ModuleStaticAssets — the four hops, the landing invariant, every
    site that lands a module and what each one owes, why the failure is invisible to review, CI and
    production, and both measurements above.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions