Skip to content

feat(#2820): a build PROVEN stale is never armed — the execute-time interlock - #2982

Merged
github-actions[bot] merged 4 commits into
mainfrom
feat/execute-time-provenance-interlock
Sep 1, 2026
Merged

github-actions[bot] merged 4 commits into
mainfrom
feat/execute-time-provenance-interlock

Conversation

@rbuergi

@rbuergi rbuergi commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Closes #2820. The other half of #2813, whose prerequisite (#2947, d499bb781) landed today: with BundleWriter writing a sourceFingerprint and PrebuiltAssemblySeeder threading it through, BuildProvenance can finally read AdoptedVerified / AdoptionRefused instead of AdoptedUnverified for everything — so an execute-time predicate is answerable for the first time. Verified on this branch: the 7-arg overload is [Obsolete(error: true)], and ApplyCompileSuccess still resets the provenance to Compiled, which is what makes the signal trustworthy.

The three questions the issue asked, answered

1. Trigger-surface coverage

A census, because a check in only some surfaces is worse than none — it makes the gap look closed.

# surface class gated
1 NodeTypeEnrichmentHelpers hot activation path binds HubConfiguration onto a per-instance node; MonolithRoutingService / MessageHubGrain build the real hub from it ✅
2 …its pinned-release branch same ✅ same check
3 NodeTypeContractHandler legacy contract hands a configuration to an activating instance ✅ same node state
4 CellSurfaceAssemblyProvider — kernel cell-surface join loads straight through NodeAssemblyLoadContext; every submission can then call the pack by bare name with full write access ✅ own check
5–7 $Model probe, SchemaReference handler, agent schema validation AsTransientNodeProbe — no control plane, no persistence sampler, no node identity, disposed immediately ❌ deliberate
8 NodeTypeBatchBake / DynamicTypePreWarmer produce Compiled provenance by construction n/a

1–3 all funnel through ApplyStreamResult, so one check above every assembly-resolving branch covers them. 4 never enriches and never builds a HubConfiguration, so it gets its own — the same shared pure predicate, not a second opinion.

2. Read vs write — the split asked for is not available at the type level

  • Every surface executes assembly code, including the read-only ones. MeshNodeCompilationService.CompileResultFromAssembly loads the assembly and calls Activator.CreateInstance on every MeshNodeProviderAttribute type before any caller decides whether to use the result. A "read-only render" already runs the assembly's constructors.
  • A rendered layout area is not write-free — WithClickAction, the Edit macro, a WithInitialization watcher installed by the same configuration.
  • Tagging the writer is not available — attributing a write back to its assembly needs an ambient identity across IObservable hops, which this repo forbids and AsyncLocal does not survive.

So the cut is durable-arming vs transient-probe, a line the framework already draws for its own reasons (AsTransientNodeProbe: "a probe hub must never be used to WRITE"). Net effect: the refused type's own pages keep rendering — Overview, data model, schema, compile diagnostics, i.e. exactly what an operator needs — while its instances serve the refusal card and its assembly never joins a kernel session.

3. Refusal visibility and the recovery verb

New ErrorType.ExecutionRefused, deliberately not CompilationFailed (which sends an author to edit source Roslyn never rejected — #641) and not Unavailable (which reads as "retry" when a verdict was reached — #2818's lost hour). It appears four ways carrying the same sentence: an UnhandledMessageNack naming the type, a page whose copy is localized at render time off the viewer's AccessContext, a LogCritical (arming) / LogError (cell surface) naming both fingerprints, and BuildProvenance on the node plus its compile-state satellite.

Recovery verb: Recompile / the compile MCP verb — since #2824 a forced release skips re-adoption and builds the live source. On a Modules:RequirePrebuilt mesh: rebake, republish, release; the log says so in those words. A successful compile stamps Compiled and the overlay's self-heal recycles every stuck instance.

The dominant risk, and what holds it

🚨 AdoptedUnverified is PERMITTED. A legacy bundle carries no fingerprint, so nothing was compared: unknown ≠ proven stale. Refusing it would park every type adopted from every bundle published to date, and on RequirePrebuilt there is no local compile to recover with — the outage "refuse every unproven bundle" was rejected to avoid. This is asserted twice (pure + on a real mesh) and is the assertion most worth protecting.

🚨 Inconclusive is its own member, never folded. It arises only where the definition could not be read at all — where every arming site already binds no assembly — so the gate names the state rather than inventing a refusal for it. Refusal is opt-in one provenance at a time (_ => Permitted), so a member appended to BuildProvenance later cannot silently become a refusal.

Where this is actually reachable: on a mesh that can compile locally, a refusal already clears the assembly coordinates, so HasUsableBuild is false and the ordinary branches take over — the gate adds no refusals there. It catches the two states the load-time refusal deliberately leaves serving: a Modules:RequirePrebuilt mesh, and the window between Seed stamping coordinates and the owner judging them.

Verification

dotnet build <one.csproj> -c Release -warnaserror, 0 Error(s) each: Messaging.Contract, Messaging.Hub, Graph, Documentation, Graph.Test, Documentation.Test, Messaging.Hub.Test, Hosting.Monolith.Test.

Tests (local, Release, .trx written):

  • NodeTypeExecutionGateTest + ExecuteTimeInterlockTest — 14/14. The latter drives the real EnrichWithNodeType on a real Monolith mesh against two persisted NodeType nodes differing in exactly one field, and reads the verdict off the UnhandledMessageNack the produced configuration installs.
  • CellSurfaceScriptingSeamTest — 6/6, including the new case: the same bare-name call runs before the provenance flip and stops resolving (CS0103) after it, with the bytes unchanged.
  • Enrichment / overlay / provenance neighbourhood in Graph.Test — 119/119.
  • DocumentationLinkIntegrityTest 1/1 · LocalizationTest 58/58.

Mutation-checked — each control inverted once, observed RED, restored:

inversion result
refuse AdoptedUnverified too 2 red — both anti-outage assertions
disable the arming-site gate 1 red — the refused type activates with CompilationFailed
disable the cell-surface gate 1 red — the refused pack stays callable by bare name
a bogus link on the new doc page 1 red — the integrity test names the page, proving it is covered

In-mesh safety: nothing public was deleted or renamed; ErrorType gained an appended member. No search_chunks sweep was needed (and it would have answered "searched": false anyway — #2741).

Docs

New page Doc/Architecture/ExecuteTimeInterlock — the surface census, the read-vs-write argument with its evidence, where this is reachable, and an explicit what this deliberately does NOT do (the probe surfaces' residual constructor execution is named rather than assumed away). Linked from NodeTypeCompilation's "refusing to LOAD is the second line of defence" note and from the Architecture index. What's New: Category: Fix.

Follow-up, not in this PR

The three new ui.* keys have a second home in MeshWeaver.Plugins/clients/react/src/i18n/. That mirror compares against a pinned core commit (catalog-source.json), which is already 9 keys behind, so this does not redden Plugins today; the keys travel with whoever bumps the pin.

🤖 Generated with Claude Code

…nterlock

Closes #2820. The other half of #2813, whose prerequisite (#2947) landed today: with a
producer now writing a source fingerprint into every bundle, `BuildProvenance` can finally
read `AdoptedVerified` / `AdoptionRefused` rather than `AdoptedUnverified` for everything —
so an execute-time predicate is answerable for the first time.

## The predicate — three values, never two

`NodeTypeExecutionGate.Evaluate(NodeTypeDefinition?)`:

| provenance | verdict |
|---|---|
| `AdoptionRefused` | **Refused** — the bundle NAMED its sources and they are not this mesh's |
| `AdoptedUnverified` | **Permitted** — a legacy bundle compared nothing; unknown ≠ proven stale |
| `AdoptedVerified` / `Compiled` | Permitted |
| definition unreadable | **Inconclusive** — no verdict was reached |

🚨 The row that matters most is the one that PERMITS. Refusing `AdoptedUnverified` would park
every type adopted from every bundle published to date, and on a `Modules:RequirePrebuilt`
mesh a local compile is refused by design — the outage "refuse every unproven bundle" was
rejected to avoid, arriving through a different door. `Inconclusive` is its own member for
the reason `ErrorType.Unavailable` is: a probe must not answer its scariest branch — or its
friendliest — on its own inability to run. Refusal is opt-in one provenance at a time, so a
member appended later cannot silently become a refusal.

## Two enforcement sites, chosen from a census

`ApplyStreamResult` sits above EVERY branch that resolves an assembly (hot path, pinned
release, legacy fallbacks), so one check covers all of them — a check inside only some would
make the gap look closed, which the issue calls worse than none. The kernel cell-surface join
is architecturally separate (it loads straight through `NodeAssemblyLoadContext`, no
enrichment, and from then on every submission can call the pack by bare name with full write
access), so `CellSurfaceAssemblyProvider.ResolveOne` carries its own check.

The transient-probe surfaces (`$Model` render, `SchemaReference`, agent schema validation)
stay open deliberately: `AsTransientNodeProbe` hubs have no control plane, no persistence
sampler and no node identity, and they are what lets a refused type's OWN pages keep
rendering so an operator can diagnose it.

**Read vs write is not the available cut, and the doc says why**: every surface —
including the read-only probes — already runs `Activator.CreateInstance` on the assembly's
`MeshNodeProvider` types inside `CompileResultFromAssembly`, and a rendered layout area is
not write-free (`WithClickAction`, the `Edit` macro, a `WithInitialization` watcher). The
honest cut is durable-arming vs transient-probe.

## The refusal is a verdict, not a timeout

New `ErrorType.ExecutionRefused` — deliberately not `CompilationFailed` (which sends an
author to edit source Roslyn never rejected, #641) and not `Unavailable` (which reads as
"retry" when a verdict WAS reached, #2818's hour). It rides an `UnhandledMessageNack` naming
the type, a `LogCritical` / `LogError` naming both fingerprints, and a page whose copy is
localized at render time off the viewer's `AccessContext` (`ui.executionRefusedIntro` /
`ui.executionRefusedGuidance` / `ui.noCodeChangeNeeded`, en + de). Recovery verb: a FORCED
release (#2824), or a rebake where no local compile is possible. The overlay self-heal makes
it clear itself, because `ApplyCompileSuccess` resets the provenance to `Compiled`.

## Verification

Builds `-c Release -warnaserror`, one project per invocation, `0 Error(s)`:
Messaging.Contract, Messaging.Hub, Graph, Documentation, Graph.Test, Documentation.Test,
Messaging.Hub.Test, Hosting.Monolith.Test.

Tests: `NodeTypeExecutionGateTest` + `ExecuteTimeInterlockTest` 14/14 (the latter drives the
real `EnrichWithNodeType` on a real Monolith mesh, two nodes differing in exactly one field),
`CellSurfaceScriptingSeamTest` 6/6 (the new case proves the same bare-name call runs before
the provenance flip and stops resolving after it), `DocumentationLinkIntegrityTest` 1/1,
`LocalizationTest` 58/58.

Mutation-checked — each control was inverted once and observed RED, then restored:
- refuse `AdoptedUnverified` too → both anti-outage assertions fail (2 red)
- disable the arming-site gate → the refused type activates with `CompilationFailed` (1 red)
- disable the cell-surface gate → the refused pack stays callable by bare name (1 red)
- a bogus link on the new doc page → `DocumentationLinkIntegrityTest` names the page (1 red)

Nothing public was deleted or renamed, so no in-mesh caller can break; `ErrorType` gained an
appended member only.

Docs: new `Doc/Architecture/ExecuteTimeInterlock` (the surface census, the read-vs-write
argument, where this is reachable, and what it deliberately does NOT do), linked from
`NodeTypeCompilation` and the Architecture index. What's New: `Category: Fix`.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings September 1, 2026 14:39
@github-actions
github-actions Bot enabled auto-merge September 1, 2026 14:40

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.

Pull request overview

Implements the execute-time “build provenance interlock” so NodeTypes with proven-stale adopted bytes (BuildProvenance.AdoptionRefused) are not armed to run: instances serve a refusal overlay (and NACK typed requests with ErrorType.ExecutionRefused), and such types are also excluded from kernel cell-surface joins. Adds tests, localization copy, and documentation to make the refusal visible and actionable.

Changes:

  • Add a pure NodeTypeExecutionGate and enforce it at (1) NodeType enrichment/arming and (2) cell-surface assembly resolution.
  • Introduce ErrorType.ExecutionRefused plus localized refusal-page copy (ui.executionRefused*).
  • Add end-to-end and unit tests plus new/updated architecture + “What’s New” documentation.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
test/MeshWeaver.Hosting.Monolith.Test/CellSurfaceScriptingSeamTest.cs Adds regression proving a proven-stale pack no longer joins the cell surface (bare-name call stops resolving).
test/MeshWeaver.Graph.Test/NodeTypeExecutionGateTest.cs Adds pure verdict-table tests (including “anti-outage” permitted row + localization-key presence checks).
test/MeshWeaver.Graph.Test/ExecuteTimeInterlockTest.cs Adds Monolith-mesh-driven tests proving arming-site refusal vs permitted provenances.
src/MeshWeaver.Messaging.Hub/Localization/strings.en.json Adds English UI copy for the refusal overlay.
src/MeshWeaver.Messaging.Hub/Localization/strings.de.json Adds German UI copy for the refusal overlay.
src/MeshWeaver.Messaging.Contract/Events.cs Adds ErrorType.ExecutionRefused with explanatory documentation.
src/MeshWeaver.Graph/Configuration/NodeTypeExecutionGate.cs Introduces the pure gate (Permitted/Refused/Inconclusive) plus refusal summary + recovery verb text.
src/MeshWeaver.Graph/Configuration/NodeTypeEnrichmentHelpers.cs Enforces the gate before assembly-resolving branches; adds refusal overlay builder + localization keys.
src/MeshWeaver.Graph/Configuration/CellSurfaceAssemblyProvider.cs Enforces the gate before joining an assembly into a kernel’s cell surface (logs + skip).
src/MeshWeaver.Documentation/Data/WhatsNew/2026-09-01-a-type-built-from-the-wrong-source-is-no-longer-allowed-to-run.md Adds user-facing “What’s New” entry describing the new refusal behavior and recovery.
src/MeshWeaver.Documentation/Data/Architecture/NodeTypeCompilation.md Links to and summarizes the execute-time interlock behavior in the compilation doc.
src/MeshWeaver.Documentation/Data/Architecture/ExecuteTimeInterlock.md Adds new architecture page documenting trigger surfaces, rationale, visibility, and recovery.
src/MeshWeaver.Documentation/Data/Architecture.md Adds the new page to the architecture index.
Suppressed comments (1)

src/MeshWeaver.Graph/Configuration/NodeTypeExecutionGate.cs:125

  • RecoveryVerb is a hard-coded English paragraph that is appended to the UnhandledMessageNack.Reason. Since this text can surface to end users (delivery failures are shown in UI surfaces), it should be localized or moved into the already-localized UI guidance copy rather than emitted as an English literal.
    public const string RecoveryVerb =
        "Recompile this type (the Recompile button, or the `compile` verb over MCP — a FORCED "
        + "release compiles the live source instead of re-adopting the bundle, #2818). On a "
        + "`Modules:RequirePrebuilt` mesh no local compile is possible: rebake and republish the "
        + "package, then request a release.";

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/MeshWeaver.Compiler.Pipeline/NodeTypeExecutionGate.cs
Comment thread test/MeshWeaver.Graph.Test/ExecuteTimeInterlockTest.cs
… cell-surface regression

main landed two moves under this change set's feet, both handled deliberately rather than by
taking a side:

- **`CellSurfaceAssemblyProvider` moved to the new `MeshWeaver.Compiler.Pipeline`** (the #1707
  layering split). The rename carried the interlock's second enforcement site with it. Its
  predicate, `NodeTypeExecutionGate`, moves to that assembly too — NOT to `MeshWeaver.Graph.Contract`
  where `NodeTypeDefinition` now lives, because Graph.Contract sits inside `MeshWeaver.Compiler`'s
  reference closure, which is a full-MVID toolchain root: a body-only change there re-bakes every
  NodeType on every mesh. The pipeline is surface-hashed and is referenced by both enforcement
  sites, so it carries the predicate at no rebake cost. Noted on the doc page.

- **`CellSurfaceScriptingSeamTest` was DELETED on main** — the whole
  `MeshWeaver.Hosting.Monolith.Test` project went to MeshWeaver.Plugins with 16 sibling suites.
  Silently accepting that deletion would have taken the only regression on enforcement site 2 with
  it, in a repo where half-finished cross-repo moves are the live defect class. The code under test
  is still core's, so the regression stays core's: `CellSurfaceRefusedBuildTest` in
  `MeshWeaver.Compiler.Pipeline.Test` now proves the same property one layer lower and faster —
  it resolves `ICellSurfaceAssemblyProvider` directly instead of round-tripping a kernel cell.

  Same controlled shape as the version it replaces: the pack is compiled, asserted PRESENT in the
  resolved cell surface, then the NodeType node is changed in exactly one field
  (`BuildProvenance = AdoptionRefused`, same bytes, same coordinates, same `CompilationStatus.Ok`)
  and asserted ABSENT. Mutation-checked at the new site: with the gate disabled it reads
  "expected false … but found True".

- `Architecture.md` conflict resolved by keeping BOTH entries — main's `GraphCompilerLayering`
  row and this change set's `ExecuteTimeInterlock` row.

Verification after the merge, `-c Release -warnaserror`, `0 Error(s)` each: Compiler.Pipeline,
Graph, Documentation, Graph.Test, Compiler.Pipeline.Test, Documentation.Test. Tests:
Graph.Test gate + interlock + enrichment/overlay/provenance 53/53, Compiler.Pipeline.Test
569/569 (whole project, including the new regression), DocumentationLinkIntegrityTest 1/1.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@rbuergi

rbuergi commented Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

Synced with main — two moves landed under this change set, both handled deliberately

main moved twice while this was in flight, and neither could be resolved by "take a side":

1. CellSurfaceAssemblyProvider moved to the new MeshWeaver.Compiler.Pipeline (the #1707 layering split). The rename carried enforcement site 2 with it. Its predicate, NodeTypeExecutionGate, follows it there — not to MeshWeaver.Graph.Contract, where NodeTypeDefinition now lives and which would be the tidier home. Graph.Contract sits inside MeshWeaver.Compiler's reference closure, i.e. inside FrameworkBuildIdentity.FullMvidAssemblies: a body-only change there re-bakes every NodeType on every mesh. Compiler.Pipeline is surface-hashed, is high-churn by design, and is referenced by both enforcement sites — so it carries the predicate at no rebake cost. Recorded on the doc page, linked to GraphCompilerLayering.

2. CellSurfaceScriptingSeamTest was DELETED on main — the whole MeshWeaver.Hosting.Monolith.Test project went to MeshWeaver.Plugins with 16 sibling suites. Taking main's side unexamined would have carried away the only regression covering enforcement site 2, in a repo where half-finished cross-repo moves are the live defect class. The code under test is still core's, so the regression stays core's: CellSurfaceRefusedBuildTest in MeshWeaver.Compiler.Pipeline.Test proves the same property one layer lower and an order of magnitude faster — it resolves ICellSurfaceAssemblyProvider directly instead of round-tripping a kernel cell.

Same controlled shape as the version it replaces: compile the pack → assert it is in the resolved cell surface → change the NodeType node in exactly one field (BuildProvenance = AdoptionRefused; same bytes, same coordinates, same CompilationStatus.Ok) → assert it is not. Re-mutation-checked at the new site: with the gate disabled it fails on "expected false … but found True", so the control arm is real and the guard is what makes the second half pass.

Architecture.md's conflict was resolved by keeping both entries — main's GraphCompilerLayering row and this change set's ExecuteTimeInterlock row.

Re-verified after the merge

-c Release -warnaserror, one project per invocation, 0 Error(s): Compiler.Pipeline · Graph · Documentation · Graph.Test · Compiler.Pipeline.Test · Documentation.Test.

  • Graph.Test — gate + interlock + the enrichment/overlay/provenance neighbourhood: 53/53
  • Compiler.Pipeline.Test — 569/569 (whole project, including the new regression)
  • DocumentationLinkIntegrityTest 1/1

The four mutation checks in the description all still stand; the cell-surface one was re-run against the moved file.

rbuergi and others added 2 commits September 1, 2026 16:59
… stays on the log and the NACK

Copilot review, and it was right: RefusalSummary was passed to the overlay as its error message,
so a German viewer read an English sentence. The three facts a refusal has to name — the node
path and the two source fingerprints — are a wire identifier and two opaque hashes, so they are
verbatim in every language; only the sentence around them is translated. The page now reads
'ui.executionRefusedSummary' with those three as {0}/{1}/{2}, in en + de.

The English sentence is NOT deleted: it stays on the log line (Loki is English by house rule) and
on the delivery NACK (read by developers and tools, never rendered to a viewer). The split is what
lets an operator grep one language while a viewer reads their own.

🚨 The guard is the WIRING, not the catalog. 'BuildExecutionRefusedMarkdown' takes its localizer as
a Func — the UserPreferencesLocalizationTest pattern — so with an ECHO localizer every string the
page supplies comes back as its own dotted key, and an English literal stands out as prose among
keys. Asserting the catalog alone would pass while nothing read it. Mutation-checked: hard-coding
the headline again fails 'EveryStringTheRefusalPageSupplies_ComesFromTheCatalog'.

Also lifts the emergency-card CSS to a const shared with the compile-error page, so the two
overlays cannot drift apart visually.

Verified: Graph, Compiler.Pipeline, Messaging.Hub, Graph.Test -c Release -warnaserror, 0 Error(s);
gate + interlock + overlay + compile-error-page suites 60/60; LocalizationTest 58/58.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…venance-interlock

# Conflicts:
#	src/MeshWeaver.Documentation/Data/Architecture.md
@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Test Results (shard 0)

216 tests   216 ✅  2m 15s ⏱️
  1 suites    0 💤
  1 files      0 ❌

Results for commit 4cc4e08.

@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Test Results (shard 3)

586 tests   586 ✅  53s ⏱️
  2 suites    0 💤
  2 files      0 ❌

Results for commit 4cc4e08.

@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Test Results (shard 5)

1 128 tests   936 ✅  1m 1s ⏱️
    3 suites  192 💤
    3 files      0 ❌

Results for commit 4cc4e08.

@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Test Results (shard 1)

419 tests   419 ✅  1m 21s ⏱️
  1 suites    0 💤
  1 files      0 ❌

Results for commit 4cc4e08.

@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Test Results (shard 2)

507 tests   507 ✅  1m 28s ⏱️
  3 suites    0 💤
  3 files      0 ❌

Results for commit 4cc4e08.

@github-actions

github-actions Bot commented Sep 1, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 4)

1 668 tests   1 668 ✅  1m 48s ⏱️
    3 suites      0 💤
    3 files        0 ❌

Results for commit 4cc4e08.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Sep 1, 2026 •

Copy link
Copy Markdown
Contributor

Test Results

4 524 tests   4 332 ✅  8m 49s ⏱️
   13 suites    192 💤
   13 files        0 ❌

Results for commit 4cc4e08.

♻️ This comment has been updated with latest results.

@rbuergi

rbuergi commented Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

The shard-4 red is attributed and filed — #2986 — and it is not this diff

One test failed in run 33523142249: ImportTypeBeforeInstanceTest.InstanceEnumeratedBeforeItsType_StillLandsInOnePass, a 60 s TimeoutException inside its Body(path) read helper. Diagnosed before re-running anything, on three pieces of evidence rather than "probably a flake":

  1. Ordering rules out contention. That test ran 15:04:50–15:05:51. This PR's new tests ran at 15:05:56 — after it — and every one reported [PASS] with teardown clean in the same shard.
  2. Reachability rules out the change. This PR adds exactly one branch, guarded on BuildProvenance == AdoptionRefused. That test's NodeType is new NodeTypeDefinition { Configuration = "config => config" }, whose BuildProvenance is the zero value Compiled — the branch is not taken and the control flow is byte-identical to main.
  3. The transcript names a different defect. The import itself succeeded (outcome=Imported count=14 failed=0 blocked=[], and both import assertions passed). What timed out is the read that follows, against an instance hub that was mid-self-recycle — the designed DisposeRequest a NodeType's first usable build posts to instances bound before it:
Stream heCb5oZx…: resubscribe failed.
  DeliveryFailureException: Hub Tb666188a0/Inst is shutting down (RunLevel=Dead, activation #017DA86C)  [×3]
… 60 s of nothing …

That is the signature ErrorType.ShuttingDown is documented against: transient, retry-worthy, never terminal — "erroring the sync stream on this NACK killed the resubscribe latch and wedged every read of a mid-recycle NodeType" (CI 30003419841). The latch spends its attempts on a disposing activation and does not re-arm when the address comes back, and the caller is then handed a bare TimeoutException that reads as slow rather than refused three times and stopped trying — the same mis-signalling #2818 cost an hour to.

The suite is one day old (added by #2944 at ~13:00Z today); main is green on it so far, and this is its first recorded failure. Locally it is 2/2 in 4 s. Filed as #2986 with the suggested direction — re-arm the latch on ShuttingDown, and make the terminal carry the NACK instead of a timeout. Explicitly not "raise the 60 s bound", which would only wait longer for a latch that has stopped trying.

Re-running the failed jobs only (the run's head is still this branch's tip, so nothing live is superseded). If it comes back red on the same test I will stop and hand it over rather than keep pushing.

@github-actions
github-actions Bot merged commit 119f3ef into main Sep 1, 2026
39 of 41 checks passed
@rbuergi
rbuergi deleted the feat/execute-time-provenance-interlock branch October 10, 2026 13:10
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.

A control plane must refuse to EXECUTE against a type whose build provenance is not verified — refusing to load is the second line of defence

2 participants