Draxl Docs

Conflicts

Hard conflicts, semantic conflicts, and the structured JSON report surface.

Draxl separates merge issues into layers. Each layer gives humans and agents a specific next action.

Conflict Layers

LayerQuestionOwner
Hard conflictCan the patch streams replay together?Draxl merge analysis
Validation and buildDoes the merged result remain valid?validator, lowering, compiler, CI
Semantic conflictDo the combined edits preserve meaning?semantic merge rules

This layering keeps replay failures, compiler failures, and meaning-level review signals distinct.

Hard Conflicts

A hard conflict means the streams lack one deterministic merged tree.

Typical causes:

  • both sides write the same node or scalar path
  • both sides write the same single-child slot
  • both sides insert at the same rank in the same slot
  • one side deletes or moves a region that the other side edits inside
  • replay order changes the final tree

Hard conflict reports include operation evidence so an agent can identify the exact conflicting edits.

Semantic Conflicts

A semantic conflict means the patch streams replay cleanly, yet the combined result touches coupled meaning-bearing regions.

The current implementation derives two concepts from the base tree:

  • semantic owner: the smallest semantic object that contains the coupled edits
  • semantic region: the meaning-bearing part of that owner touched by an edit

Implemented owner and region families include:

  • binding owner with binding_name and binding_initializer
  • parameter owner with parameter_type_contract and body interpretation regions

Binding Example

Starting source:

@m1 mod demo {
  @f1[a] fn price(@p1[a] amount: @t1 Cents) -> @t2 Cents {
    @s1[a] let @p2 subtotal = @e1 amount;
    @s2[b] @e2 subtotal
  }
}

Left patch:

set @p2.name = subtotal_cents
replace @e2: @e2 subtotal_cents

Right patch:

replace @e1: @e1 to_dollars(@e3 amount)

The two streams replay structurally. Together they produce a binding named subtotal_cents whose initializer computes to_dollars(amount).

The semantic conflict report identifies that coupled owner:

{
  "class": "semantic",
  "code": "binding_rename_vs_initializer_change",
  "owner": {
    "kind": "binding",
    "let_id": "s1",
    "binding_id": "p2"
  },
  "left_regions": ["binding_name"],
  "right_regions": ["binding_initializer"],
  "left": [
    {
      "op_index": 0,
      "op_kind": "set",
      "target": "@p2.name"
    }
  ],
  "right": [
    {
      "op_index": 0,
      "op_kind": "replace",
      "target": "@e1"
    }
  ]
}

Parameter Example

Starting source:

@m1 mod demo {
  @f1[a] fn is_discount_allowed(@p1[a] rate: @t1 Percent) -> @t2 bool {
    @s1[a] @e1 (@e2 rate < @l1 100)
  }
}

Left patch:

put @p1.ty: @t3 BasisPoints

Right patch:

replace @e1: @e1 (@e2 rate < @l1 95)

The left side changes the parameter contract. The right side changes body logic that still interprets the parameter through the old scale. The merged source is valid Draxl, but the function needs review.

Review Guidance

Treat hard conflicts as merge blockers. Resolve the competing patch operations or split the work into compatible streams.

Treat semantic conflicts as review blockers. Inspect the reported owner and regions, then decide which side owns the combined meaning.

On this page