Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,88 @@ completely silent: nothing anywhere named a NodeType that is broken and will not
> token that happened to match the importing deployment's live inputs would suppress precisely the
> retry it exists to grant.

### 🚨 An ADOPTED build must say whether it was ever checked against the source

Adoption — taking a prebuilt assembly from a bundle instead of compiling — is what makes installs
and restarts cheap. It is also, until #2813, the one path that could make a NodeType assert something
nobody established.

`PrebuiltAssemblySeeder.Seed` writes CROSS-HUB, so it cannot read the owner's live source snapshot
(#1834); it asks instead, via `RequestedSourceStampAt`, and the owner answers by stamping
`CompiledSources = CurrentSourceVersions`. **That stamp is what makes `IsDirty` false**, which is
what `InstallReleaseRequestWatcher`'s "satisfied by the existing current build" branch requires and
what makes an adoption stick.

And it is also how an adopted build lied. The two signals an operator is taught to trust —
`CompilationStatus.Ok` and `CompiledSources == CurrentSourceVersions` — both read clean **whether or
not the bytes have anything to do with the live source**, because the adoption writes the second one
itself. The staleness detector was never broken; it was answering a question the adoption had already
answered for it. On 2026-08-30 a GitSync `update` pulled new source, adopted a prebuilt built from
older source, reported `Succeeded`, and the stale code destroyed four client documents' bodies — one
unrecoverable. Only forcing a compile moved the MVID.

#### The check, and where it can be made

The producer records a **content** fingerprint of the sources the bytes were built from
(`PartitionSourceFingerprint`). It has to be content, not versions: `CurrentSourceVersions` is
`{path → LastModified.UtcTicks}`, mesh-LOCAL modification times the producer cannot know and does not
have (the bake writes zeros), so a fingerprint over ticks would never match and every adoption would
be refused.

The comparison happens on the **owner**, in `ApplyAdoptedSourceStamp` — one pure function shared by
all three writers that can fulfil the request, so turning assert into check fixes all three at once:

| bundle stamp | matches the live one | outcome |
|---|---|---|
| yes | yes | adopt; `BuildProvenance = AdoptedVerified` |
| yes | **no** | 🚨 **refuse** — no stamp, flip `Pending` to compile the live source; `AdoptionRefused` |
| no (legacy), or the owner's own not published yet | — | adopt, **keep the stamp**; `AdoptedUnverified` |

> 🚨 **The legacy row keeps the stamp deliberately.** Withholding it makes every legacy-bundle type
> `IsDirty` on arrival, the `!IsDirty` absorb branch stops firing, and every install recompiles
> everything — the 43 activations / 13.5 s of boot the prebuilt lane exists to remove. On a
> `Modules:RequirePrebuilt` mesh a local compile is refused by design, so not stamping would **park
> every legacy-bundle type**: the outage that refusing unproven bundles was rejected to avoid,
> arriving through a different door. A bundle with no fingerprint is of **unknown** provenance, not
> **proven stale**, and those deserve different answers. The requirement is VISIBILITY, not refusal.

`BuildProvenance` is operational (stripped on export, preserved from the live node on import) and is
mirrored onto the compile-state satellite, so a control plane can read it through
`GetMeshNodeStream(path)`.

#### Whether the refused bytes keep serving is CONDITIONAL

`Seed` has already stamped the adopted build's assembly coordinates by the time the owner judges it,
so a refusal that changes nothing else leaves proven-stale code executing. Both answers are wrong in
one direction, and the fork is decided at the point of refusal rather than by assuming how a flag is
set on a mesh nobody can see:

- **this mesh can compile** → **clear** the coordinates. The `Pending` flip has already dispatched a
fresh compile, so the type is unserviceable for seconds — and "marked and still serving" is exactly
the state that lets an armed control-plane node fire pre-fix code unattended.
- **`Modules:RequirePrebuilt`** → **keep** them, and log `Critical` naming the type. The local compile
that would replace the bytes is refused by design, so clearing leaves the type with no assembly at
all, indefinitely — an outage with no recovery path, self-inflicted by a guard. Only a rebake fixes
it, and the log says so.

> 🚨 Do not collapse this to "`RequirePrebuilt` is unset everywhere". It is measured absent on
> **memex** and **memex-cloud** (#2194 item 3 records the same) — two instances, saying nothing about
> `pearl`, `atioz`, local installs, or any external instance the registry serves.

#### Two things this deliberately does NOT do

- **Nothing writes a fingerprint into a bundle yet**, so today every adoption reads
`AdoptedUnverified`. That is the honest state and the whole visibility win; the refusal row is armed
and starts discriminating the day the bake records one, with no further change here.
- **Refusing to LOAD is the second line of defence.** The damage needs two ingredients — stale bytes
*and something armed to run them*. A type that renders read-only pages from unverified bytes is a
degraded system; a type that WRITES from them is the incident. The execute-time interlock is
tracked separately, and `BuildProvenance` is what makes it stateable.

> 🚨 **Do not use `LatestReleasePath` vs the NodeType as a staleness signal.** It lags routinely —
> a healthy type can serve perfectly with its release pointer several versions behind, and a check of
> that shape fires constantly on healthy meshes. The fingerprint comparison above is the signal
> precisely because it does not have this problem.
### 🚨 When the PROCESS cannot emit — a failure that is not a verdict at all

Everything above assumes the compile *found something out*. Sometimes it does not: Roslyn's `Emit`
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
---
Name: An adopted assembly now says whether it matches your source
Category: Fix
Description: A NodeType that took a prebuilt assembly instead of compiling looked identical to one that compiled — same Ok status, same "sources up to date" — even when the bytes were built from older source. Adopted builds now carry their provenance on the record, and an adoption that provably disagrees with your source is refused instead of accepted.
Icon: ShieldError
Order: -20260830
---

# An adopted assembly now says whether it matches your source

When a NodeType can take a ready-built assembly from a package instead of compiling it here, it
does — that is what makes installs and restarts fast. The problem was that afterwards **nothing
told you which had happened**, and the two states were indistinguishable by every signal you would
think to check: the type read **Ok**, and its sources read **up to date**.

They read up to date because the adoption said so. Taking a prebuilt assembly *also* recorded
"these are the sources it was built from" — copied from whatever the type was holding at that
moment, without ever comparing them to the bytes. So the staleness check was not broken; it was
answering a question the adoption had already answered for it.

On 30 August that cost a client real data: a sync pulled new source, took a prebuilt assembly built
from **older** source, and reported success. The next run executed the old code and stripped the
text out of four documents — one of them a 7,000-word offer, and one that could not be recovered.
Every check available said the fix was live.

## What changed

A package can now record **which sources an assembly was built from**, and the check happens where
it can actually be made — on the NodeType itself, against the sources it is holding right now:

| | what happens |
|---|---|
| the package records sources and they **match** | adopted, marked **verified** |
| the package records sources and they **disagree** | 🚨 **refused** — the assembly is not accepted, and your source is compiled instead |
| the package records nothing (anything published so far) | adopted, marked **unverified** |

Every NodeType now carries this as **build provenance** you can read on the type — compiled here,
adopted and verified, adopted but unverified, or an adoption that was refused. That is the piece
that was missing: not a better checker, but a record of whether anything was ever checked.

**"Unverified" is not a warning that something is wrong.** It means nobody has compared these bytes
to your source, because the package that supplied them does not yet say what it was built from.
Until packages start recording that, every adopted assembly reads unverified — which is simply the
truth, and better than a record that claims a check that never happened.

## What still needs care

- A refused adoption **stops serving** the rejected assembly and compiles your source instead — on
an installation that is allowed to compile. On one configured to use only prebuilt assemblies
there is no local compile to fall back on, so the assembly is left in place (nothing at all would
be worse) and a **critical** message names the type: that one needs the package rebuilt and
republished.
- Something that is *armed to run* against a NodeType — a scheduled or triggered action — does not
yet refuse to run against an unverified one. That interlock is the next step and is tracked
separately; provenance is what makes it possible to state.
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ namespace MeshWeaver.Graph.Configuration;
///
/// <para><b>The race this closes, measured 2026-08-22.</b> Adopting a prebuilt assembly requires
/// writing the NodeType's node, and that write goes through the type's OWN hub — so
/// <see cref="PrebuiltAssemblySeeder.Seed"/> ACTIVATES the hub it is about to stamp. Activation is
/// <see cref="PrebuiltAssemblySeeder.Seed(MeshWeaver.Messaging.IMessageHub, string, byte[], byte[], string, Microsoft.Extensions.Logging.ILogger, System.Collections.Generic.IReadOnlyDictionary{string, string})"/> ACTIVATES the hub it is about to stamp. Activation is
/// exactly what arms the first-build kickoff (<c>CompilationStatus is null</c> + no usable build ⇒
/// flip Pending), so the seeder's own probe started the very Roslyn compile the adoption exists to
/// avoid:</para>
Expand Down
Loading
Loading