# Harden relay strategy boundaries

## Sessions

## Changes

6
- docs

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

- internal

- strategy

- bootstrap

- Mbootstrap.go+4/-3

- Mbootstrap_test.go+93

- incremental

- Mincremental.go+5/-1

- Mincremental_test.go+72/-1

- syncer

- Msyncer.go+1

```
156 unmodified lines

157
158
159
160
160
161
162
163
164
123 unmodified lines

288
289
290
291
292
293
294

```

Current rewrite note:
- Ownership of stream lifecycle is clearer than on `main`, and the rewrite now has direct tests for key pack-stream close behavior on success and error paths.
- This still wants a fuller close-audit around all strategy-level failure paths before it should be considered fully done.
- Direct strategy-level error-path tests now verify that relay bootstrap and incremental paths close source pack streams when pushes fail.
- This still wants a fuller close-audit around deeper batched failure paths before it should be considered fully done.

### 6. Protocol v2 tag fetches request `include-tag` without capability gating

- Major strategy and protocol concerns were extracted.
- The strategy packages now depend on narrower source-side interfaces instead of the full concrete `gitproto.RefService`.
- The strategies now also depend on a narrower target-side push executor instead of raw target transport state, and direct strategy tests exercise those boundaries.
- Incremental relay policy decisions are now injected consistently instead of splitting between one injected check and one hard-coded planner call.
- Some helpers still carry broad parameter structs, so this remains partial rather than fully complete.

## Performance And Scalability

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

```go
// Execute runs the bootstrap strategy (one-shot or batched).
func Execute(ctx context.Context, p Params, relayReason string) (Result, error) {
	if p.TargetPusher == nil {
		return Result{Relay: true, RelayMode: "bootstrap", RelayReason: relayReason}, fmt.Errorf("bootstrap strategy requires TargetPusher")
	}

// GitHub large-repo preflight
	if batchLimit, ok := githubBatchLimit(ctx, p); ok {
		p.BatchMaxPack = batchLimit

result := Result{
		Plans: plans, Relay: true, RelayMode: "bootstrap", RelayReason: relayReason,
	}
	if p.TargetPusher == nil {
		return result, fmt.Errorf("bootstrap strategy requires TargetPusher")
	}

if p.BatchMaxPack > 0 {
		return executeBatched(ctx, p, plans, result)
```

Minternal/strategy/bootstrap/bootstrap.go+4/-3

```
4 unmodified lines

5
6
7
8
9
10
11
12
13
14
15
16
17
129 unmodified lines

147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
53 unmodified lines

216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
36 unmodified lines

292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340

```

"context"
	"errors"
	"io"
	"net/http"
	"net/http/httptest"
	"testing"

"github.com/go-git/go-git/v5/plumbing"
	"github.com/go-git/go-git/v5/plumbing/storer"
	"github.com/go-git/go-git/v5/plumbing/transport"

"github.com/soph/git-sync/internal/gitproto"
	"github.com/soph/git-sync/internal/planner"

```go
func (f fakeBootstrapPusher) PushPack(ctx context.Context, cmds []gitproto.PushCommand, pack io.ReadCloser) error {
	return f.pushPack(ctx, cmds, pack)
}
```

type trackingReadCloser struct {
	io.Reader
	closed bool
}

func (r *trackingReadCloser) Close() error {
	r.closed = true
	return nil
}

func TestExecuteOneShotClosesPackOnPushError(t *testing.T) {
	mainRef := plumbing.NewBranchReferenceName("main")
	mainHash := plumbing.NewHash("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
	pack := &trackingReadCloser{Reader: bytes.NewReader([]byte("PACK"))}

_, err := Execute(context.Background(), Params{
		SourceService: fakeBootstrapSource{
			fetchPack: func(_ context.Context, _ *gitproto.Conn, _ map[plumbing.ReferenceName]gitproto.DesiredRef, _ map[plumbing.ReferenceName]plumbing.Hash) (io.ReadCloser, error) {
			return pack, nil
			},
		},
		TargetPusher: fakeBootstrapPusher{
			pushPack: func(_ context.Context, _ []gitproto.PushCommand, pack io.ReadCloser) error {
				_ = pack.Close()
				return errors.New("boom")
			},
		},
		DesiredRefs: map[plumbing.ReferenceName]planner.DesiredRef{
			mainRef: {
				SourceRef:  mainRef,
				TargetRef:  mainRef,
				SourceHash: mainHash,
				Kind:       planner.RefKindBranch,
			},
		},
	}, "empty target")
	if err == nil || err.Error() != "push target refs: boom" {
		t.Fatalf("unexpected error: %v", err)
	}
	if !pack.closed {
		t.Fatal("expected pack to be closed on push error")
	}
}

```

Minternal/strategy/bootstrap/bootstrap_test.go+93

```

29 unmodified lines

30
31
32
33
34
35
36
14 unmodified lines

51
52
53
54
55
56
57
58
59
8 unmodified lines

68
69
70
67
71
72
73
74

29 unmodified lines

MaxPackBytes int64
 Verbose      bool
 CanRelay     func(bool, bool, bool, []planner.BranchPlan) (bool, string)
 CanTagRelay  func([]planner.BranchPlan) (bool, string)
}

// Result holds the outcome of an incremental relay.

...
