A Reader Never Deletes A Shared Build

The rule. NodeAssemblyLoadContext.LoadNodeAssembly loads the path it is given. It does not decide whether the build belongs to the running generation, and it never deletes a loadable file. The one delete left in it is for a BadImageFormatException, where the bytes provably cannot load.

The generation is decided by identity, before the path reaches the loader:

Owner What decides the generation
FileSystemAssemblyStore every file is named v{version}-{FrameworkTag}-{hash}.dll, and the store serves only its own tag (NamedBuild, TryGetBuildPath)
the NodeType record CompiledFrameworkVersion gates the bind (HasUsableBuild)
the local disk cache TryGetLatestCachedDllPath checks the framework time itself (Check 2) before it hands out a path
a release its hash includes the framework (NodeTypeRelease)

A write time is not a generation. Under the compatibility policy, a build of the same framework tag made by an earlier image is exactly what may be bound.

What happened

The loader compared the file's write time with MeshWeaver.Compiler.Pipeline.dll's and deleted the file "for regeneration" when it was older. A new image's framework DLL is newer than every build compiled before the image existed, so on the ReadWriteMany /data the first replica of each roll deleted the shared builds the old replicas were serving. Measured on memex.systemorph.com (2026-10-08, governed Logs actions under Ops/Actions/logs-6161-…):

Why it produced three symptoms

  1. The recompile wave and the version climb (a residue found while verifying #6161 / #5555). Each deleted build is a store miss. The miss triggers a recompile on the owner and a new record version. That is why the Store/Plugin version rose in clusters at pod boots, and why every roll paid a compile per locally built type.
  2. "Stuck AGAIN" for about five minutes at boot (the same verification). While Store/Plugin recompiled, every installed package root overlaid. The overlay self-heal fired on the record's still-usable verdict, re-activated against bytes that were gone, and overlaid again until the new build landed. The heal budget then logged the non-convergence.
  3. The refetch that could not land on the shared volume (#6052). Store/Plugin is built locally on purpose. The bundle's source fingerprint (3d0df09d…, module 1.20.4) is not the live source's (dbbc1754…, 1.21), so the owner declines the adoption (#2813). The record therefore names a local MVID, which no shipped bundle carries. When the delete made those bytes missing, the registry refetch (#6262) correctly refused to bind a different build. The only remaining remedy was a recompile. The refetch is right for the case it was built for (bytes missing for a shipped build), but here the missing bytes were self-inflicted.

The test

AReaderNeverDeletesASharedBuildTest (MeshWeaver.Compiler.Pipeline.Test):

Negative control: against the pre-fix loader, both stale arms answer null and delete the file, while the control still loads.

What this does not establish

Related: An Unloadable Build Is Never A Silent Default, Stale State Until Recycle.