# Narrow planner relay policy input

`1a9efa9`→[main](/content/gh/entireio/git-sync/commits/main/index.html)·  
  
Soph·3mo ago·4 files·+21 added/-12 removed

## Sessions

441e23e1e3f5View transcript

## Changes

4

- docs

- Mrewrite-issue-list.md+1/-1

- internal

- planner

- Mplanner_test.go+4/-5

- Mrelay.go+10/-4

- syncer

- Msyncer.go+6/-2

```
448 unmodified lines

- All mapping validation happens before network activity. Status: done
- Capability negotiation is centralized and enforced consistently. Status: partial
  Source-side fetch capability checks now live behind `gitproto.RefService` methods, but some target-side relay gating still relies on orchestration wiring rather than a fully typed capability boundary.
  Source-side fetch capability checks now live behind `gitproto.RefService` methods, and planner relay gating now consumes a narrower syncer-level policy instead of importing `gitproto` types directly. Some target-side relay decisions still rely on orchestration wiring rather than a fully unified capability model, so this remains partial.
- Relay strategies are separate packages with explicit inputs and outputs. Status: done
- Tag creation is correct whether or not a pack transfer is needed. Status: done
- Stats are concurrency-safe. Status: done
```

Mdocs/rewrite-issue-list.md+1/-1

```
9 unmodified lines

```go
"github.com/go-git/go-git/v6/plumbing"
"github.com/go-git/go-git/v6/plumbing/object"
"github.com/go-git/go-git/v6/storage/memory"
"github.com/soph/git-sync/internal/gitproto"
"github.com/soph/git-sync/internal/validation"
```

698 unmodified lines

// RelayTargetPolicy captures only the target-side facts that affect planner
// relay eligibility decisions.
type RelayTargetPolicy struct {
    CapabilitiesKnown bool
    NoThin            bool
}

// CanBootstrapRelay checks whether all desired target refs are absent on the target,
// making a bootstrap relay possible.
func CanBootstrapRelay(

// CanIncrementalRelay checks whether all plans are eligible for the incremental
// relay fast-path (fast-forward branch updates + new tag creates).
func CanIncrementalRelay(force, prune, dryRun bool, plans []BranchPlan, target gitproto.TargetFeatures) (bool, string) {
    if force || prune || dryRun {
        return false, "incremental-disabled-by-force-prune-or-dry-run"
    }
    if len(plans) == 0 {
        return false, "incremental-no-plans"
    }
    if !target.Known {
        if !target.CapabilitiesKnown {
            return false, "incremental-missing-target-capabilities"
        }
        if target.NoThin {
            // Further checks...
        }
    }
}

// RelayFallbackReason returns the reason why relay was not used.
func RelayFallbackReason(force, prune, dryRun bool, plans []BranchPlan, target gitproto.TargetFeatures) string {
    if ok, reason := CanIncrementalRelay(force, prune, dryRun, plans, target); ok {
        return reason
    } else if ok, reason := CanFullTagCreateRelay(plans); ok {
        return reason
    }
}

Minternal/planner/planner_test.go+4/-5

```
1 unmodified line

import (
    "github.com/go-git/go-git/v6/plumbing"
    "github.com/soph/git-sync/internal/gitproto"
)

// ... More code for tests
```

Minternal/planner/relay.go+10/-4

```
383 unmodified lines
```  
sourceService := s.sourceService
    targetAdv := s.targetAdv
    targetFeatures := gitproto.TargetFeaturesFromAdvRefs(targetAdv)
    targetPolicy := planner.RelayTargetPolicy{
        CapabilitiesKnown: targetFeatures.Known,
        NoThin:            targetFeatures.NoThin,
    }
    targetPusher := gitproto.NewPusher(s.targetConn, s.targetAdv, cfg.Verbose)
    // ... Logic to handle relaying actions
```

Minternal/syncer/syncer.go+6/-2

```
