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
| Layer | Question | Owner |
|---|---|---|
| Hard conflict | Can the patch streams replay together? | Draxl merge analysis |
| Validation and build | Does the merged result remain valid? | validator, lowering, compiler, CI |
| Semantic conflict | Do 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_nameandbinding_initializer - parameter owner with
parameter_type_contractand 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_centsRight 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 BasisPointsRight 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.