Repository navigation
SettleAsError never stamps FailedBuildInputs, so every adopt-only refusal burns one needless automatic re-drive #2264
Description
What
NodeTypeCompilationHelpers' SettleAsError — the compile watcher's re-settle of a Pending flip it
refuses to dispatch — writes CompilationStatus = Error and CompilationError, but never stamps
FailedBuildInputs.
ApplyCompileFailure (a real compile's failure write-back) does stamp it. SettleAsError is the
other way a NodeType reaches a terminal Error, and it is the ONLY way a type reaches Error under
the adopt-only gate (Modules:RequirePrebuilt, #2193 §A) — which never runs a compile at all.
Why it matters
HasStaleFailureVerdict reads FailedBuildInputs to answer "has this type already had its attempt
under these compile inputs?". An unstamped verdict differs from every live token, so it always reads
"never attempted". Consequence: the adopt-only gate's refusal is followed, on every refused type,
by one automatic failed-verdict re-drive (#1793) — a Pending flip, a second trip through the gate,
a second refusal — that was never needed. It burns one of the five automatic re-drives, and it puts
a needless round on the per-NodeType hub's single-threaded action block.
With the maintainer's direction on #2193 to set Modules:RequirePrebuilt for "all which we mark as
such — should be almost all", that is one spurious round per refused type.
It converges (the re-drive's own write stamps the token, and the stamp is persisted on the node), so
it is bounded and once-per-node, not a storm.
Not the same as #2260
#2260 was the un-park inside that re-drive; that is fixed (#2262) and this is deliberately not
folded into it. Two reasons to keep them apart:
-
Removing this trigger would not have closed RequirePrebuiltParkTest flake is a real defect: the failed-verdict re-drive un-parks from a stale snapshot and drops a newer park #2260's window. The first refusal settles before
the sources watcher has establishedCurrentSourceVersions, so a stamp written there records
src=(unseeded); when the watcher publishes{}the live token moves anyway and the re-drive
fires regardless. The window had to be closed at the un-park, which is what Fix #2260: an automatic re-drive must never un-park a NodeType #2262 did. -
The two
SettleAsErrorcall sites need different answers, so this is a real design question and
not a one-liner:- the adopt-only gate (
SettleAsError(reason)) — the refusal genuinely was formed under the
live inputs, so stamping is honest; - the parked short-circuit (
SettleAsError(parkRegistry.GetParkedError(hubPath))) — the
verdict being re-served is the original failure's, formed under its inputs. Stamping the
live inputs there would mask a genuine input change since that failure and defeat A NodeType that has never compiled successfully and settles at Error is unreachable by every automatic re-drive #1793's whole
recovery path.
So the fix is a per-call-site stamp (or the token threaded from the gate), never a blanket one.
- the adopt-only gate (
Where
src/MeshWeaver.Graph/Configuration/NodeTypeCompilationHelpers.cs—SettleAsErrorinside
InstallCompileWatcher, and its two call sites.src/MeshWeaver.Graph/Configuration/NodeTypeCompilationHelpers.cs—HasStaleFailureVerdict/
BuildInputsTokenfor the semantics.
Acceptance
A require-prebuilt-refused type settles at Error with FailedBuildInputs recording the inputs the
refusal was formed under, and GetFailureRedriveCount for it stays at 0 — no automatic re-drive
where none was warranted. The parked short-circuit's re-settle must still leave the stamp alone, and
NeverCompiledFailureRedriveTest must stay green (it pins that a genuinely stale verdict IS
re-driven, exactly once).
Activity
Merge pull request #2318 from Systemorph/worktree-agent-a890b7ec72dd6…
rbuergi commentedon Aug 26, 2026
Fixed by #2318, merged as
187f91048.SettleAsError— the only path toCompilationStatus.Errorunder theModules:RequirePrebuiltadopt-only gate — never stampedFailedBuildInputs, soHasStaleFailureVerdictread every refusal as "never attempted" and drove one needless automatic re-drive per refused type, exactly as reported.The fix extracts
ApplyGateSettle, which deliberately gives the two call sites opposite treatment:Verified on
main:NodeTypeCompilationHelpers.cscarriesApplyGateSettleand theFailedBuildInputshandling.Tests:
GateSettleFailedBuildInputsTest+FailedVerdictRedriveTest24/24,RequirePrebuiltParkTest(integration) 1/1,NeverCompiledFailureRedriveTest+NodeTypeCompileParkTest+RedriveObservation*18/18.(Closing by hand — #2318's body omitted the closing keyword, so the auto-close never fired.)