Skip to content

Conflict workflows & diagrams

Visual references for how TOVIO treats conflicts as data, not events.

Prose explanations live on Conflicts as data and Resolve conflicts — this page is the map.

How to read these

Rectangles are states or artifacts, rounded shapes are actions, and diamonds are decisions. A stored conflict is a success glyph (✓), not an error — the merge completed and the open question is durable data attached to the resulting change.

1. Conflict-as-data: the mental model

Git treats a conflict as a halt; TOVIO treats it as a typed object the repository holds for you.

flowchart LR
    subgraph GIT[" Git model "]
        G1["merge"] --> G2{"auto-merges?"}
        G2 -- yes --> G3([✓ merged])
        G2 -- no --> G4[["✗ HALT<br/>working tree filled<br/>with <<<<<<< markers"]]
    end
    subgraph TOVIO[" TOVIO model "]
        V1["land / sync"] --> V2{"auto-merges?"}
        V2 -- yes --> V3([✓ merged])
        V2 -- no --> V4[["✓ merged<br/>+ conflict object stored<br/>(first-class data, keyed by path)"]]
        V4 --> V5["Repo stays operational:<br/>commit, switch, sync, stack<br/>keep working"]
    end

    classDef halt fill:#fdd,stroke:#c33,color:#600;
    classDef ok fill:#dfd,stroke:#3a3;
    class G4 halt;
    class V3,V4,V5 ok;

See Conflicts as data.

2. The conflict object — anatomy

A conflict is content-addressed data in the object graph. It carries the base, both sides, its kind, and a stable identity that resolutions can key against.

flowchart TB
    C["Conflict object<br/>(content-addressed · one per path)"]
    C --> K{{"kind"}}
    K --> KC["content"]
    K --> KDM["delete/modify"]
    K --> KRE["rename/edit"]
    K --> KRR["rename/rename"]
    K --> KSEM["semantic (advisory)"]
    C --> BASE["merge base<br/>(common ancestor region)"]
    C --> OURS["ours side<br/>(local change)"]
    C --> THEIRS["theirs side<br/>(incoming change)"]
    C --> ID["stable identity<br/>(survives amend/rebase/absorb)"]
    C --> STATUS{"unresolved · partially resolved<br/>· resolved · deferred"}

    classDef obj fill:#eef,stroke:#557;
    class C,BASE,OURS,THEIRS,ID obj;

3. Typed three-way merge — decision flow

tovio-core runs a typed three-way merge over the commit DAG. Auto-merge succeeds unless both sides edit the same region incompatibly.

flowchart TD
    S([Start merge]) --> BASE["Find merge base<br/>(common ancestor)"]
    BASE --> DIFF["Compute (base → ours) and<br/>(base → theirs) region diffs"]
    DIFF --> LOOP{{"For each region"}}
    LOOP --> BOTH{"Both sides<br/>touch it?"}
    BOTH -- no --> KEEP["Keep the side that changed<br/>(or base if neither)"]
    BOTH -- yes --> SAME{"Same result?"}
    SAME -- yes --> KEEP
    SAME -- no --> KIND{"Classify by kind"}
    KIND --> STORE[["Store typed conflict object<br/>on the resulting change"]]
    KEEP --> NEXT
    STORE --> NEXT([next region])
    NEXT --> DONE([Merge completes ✓])
    DONE --> OUT{"Any stored conflicts?"}
    OUT -- yes --> OPEN["Surface in status &<br/>tovio conflicts"]
    OUT -- no --> CLEAN([Clean merge])

4. Kinds of conflict → how tovio resolve dispatches

tovio resolve is type-directed. A rename/rename isn't a text merge; a delete/modify is a keep-or-delete decision.

flowchart LR
    R["tovio resolve &lt;path&gt;"] --> K{"conflict.kind"}
    K -- content --> C1["per-region choice or hand-merge,<br/>or --ours / --theirs / --base"]
    K -- delete/modify --> C2["--keep · --delete"]
    K -- rename/edit --> C3["Usually auto-merged;<br/>surfaces only if edits overlap"]
    K -- rename/rename --> C4["--rename-to &lt;path&gt;"]
    K -- semantic --> C5["Advisory finding<br/>(warn; blocks only if<br/>protected-lane policy requires)"]

    C1 --> APPLY["Apply resolution<br/>(structured Resolution)"]
    C2 --> APPLY
    C3 --> APPLY
    C4 --> APPLY
    C5 --> ADV["Recorded as warning"]

Prose: Resolve conflicts → kinds.

5. The state machine of a single conflict

stateDiagram-v2
    [*] --> Unresolved: merge stored it
    Unresolved --> PartiallyResolved: tovio resolve --region n<br/>(some regions settled)
    PartiallyResolved --> Resolved: remaining regions settled
    Unresolved --> Resolved: tovio resolve<br/>(--ours / --theirs / --keep /<br/>--delete / --rename-to / interactive)
    Resolved --> Resolved: same identity seen elsewhere —<br/>rerere replay, no re-fight
    Resolved --> [*]: change lands

These are the conflict object's own lifecycle states, and its status field spells them — unresolved (the value a conflict carries with the field absent), partially_resolved, resolved, and a fourth the diagram leaves out: deferred, a conflict deliberately left open, which no shipped command records today. A refused tovio land is deliberately not a state here either: the land gate (§6) reads the stored conflicts and rejects the land, leaving the conflict unresolved exactly as it was — a verdict on the land, not a state the conflict enters. There is likewise no shipped verb for deliberately reopening a resolution — one is specified, but it is not in the CLI today.

6. The one hard limit — the land gate

Conflicts are non-blocking within your stack. They are strictly blocking at the land boundary itself — the source tip, the target tip, and the merge base must all be conflict-free — and a protected lane runs conflict-freedom as an explicit gate on top of that, because shared code must stay buildable.

flowchart TD
    L["tovio land my-lane --into main"] --> H["tovio change health chg:002"]
    H --> Q{"unresolved conflicts?"}
    Q -- no --> GATES["Other land gates<br/>(policy, plugins, review)"]
    Q -- yes --> B[["✗ Land refused<br/>resolve them first"]]
    B --> FIX["tovio resolve"]
    FIX --> L
    GATES --> OK([✓ Landed])

    classDef stop fill:#fdd,stroke:#c33,color:#600;
    class B stop;

Prose: Conflicts as data → strictly blocked.

7. Non-blocking scope vs. cross-developer isolation

A conflict on someone else's change does not silently spray markers into your working tree. sync is gated; --isolate builds against the last-known-clean version.

sequenceDiagram
    autonumber
    actor Alice
    actor Bob
    actor Carol
    participant Repo as Repository

    Alice->>Repo: land chgA (forces conflict onto Bob's chg-002)
    Repo-->>Alice: ✓ landed
    Repo-->>Bob: chg-002 now stores a conflict on cgm.ts
    Note over Bob: Bob's conflict — Bob resolves when ready.<br/>Alice is not blocked.

    Carol->>Repo: tovio sync (depends on chg-002)
    Repo-->>Carol: ⚠ chg-002 is conflicted.<br/>Not materializing markers into your tree.
    Carol->>Repo: tovio sync --isolate
    Repo-->>Carol: Built against last-known-clean chg-002

Prose: Conflicts as data → the boundary that matters.

8. Resolve once, applies everywhere (rerere-by-default)

Because a conflict has a stable identity, a resolution recorded once is replayed automatically wherever the same conflict recurs — through amend, rebase, and the descendant cascade a land triggers.

flowchart LR
    R1["Resolve cgm.ts<br/>in chg-002"] --> CACHE[("Resolution cache<br/>keyed by conflict identity")]
    CACHE --> A1["chg-002 amended"]
    CACHE --> A2["chg-002 rebased"]
    CACHE --> A3["chg-002 re-derived by a<br/>land's descendant cascade"]
    CACHE --> A4["Same conflict shows up<br/>on a peer's stack"]
    A1 --> APPLIED["Resolution replayed — no re-fight"]
    A2 --> APPLIED
    A3 --> APPLIED
    A4 --> APPLIED
    APPLIED --> REOPEN{"Deliberately<br/>reopen?"}
    REOPEN -- yes --> RE["deliberate reopen<br/>(specified · not yet shipped)"]
    REOPEN -- no --> STAY([Stays resolved])

9. Region-level substrate & operation resolutions

Conflicts aren't just per-file blobs — the substrate resolves at the region level. Each conflicted region takes its own choice — ours, theirs, the base, both sides, or a hand-merge — recorded as a per-region resolution that replays on a later land (tovio resolve <path> --region <n> --ours, or the interactive walk, which never writes a conflict marker into your file).

flowchart TB
    F["File cgm.ts"] --> CO["One stored conflict object<br/>(file-granular, content-addressed)"]
    CO --> RG["Regions derived on demand<br/>from base / ours / theirs"]
    RG --> R1["Region A<br/>(non-overlapping edits)"]
    RG --> R2["Region B<br/>(overlapping edits)"]
    R1 --> M1(["Auto-merged"])
    R2 --> OP{"Per-region choice"}
    OP --> OP1["ours / theirs / base"]
    OP --> OP2["take both"]
    OP --> OP3["hand-merge inline<br/>(or --working-tree)"]
    OP --> OP4["--ai proposal<br/>(applied like --ours)"]
    OP1 --> APPLY["Apply through the<br/>normal Resolution path"]
    OP2 --> APPLY
    OP3 --> APPLY
    OP4 --> APPLY

10. Semantic conflicts — advisory by default

A semantic conflict fires when a change breaks references another change relied on (e.g., a renamed symbol). It's a warning by default; policy can promote it to a required check.

flowchart TD
    S["Semantic analyzer<br/>(symbol graph, refs, types)"] --> D{"broken reference?"}
    D -- no --> OK([no conflict])
    D -- yes --> SC["Store a conflict, kind=semantic"]
    SC --> POL{"Protected lane policy<br/>requires semantic check?"}
    POL -- no --> ADV[["Advisory warning<br/>(does not block)"]]
    POL -- yes --> BLK[["Blocks land<br/>until resolved"]]

    classDef warn fill:#ffe,stroke:#aa5;
    classDef stop fill:#fdd,stroke:#c33,color:#600;
    class ADV warn;
    class BLK stop;

11. Divergence reconciles on the lane

Two replicas advancing the same lane to concurrent commits is not an error. On sync TOVIO reconciles the two on the lane into a two-parent commit — it never picks a pointer winner and strands the loser, and never asks you to force-push. Both diverged tips become parents of the new lane tip, so nothing lands off-lane.

flowchart LR
    A[["Replica A<br/>advances main → X"]] --> SYNC{{"sync brings a<br/>divergent tip"}}
    B[["Replica B<br/>advances main → Y"]] --> SYNC
    SYNC --> BASE["merge-base(X, Y)<br/>+ typed 3-way merge<br/>(same engine as land)"]
    BASE --> Q{"merge cleanly?"}
    Q -- yes --> MC[["✓ two-parent merge commit<br/>parents = X and Y"]]
    Q -- no --> CC[["✓ two-parent commit<br/>+ conflict object(s) stored<br/>parents = X and Y"]]
    MC --> LANE["main advances to the<br/>reconciliation tip<br/>(both X and Y reachable)"]
    CC --> LANE

    classDef ok fill:#dfd,stroke:#3a3;
    class MC,CC,LANE ok;

The CRDT / HLC-LWW ref register still runs underneath, but only as the mechanism that propagates that reconciliation pointer between replicas — it governs the lane pointer, not the content.

Prose: Offline & distributed.

12. tovio resolve --ai — the resolver-plugin seam

AI-assisted resolution asks a configured resolver to propose a Resolution and applies an accepted proposal through the same path --ours/--theirs use. A configured ai.provider selects the built-in LLM resolver at the CLI edge; with none configured, the request falls through to the sandboxed plugin framework in resolver mode; if no resolver plugin is bound either, the conflict stays untouched and a typed no-resolver error is returned.

flowchart TD
    C["Open conflict on a path"] --> AI["tovio resolve --ai"]
    AI --> PROV{"ai.provider<br/>configured?"}
    PROV -- yes --> LLM["Built-in LLM resolver<br/>(decrypted sides; a protected path<br/>needs consent + a signed audit<br/>entry before any egress)"]
    PROV -- no --> BOUND{"resolver plugin<br/>bound to<br/>resolve-requested?"}
    BOUND -- no --> NORES[["No-resolver typed error<br/>(REQ-PLUGIN-079)<br/>conflict stays open"]]
    BOUND -- yes --> SB["Sandboxed execution<br/>(WASM/WASI, deny-by-default)"]
    LLM --> PROP{"Proposed Resolution?"}
    SB --> PROP
    PROP -- no --> KEEP["Leave conflict for human"]
    PROP -- yes --> ACCEPT{"Actor accepts?"}
    ACCEPT -- no --> KEEP
    ACCEPT -- yes --> APPLY["Apply via normal<br/>Resolution path<br/>(REQ-PLUGIN-080)"]
    APPLY --> DONE([✓ Resolved])

Full plugin flow: Plugins → Workflows · Plugins → concepts.

13. End-to-end: merge → work → resolve → land

The whole loop on one page.

sequenceDiagram
    autonumber
    actor Dev as Developer
    participant CLI as tovio-cli
    participant Core as tovio-core
    participant Repo as Repository

    Dev->>CLI: tovio land feature/cgm-sync --into my-lane
    CLI->>Core: typed 3-way merge
    Core->>Repo: store conflicts (dexcom.ts, types.ts)
    Core-->>CLI: ✓ merged + N stored
    CLI-->>Dev: success glyph + list

    Dev->>CLI: keep working (commit, switch, sync, stack)
    CLI->>Core: allowed — conflicts don't quarantine

    Dev->>CLI: tovio conflicts
    CLI-->>Dev: each open conflict, its kind, how to resolve it

    Dev->>CLI: tovio resolve src/integrations/cgm/dexcom.ts --theirs
    CLI->>Core: apply Resolution
    Core->>Repo: mark resolved, cache for replay

    Dev->>CLI: tovio land my-lane --into main
    CLI->>Core: land gate: any open conflicts?
    Core-->>CLI: none — proceed
    CLI-->>Dev: ✓ landed

Where to go next

Last reviewed September 9, 2026

Suggest an improvement to this page Not for security reports — see disclosure