brick/docs/research/worktree-comparison.md
trvon 050b1b105b
Some checks failed
build / build-linux (push) Successful in 1m2s
build / build-macos (push) Has been cancelled
build / release (push) Has been cancelled
feat: support explicit cross-worktree structural diffs
2026-09-05 12:34:35 -06:00

8.4 KiB

Comparing models from different worktrees

Status: explicit relocation policy implemented; automatic Git verification deferred.

What blocks the comparison

Building::ensure_compatible_with in src/model.rs rejects different absolute roots before diff::between compares the stable-ID indexes. Source node IDs already use repository-relative paths and semantic ownership. The absolute root is execution provenance, not a component of those IDs.

The repository node ID is stable_id("repository", ".") for every scan. It is a graph-local identity, not evidence that two models represent the same repository. Its display name is the checkout directory basename. Consequently, simply deleting the root check would admit unrelated repositories and would still show a repository-name change between differently named worktrees.

Reproduction

build/worktree-comparison/probe.py creates an isolated Git repository and a detached linked worktree at the same revision. It scans both, invokes the current CLI, and compares the raw node/edge records without modifying either model. It also scans an unrelated directory with identical source, and imports the same Fold finding from separate report paths in the two worktrees.

Observed results in build/worktree-comparison/results.json:

  • The real CLI rejects the linked-worktree baseline because scan roots differ.
  • The linked worktrees have the same canonical Git common directory.
  • All source and repository node IDs and all source edge IDs match.
  • No nodes/edges are added or removed; only the repository node's name differs.
  • An unrelated scan also has the same repository node ID.
  • Identical imported findings retain the same finding/edge IDs in this fixture, but the Flags edge evidence differs because report provenance is absolute.

This is a functional reproduction, not a performance benchmark. All fixtures and raw models are under ignored build/; no user source files were involved.

Implemented first stage

The diff/check API and CLI support an explicit relocation mode:

brick check current.json --min-score 0 \
  --baseline earlier-main.json \
  --allow-root-relocation \
  --diff-json comparison.json

The flag is a caller assertion that these roots denote corresponding scan roots. It does not prove Git identity. It is useful immediately for existing schema-4 artifacts, including cases where the earlier worktree no longer exists.

Contract:

  1. Strict same-root comparison remains the default. Reject the flag without a baseline. Do not change the shared strict compatibility helper used elsewhere.
  2. Add a diff-specific comparison policy that relaxes only root equality. Both models must still pass full validation and match building schema, repository graph identity, and complete scan scope, including exclusions/P4 frontend.
  3. Never rewrite either input model, canonical root, node ID, or opaque evidence provenance string. Do not use path-suffix matching to guess equivalence.
  4. Emit a versioned diff contract retaining baseline_root, current_root, and a comparison basis such as explicit-root-equivalence. Existing diff-v1 only records one root, so relocated output needs diff-v2 reader behavior. Building schema 4 can remain unchanged for this explicit-policy stage.
  5. Keep full-record equality initially: checkout display-name changes and real evidence/provenance differences remain inspectable changed records. A later structural-only view can classify display metadata separately, but must not silently discard changes from existing count gates.

This removes the same-path baseline dance without claiming repository identity that old artifacts do not contain. It intentionally does not make an unchanged checkout with a different display name appear as an entirely empty full-record diff. Source relationships themselves should remain unchanged.

Automatic verification as a separate stage

At scan time, record repository-origin evidence independently of graph-local IDs and absolute scan roots. For linked worktrees on one machine, a canonical Git common directory provides useful local evidence. Also record the scan root's path relative to the Git top level: equal Git common directories alone would otherwise let a repository-root scan compare with a subtree scan.

Capture this evidence with the artifact. Looking up today's filesystem alone cannot establish where an older model came from. Include origin method/version and relevant revision/dirty-state provenance; revision equality must not be a compatibility requirement because comparing revisions is the purpose of a diff.

A common-directory fingerprint will not survive moves or independent clones. Remote URLs are mutable and can be shared by forks or mirrors; branch names, HEAD hashes, and content equality also cannot serve as sole repository identity. Cross-machine/clone comparison therefore needs a persistent explicitly assigned project identity or another qualified origin contract, not automatic URL guesses. New mandatory reader-visible origin semantics would require a building-schema version change and migration rules for legacy artifacts.

Acceptance tests for implementation

  • Different-root models fail by default and compare only under explicit policy.
  • Identical source in linked worktrees has no added/removed source entities or changed source edges; repository display metadata remains visible.
  • A real function/edge change is detected across roots with unchanged stable IDs preserved for unrelated declarations.
  • Invalid graph records, unsupported schemas, different exclusions/frontends, and scope mismatches still fail under relocation mode.
  • Unrelated repositories never auto-match merely because repository IDs match.
  • Imported evidence differences remain complete and input bytes remain unchanged.
  • Diff output records both roots, direction, policy, and its new schema version.
  • If Git verification is added, test linked worktrees versus independent repos, subdirectory scans, unavailable/deleted roots, moves, and legacy artifacts.
  • Repeat the positive/negative diff benchmark three times and retain raw models, diffs, resolved comparison policy, and run manifests.

Implementation: diff::between_with_policy accepts ComparisonPolicy::ExplicitRootEquivalence. All new diff output uses schema 2, with comparison_policy, baseline_root, and current_root; root remains a current-root alias. The default API and shared compatibility helper remain strict.

Next: qualify automatic repository-origin matching independently.

Implementation validation (2026-09-05)

TASK: worktree-comparison / PHASE: complete.

Context: the strict compatibility helper, full-record stable-ID diff, and the linked-worktree reproduction above established the root-only limitation. Implementation preserves that strict helper and adds the explicit diff policy.

  • The CLI regression first failed on the missing flag, then passed after implementation.
  • Focused tests cover deleted checkouts, original input bytes, both output roots, strict/explicit policies, visible display-name and evidence changes, count gates, and rejection of incompatible schemas, scopes, repository IDs, and malformed graphs.
  • ./scripts/quality-gate.sh passed: formatting, Rust tests and clippy for default and P4 pilot configurations, Python tests, harness self-tests, and language/import benchmarks. Log: build/worktree-comparison/quality-gate.log.
  • Four structural-diff benchmark cases passed three repeats each. Same-root and relocated positives both produced node counts (+1, -1, changed 1) and edge counts (+2, -1, changed 1). Both clean negatives produced all-zero counts. Each case's complete diff JSON was deterministic across repeats; relocated cases also rejected strict comparison on every repeat.
  • Raw models, diff-v2 JSON, commands, resolved plan and run manifest: build/benchmarks/structural_diff/20260905T183330Z/.
  • The saved real linked-worktree probe models compare successfully with the flag: source-only output has one repository-name change and no edge changes; Fold output additionally preserves one evidence change. Outputs: build/worktree-comparison/relocated.json and relocated-fold.json.

No extractor, score, or automatic repository-identity claim is made by this change. Used context and citations: src/model.rs, src/diff.rs, src/main.rs, docs/architecture/structural-diff.md, and the preserved reproduction artifacts. Changed implementation, tests, and this handoff are indexed in YAMS at completion.