Changes touching this path
- Fix #128: pull/apply collapses a concurrent two-writer fork (#129)
Engine `apply_sync` ingests a peer's divergent tip as a *sibling head* and
classifies per-path outcomes, but never merges tips — so a keyholder that had
also advanced its own line was left on two heads with a working tree showing
only its own side (the peer's content in the graph but never materialized).
No CLI verb converged it (`dock merge` is docks-only). This is the gap the
concurrent-agents proof surfaced under a real run.
Add `Workspace::converge_heads(base)` — the peer-side analogue of `merge_dock`
(ADR 0011: keyholders collapse forks on pull+apply). It folds every other head
into our line via `merge_tips`, signs each merge so it travels, then
materializes the merged tree (visibility-aware: a sealed path the identity
can't open stays relayed, not dropped). `cmd_pull` calls it after the batch
loop, passing the pre-pull head as our side, and now also honors
`--porcelain`/`--json` like the other reconciliation verbs.
Proven end to end by docs/evidence/scripts/concurrent-agents-demo.ps1 — both
acts pass: Act 1 (local docks → harbor, real conflict + resolve + buoy),
Act 2 (two identities push concurrently, relay DAG forks, agent's pull
collapses it; public converges with no side dropped, restricted path relays R).
Regression test: converge_heads_collapses_a_two_writer_fork_no_side_dropped.
Git-Author: Connor Miller <53197564+Connor-Miller@users.noreply.github.com>
bb4c849f
Renames are not followed. loot's tree maps a path to an address, so a rename is a delete and an add. This list is the history of the name, not of the bytes.