Skip to content

Invariant-suite handoff treats a superseded ancestor source as a conflict, because ancestors are ordered alphabetically rather than causally #315

Closed

Description

@aviggiano

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions