152 lines
8.4 KiB
Markdown
152 lines
8.4 KiB
Markdown
# 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:
|
|
|
|
```text
|
|
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.
|