# cli: route `entire recap` to entire-api, team column included

`2bf185a`·

Soph·2w ago·5 files·+65 added/-29 removed

recap always sends the current repo as /me/recap's ?repo= to render the
team/contributors column. entire-api addresses repos by ULID there, not
owner/repo — but entire.io/api documents the mirror id as *exactly* that
repo_id (repo_id = mirror_repos.id; "mirrorId is exactly the repoId
entire-api wants"). The CLI already fetches mirrorId in the same listing
it uses for the cell, so no extra lookup is needed.

- Carry the mirror id through as entireAPIPlacement.RepoID and expose it
from the entire-api client as repoID.
- newRecapClient now returns the correct ?repo= value for its client: the
repo ULID on entire-api, the owner/repo slug on the data API. It prefers
the entire-api cell and, keeping recap's render-through-401 behaviour,
falls back to the data API when not logged in.

Both personal and team recap work on entire-api; no owner/repo→ULID
side-lookup required after all.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

## Sessions

43577009ee6cView transcript

## Changes

5

- cmd/entire/cli

- Mentireapi_cell.go+9/-3

- Mentireapi_cell_test.go+17/-12

- Mentireapi_client.go+13/-5

- Mentireapi_client_test.go+3/-3

- Mrecap.go+23/-6

```go
8 unmodified lines

9
10
11
12
13
12
13
14
15
16
17
18
19
20
21
22
23
24
25
24 unmodified lines

50
51
52
47
53
54
55
56

8 unmodified lines

// entireAPIPlacement is a repo's entire-api routing coordinates, read from its
// mirror: the cell that hosts it (fills {cell} in ENTIRE_API_BASE_URL_TEMPLATE)
// and the jurisdiction that homes it (fills {jurisdiction} in the audience and
// token-minting-core templates). Both must be present for a repo to be routable
// to entire-api; older cores omit them.
// token-minting-core templates). Cell and Jurisdiction must both be present for
// a repo to be routable to entire-api; older cores omit them.
//
// RepoID is the mirror id, which entire.io/api documents as exactly the repo_id
// entire-api uses for repo-scoped params (e.g. /me/recap?repo=<repo_id>) — not
// a separate identifier. It lets repo-scoped calls address the repo by ULID
// without a second lookup.
type entireAPIPlacement struct {
	Cell         string
	Jurisdiction string
	RepoID       string
	ClusterHost  string
}

24 unmodified lines

if cell == "" || jur == "" {
		continue
	}
	p := entireAPIPlacement{Cell: cell, Jurisdiction: jur, ClusterHost: mirrors[i].ClusterHost}
	p := entireAPIPlacement{Cell: cell, Jurisdiction: jur, RepoID: mirrors[i].MirrorId, ClusterHost: mirrors[i].ClusterHost}
	if preferredCluster != "" && strings.EqualFold(mirrors[i].ClusterHost, preferredCluster) {
		return p, true
	}
```

Mcmd/entire/cli/entireapi_cell.go+9/-3

```
6 unmodified lines

7
8
9
10
10
11
12
13
7 unmodified lines

21
22
23
24
25
26
27
28
29
24
25
26
27
28
29
30
31
32
32
33
34
35
36
33
34
35
36
37
38
39
40
41
58 unmodified lines

100
101
102
103
104
105
106
107
108

6 unmodified lines

)

func mirror(cluster, cell, jurisdiction string) coreapi.Mirror {
	m := coreapi.Mirror{ClusterHost: cluster}
	m := coreapi.Mirror{ClusterHost: cluster, MirrorId: cell + "-repo-ulid"}
	if cell != "" {
		m.Cell = coreapi.NewOptString(cell)
	}
7 unmodified lines

t.Parallel()

tests := []struct {
	name      string
	mirrors   []coreapi.Mirror
	preferred string
	wantOK    bool
	wantCell  string
	wantJur   string
	name       string
	mirrors    []coreapi.Mirror
	preferred  string
	wantOK     bool
	wantCell   string
	wantJur    string
	wantRepoID string
}{
	{
		name:     "single routable mirror",
		mirrors:  []coreapi.Mirror{mirror("a.entire.io", "aws-us-east-2", "us")},
		wantOK:   true,
		wantCell: "aws-us-east-2",
		wantJur:  "us",
		name:       "single routable mirror",
		mirrors:    []coreapi.Mirror{mirror("a.entire.io", "aws-us-east-2", "us")},
		wantOK:     true,
		wantCell:   "aws-us-east-2",
		wantJur:    "us",
		wantRepoID: "aws-us-east-2-repo-ulid",
	},
	{
		name:    "no mirrors",
58 unmodified lines

if got.Cell != tt.wantCell || got.Jurisdiction != tt.wantJur {
			t.Fatalf("placement = %+v, want cell %q jurisdiction %q", got, tt.wantCell, tt.wantJur)
		}
		if tt.wantRepoID != "" && got.RepoID != tt.wantRepoID {
			t.Fatalf("placement.RepoID = %q, want %q", got.RepoID, tt.wantRepoID)
		}
	})
}
}
```

Mcmd/entire/cli/entireapi_cell_test.go+17/-12

```
44 unmodified lines

45
46
47
48
48
49
50
51
52
53
54
55
56
57
58
59
2 unmodified lines

62
63
64
57
65
66
67
68
31 unmodified lines

100
101
102
95
103
104
105
106
1 unmodified line

108
109
110
103
111
112
113
114
115
116
117
110
118
119

44 unmodified lines

return placement, nil
}

// newEntireAPIClientForCurrentRepo builds an api.Client pointed at the entire-api
// entireAPIClient bundles an api.Client aimed at an entire-api cell with the
// current repo's ULID (RepoID), so repo-scoped params like /me/recap?repo=
// can address the repo by the id entire-api expects.
type entireAPIClient struct {
	client *api.Client
	repoID string
}

// newEntireAPIClientForCurrentRepo builds a client pointed at the entire-api
// cell hosting the current repo, carrying a jurisdictional identity token.
//
// ok=false with a nil error means "entire-api not in play": either the operator
2 unmodified lines

// data API. Genuine failures (control-plane unreachable, token exchange
// rejected, malformed template) return an error rather than silently masking a
// misconfigured opt-in.
func newEntireAPIClientForCurrentRepo(ctx context.Context, insecureHTTP bool) (*api.Client, bool, error) {
func newEntireAPIClientForCurrentRepo(ctx context.Context, insecureHTTP bool) (*entireAPIClient, bool, error) {
	baseTemplate := api.EntireAPIBaseURLTemplate()
	audTemplate := api.EntireAPIAudienceTemplate()
	if baseTemplate == "" || audTemplate == "" {
31 unmodified lines

if err != nil {
		return nil, false, fmt.Errorf("resolve entire-api token: %w", err)
	}
	return api.NewClientWithBaseURL(token, baseURL), true, nil
	return &entireAPIClient{client: api.NewClientWithBaseURL(token, baseURL), repoID: placement.RepoID}, true, nil
}

// runAuthenticatedActivityAPI runs fn with an authenticated client for the
1 unmodified line

// configured and the repo is routable, otherwise the data API. Both serve the
// same /me/* paths, so fn is agnostic to which client it receives.
func runAuthenticatedActivityAPI(ctx context.Context, errW io.Writer, insecureHTTP bool, fn func(context.Context, *api.Client) error) error {
	client, ok, err := newEntireAPIClientForCurrentRepo(ctx, insecureHTTP)
	ec, ok, err := newEntireAPIClientForCurrentRepo(ctx, insecureHTTP)
	if err != nil {
		return renderDataAPIAuthError(errW, err)
	}
	if !ok {
		return runAuthenticatedDataAPI(ctx, errW, insecureHTTP, fn)
	}
	return fn(ctx, client)
	return fn(ctx, ec.client)
}
```

Mcmd/entire/cli/entireapi_client.go+13/-5

```
15 unmodified lines

16
17
18
19
19
20
21
22
23
24
23
24
25
26
27

15 unmodified lines

t.Setenv(api.EntireAPIBaseURLTemplateEnvVar, "")
	t.Setenv(api.EntireAPIAudienceTemplateEnvVar, "")

client, ok, err := newEntireAPIClientForCurrentRepo(context.Background(), false)
	ec, ok, err := newEntireAPIClientForCurrentRepo(context.Background(), false)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if ok || client != nil {
		t.Fatalf("got (client=%v, ok=%v), want (nil, false) when unconfigured", client, ok)
	if ok || ec != nil {
		t.Fatalf("got (client=%v, ok=%v), want (nil, false) when unconfigured", ec, ok)
	}
}
```

Mcmd/entire/cli/entireapi_client_test.go+3/-3

```
122 unmodified lines

123
124
125
126
126
127
128
129
8 unmodified lines

138
139
140
141
141
142
143
34 unmodified lines

178
179
180
182
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
3 unmodified lines

206
207
208
192
209
210
211
212
196
213
214
215
199
216
217
218
219

122 unmodified lines

if err != nil {
		return err
	}
	client, err := newRecapClient(ctx, f.insecureHTTP)
	client, repoSlug, err := newRecapClient(ctx, f.insecureHTTP)
	if err != nil {
		if errors.Is(err, api.ErrInsecureHTTP) {
			fmt.Fprintf(errW, "ENTIRE_API_BASE_URL is set to an insecure http:// URL (%s). Use https:// for production, or pass --insecure-http-auth for local dev.\n", api.BaseURL())
8 unmodified lines

return err
	}
	rangeKey := f.rangeKey()
	repoSlug := currentRepoSlug(ctx)
	if f.useTUI(interactive.IsTerminalWriter(w), interactive.CanPromptInteractively(), IsAccessibleMode()) {
		return runRecapTUI(ctx, client, recapTUIOptions{
			Range: rangeKey,
34 unmodified lines

// these were all relabelled as keyring read failures via keyringReadError,
// which sent users on wild goose chases when the keyring was fine and the real
// problem was downstream.
func newRecapClient(ctx context.Context, insecureHTTP bool) (*api.Client, error) {
// newRecapClient returns the recap client and the value to pass as /me/recap's
// ?repo= (its team/contributors scope): the current repo's ULID when routed to
// entire-api (which addresses repos by id), or its owner/repo slug on the data
// API (which addresses them by name). Empty when the current repo can't be
// determined — recap then shows the personal side only.
//
// It prefers the entire-api cell for the current repo. recap tolerates not
// being logged in (it renders and lets the server answer 401), so an
// ErrNotLoggedIn from the entire-api attempt falls through to the data API
// rather than erroring; every other failure surfaces.
func newRecapClient(ctx context.Context, insecureHTTP bool) (*api.Client, string, error) {
	ec, ok, err := newEntireAPIClientForCurrentRepo(ctx, insecureHTTP)
switch {
case err != nil && !errors.Is(err, auth.ErrNotLoggedIn):
	return nil, "", err
case ok:
	return ec.client, ec.repoID, nil
}

if insecureHTTP {
	auth.EnableInsecureHTTP()
}
3 unmodified lines

err = nil
}
if err != nil {
	return nil, err
	return nil, "", err
}
if token != "" && !insecureHTTP {
	if err := api.RequireSecureURL(api.BaseURL()); err != nil {
		return nil, fmt.Errorf("base URL check: %w", err)
		return nil, "", fmt.Errorf("base URL check: %w", err)
	}
}
return api.NewClient(token), nil
return api.NewClient(token), currentRepoSlug(ctx), nil
}

func handleRecapFetchError(w io.Writer, err error) error {
