Supervised Reconciliation

The three regimes place collaboration — arbitrating overlapping intentions, the manual-merge / review model — above the engine: “Commit provides none”. Cooperative Discipline says the same from the other side: when scope cannot be decomposed, “the supervisor is yours to build”. This page documents one implementation of that supervisor: an API that identifies, surfaces, and reconciles a reconstructed conflict over a merge the engine has already performed.

It is additive: a set of free functions over CommitDatabase’s public API, with no change to the engine, the storage format, or the runtime. It ships inside Viper and reaches Python through dsviper.

A conflict is reconstructed, not reported

The engine has no notion of conflict. Mechanical reduction linearises streams and collapses overlapping intent by structural rule, signalling nothing. A pairwise merge is target-wins and non-commutative; when several heads meet, reduce_heads() seeds the fold with the most recent head and folds the rest into it in ascending CommitId order (their SHA-1 content hashes). The resolved state is therefore deterministic, but the determinant is the opcode shape, which head is most recent, and the hash ordering of the rest, not the authors’ intent — so to an observer, which intent the merge keeps is arbitrary. This is the index’s own caveat: “which sequence is applied when several heads meet is an application strategy, not an engine guarantee”.

This layer does not change that. It reconstructs a notion of conflict as an analysis overlay: after a merge, it recomputes each stream’s typed delta and reports the loci where one stream’s intent did not survive. The conflict is manufactured on replay; it is not something the engine stores or knows.

The headless triad

No user interface is part of this layer. Both seams are plain data, so the presentation tier is the consumer’s choice — QML tooling, a CLI, a web review, or an automated policy.

Step

API

Produces

Identify

CommitMergeAnalyzer.analyze_merge(db, merge)

CommitMergeAnalysis with the reconstructed conflicts

Surface

CommitMergeConflict (read-only, serialisable)

{attachment, key, path, base/ours/theirs/merged}

Reconcile

CommitMergeAnalyzer.reconcile(db, merge, [...], label)

a survival commit, child of the merge

The supervisor reads the conflicts, decides per conflict, and expresses each decision as a CommitMergeResolution. Accepting the merge requires no resolution.

Conflicts arrive grouped per document — CommitMergeAnalysis.documents() returns one CommitMergeDocument per (attachment, key), the unit reconcile operates on; conflicts() is the flattened view across all of them.

Why the anchor is post-merge

The choice cannot be made before the merge. The merge result depends on the opcode shape — an absolute set re-baselines the document and is then overwritten target-wins, while incremental path-based operations combine without loss — and a value-level three-way comparison cannot predict which case applies. The only authority is state(merge), the result the engine actually produced.

So the layer endures the merge, reads merge_state = db.state(merge), and defines lost intent against it: a stream’s intent is lost at an opcode’s locus iff that opcode’s effect is not already present in merge_state. The check is per opcode type — a Set_Union survived iff its elements are present, a Document_Update(path, value) iff path resolves to value, and so on. A conflict is therefore anchored on a path (the opcode locus), not on a whole document.

Reconciliation is a survival commit

To make a chosen value survive, the layer authors a follow-up Mutations commit, child of the merge: it copies merge_state, places each decree at its path, and diffs the result against merge_state. “Accept the merge” yields an empty diff, so the merge simply stands and no commit is created.

No patch algebra is invented: the layer builds the survival commit with the engine’s own diff, applied by the same deterministic evaluator. The round-trip

apply(merge_state, diff(merge_state, chosen)) == chosen

is therefore the diff/apply law itself, not a property the layer proves. What the layer does guarantee is that this holds for every value shape — and that guarantee is the coarsening, not the equation: the decree stays fine-grained where the container round-trips element by element, and falls back to a whole-field Document_Update where it would not (see Granularity).

Granularity and its ceiling

The decree’s reach mirrors the merge’s own nature, container by container:

Container

Reconciliation

Set / Map

fine, per element / entry (union, subtract, update)

Structure

per field (Document_Update, sibling fields preserved)

XArray / Vector

field-scoped Document_Update (whole field)

A decree is anchored on the conflict’s own path — the finest locus the merge localised the lost intent to — and the supervisor dictates only the value that should survive there (CommitMergeResolution(conflict, chosen)). reconcile applies a recursive deep Document_Update at that path, so the merge’s sibling fields and leaves are preserved untouched; the blast radius is bounded to the locus, not the whole document. Sequence reordering and element-level XArray merge are out of scope.

When there is no base

When the two streams share no unambiguous common ancestor — a criss-cross history, or two independent roots — there is no base to anchor detection against. The layer degrades to a 2-way scan (candidates are every key where the streams differ) rather than failing; CommitMergeAnalysis.two_way() reports this mode. Survival is base-free either way, so reconciliation is identical.

Honest bounds

  • It inherits the engine’s reduction; it does not repair it. The merge it reconciles is still hash-ordered and target-wins. This layer makes the lost intent observable and correctable — nothing more.

  • It is a supervisor, not the supervisor. Other supervised regimes — a semantic gate that refuses commits, a service that mediates allocation of strong invariants — occupy the same row and are not provided here. See Cooperative Discipline.

  • Intent beyond commit headers is not recoverable. The layer surfaces that values collided and what each was, never why an author wanted it.

The full class surface — signatures, accessors, and a runnable example — is the API reference: Merge Reconciliation.

See Also