Repository navigation
Description
What happens
R50 (aave-v4-v0012-main-issue312fix-r50-invariant-only, image from merged main b2c3571) reached 25 succeeded / 0 failed — the cleanest run of this investigation — then died on a resume at prepare:stateful-invariant-implement-properties:
error task execution failed
nodeId=prepare:stateful-invariant-implement-properties
iteration=0 attempt=1 maxAttempts=1
error='artifact handoff ancestor invariant suite sources conflict for tests/recon/Properties.sol:
.../artifacts/stateful-invariant-handlers vs
.../artifacts/stateful-invariant-setup'
[00:00:34] ✗ Run failed
This is not #312. The workflow log contains zero occurrences of base tree mismatch, on a session that logs resume=true restoredTaskCount=48 — the exact condition that killed R48 and R49. The #312 fix held; this is a different defect at the same node.
Root cause
orderedInvariantSuiteDependencies (packages/runtime/src/templates/smithers/workflows/workflow.tsx) sorts ancestors by directness, then alphabetically:
if (leftDirect !== rightDirect) return leftDirect ? 1 : -1;
return left.localeCompare(right);stateful-invariant-implement-properties depends_on only stateful-invariant-coverage, so stateful-invariant-setup and stateful-invariant-handlers are both indirect. Alphabetically handlers sorts before setup, which is the reverse of causal order: setup runs first, handlers runs after it and legitimately rewrites tests/recon/Properties.sol.
The selection loop then sees the same path published twice with different bytes and equal directness, and fails closed:
const previous = selectedSources.get(relativePath);
if (previous !== undefined && !previous.bytes.equals(bytes)) {
if (previous.direct === isDirect || (!previous.direct && !isDirect)) {
throw new Error(`artifact handoff ancestor invariant suite sources conflict for ${relativePath}: ...`);
}
if (!isDirect) continue;
}The directness tie-break encodes an ordering intuition — a direct dependency should win over an indirect one — but between two indirect ancestors there is no tie-break at all, and alphabetical order is not causal order.
Why this is the same mistake as #312, in a different subsystem
#312 was applyWorkspacePatch reading a superseded dependency patch as drift, because it compared one patch's base_tree in isolation and could not see the chain. This is the invariant-suite handoff reading a superseded source as a conflict, because it compares two ancestors' bytes in isolation and cannot see which ran first.
In both cases a later ancestor legitimately overwrote an earlier one's output, and in both cases the machinery had no ordering with which to notice.
What a fix must NOT do
Simply ordering by taskSpecs index and letting the later ancestor win would repeat the error that made the first revision of #314 unmergeable: it assumes the ancestor set is a chain. Two genuinely parallel ancestors publishing different bytes for the same path is a real conflict and must still fail closed. The distinction needed is whether the two ancestors are ordered with respect to each other — i.e. whether one is reachable from the other in the dependency graph — not merely whether one sorts later.
Note the topology's strategies group defaults to loops: 3, loop_mode: parallel; the invariant nodes are held to one copy each only by explicit loops: 1, so a sibling fan-in here is one line of topology away.
Impact
This is now the sole known blocker between the pipeline and a Recon campaign, which no run has ever entered, and report.md, which no run has ever produced. Every node up to and including stateful-invariant-coverage has now been passed by three consecutive runs on three different images.
R50 reached it with zero failed nodes and ten authored properties — one more than any previous run, adding monotonic-index-price, a concept match for the ground truth invariant_supplySharePriceAndDrawnIndexMonotonic.
Related
- applyWorkspacePatch base tree mismatch blocks stateful-invariant-implement-properties, with maxAttempts=1 so it kills the run #312 / fix(runtime): start dependency replay after the prefix the worktree already holds #314 — the workspace-patch replay fix, confirmed in production by this very run: the resume got past the base-tree mismatch that killed R48 and R49.
- Preserve invariant-suite deletions and renames across retries #217 / Persist invariant dependency handoff provenance across workflow restarts #219 — the invariant-suite handoff and its durable record, which this selection logic belongs to.
Activity
docs(runtime): scope the fail-closed claim to where it actually holds
fix(artifacts): align the producer-facing schemas, and close three re…
fix(artifacts): accept an empty optional expectations list, and name …