# cli: route `entire activity` to entire-api when configured

`efe23fd`·

Soph·2w ago·4 files·+194 added/-1 removed

Assemble the entire-api client for the current repo and point `activity`
at it. The command already calls /me/activity and /me/commits — the exact
paths entire-api serves — so this is a routing change, not a response
rewrite:

- auth.ResolveEntireAPIToken mints a jurisdictional identity token for
an explicit audience (no /.well-known; entire-api serves none),
exchanged from the active login context.
- newEntireAPIClientForCurrentRepo ties it together: resolve the repo's
mirror → cell/jurisdiction, fill the base-URL/audience templates, mint
the token, return a client pointed at https://{cell}.api.entire.io.
- runAuthenticatedActivityAPI prefers that client and falls back to the
data API when the ENTIRE_API_* templates are unset or the repo isn't
routable, so existing users are unaffected.

Fallback is silent; genuine failures (control plane down, token rejected,
bad template) surface. recap moves next.

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

## Sessions

690550a49bd7View transcript

## Changes

4

- cmd/entire/cli

- Mactivity_cmd.go+1/-1

- auth

- Aentire_api.go+40

- Aentireapi_client.go+111

- Aentireapi_client_test.go+42

```
55 unmodified lines

56
57
58
59
59
60
61
62

55 unmodified lines

}

func runActivity(ctx context.Context, w, errW io.Writer) error {
	return runAuthenticatedDataAPI(ctx, errW, false, func(ctx context.Context, client *api.Client) error {
	return runAuthenticatedActivityAPI(ctx, errW, false, func(ctx context.Context, client *api.Client) error {
		// Non-interactive fallback: piped output or accessibility mode
		if !interactive.IsTerminalWriter(w) || IsAccessibleMode() {
			return runActivityStatic(ctx, w, client)
```

Mcmd/entire/cli/activity_cmd.go+1/-1

```
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

package auth

import (
	"context"

"github.com/entireio/cli/cmd/entire/cli/api"
)

// ResolveEntireAPIToken mints a bearer for entire-api (the per-cell product
// API). entire-api verifies that the token's audience equals the jurisdiction
// it serves, so audienceOrigin must be the jurisdictional identity-token
// audience the caller derived from the repo's mirror and
// ENTIRE_API_AUDIENCE_TEMPLATE — NOT the entire-api host.
//
// Unlike ResolveDataAPIToken there is no /.well-known discovery (entire-api
// serves none): the audience is supplied explicitly and the token is exchanged
// from the active login context. That context's core must therefore be the one
// that signs tokens for this jurisdiction — true for single-core deployments;
// minting at a foreign jurisdiction's core (ENTIRE_CORE_BASE_URL_TEMPLATE) is a
// follow-up.
//
// Callers honouring --insecure-http-auth must call EnableInsecureHTTP first, as
// the data-API path does.
func ResolveEntireAPIToken(ctx context.Context, audienceOrigin string) (string, error) {
	c, ok, err := activeContext()
	if err != nil {
		return "", err
	}
	if !ok {
		return "", &reauthError{msg: "not logged in; run `entire login`", sentinel: ErrNotLoggedIn}
	}

origin := api.OriginOnly(audienceOrigin)
	allowInsecure := insecureHTTPEnabled() || isLoopbackHTTP(c.CoreURL)
	provider, err := NewRefreshingResourceProvider(c, origin, nil, allowInsecure)
	if err != nil {
		return "", err
	}
	return provider(ctx)
}
```

Acmd/entire/cli/auth/entire_api.go+40

```
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111

package cli

import (
	"context"
	"errors"
	"fmt"
	"io"
	"strings"

"github.com/entireio/cli/cmd/entire/cli/api"
	"github.com/entireio/cli/cmd/entire/cli/auth"
	"github.com/entireio/cli/cmd/entire/cli/gitremote"
	"github.com/entireio/cli/internal/coreapi"
)

// errRepoNotRoutable means the repo has no mirror carrying both a cell and a
// jurisdiction, so the CLI can't address its entire-api. It is not fatal:
// callers fall back to the data API.
var errRepoNotRoutable = errors.New("repo has no entire-api-routable mirror (missing cell/jurisdiction)")

// resolveCurrentRepoPlacement resolves the entire-api placement for the repo in
// the current working directory, via its "origin" remote. It lists the repo's
// mirrors through the control plane (c) and selects a routable one, returning
// errRepoNotRoutable when the repo is unmirrored or its mirrors predate
// cell/jurisdiction metadata.
func resolveCurrentRepoPlacement(ctx context.Context, c *coreapi.Client) (entireAPIPlacement, error) {
	forge, owner, repo, err := gitremote.ResolveRemoteRepo(ctx, "origin")
	if err != nil {
		return entireAPIPlacement{}, fmt.Errorf("resolve current repo: %w", err)
	}
	provider, ok := forgeToMirrorProvider(forge)
	if !ok {
		return entireAPIPlacement{}, fmt.Errorf("unsupported forge %q for entire-api routing", forge)
	}

mirrors, err := listMirrorsForRepo(ctx, c, provider, strings.ToLower(owner), repo)
	if err != nil {
		return entireAPIPlacement{}, fmt.Errorf("list mirrors for %s/%s: %w", owner, repo, err)
	}

placement, ok := selectRoutablePlacement(mirrors, "")
	if !ok {
		return entireAPIPlacement{}, fmt.Errorf("%s/%s: %w", owner, repo, errRepoNotRoutable)
	}
	return placement, nil
}

// newEntireAPIClientForCurrentRepo builds an api.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
// hasn't set the ENTIRE_API_BASE_URL_TEMPLATE / ENTIRE_API_AUDIENCE_TEMPLATE
// pair, or the repo has no routable mirror — so the caller falls back to the
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) {
	baseTemplate := api.EntireAPIBaseURLTemplate()
	audTemplate := api.EntireAPIAudienceTemplate()
	if baseTemplate == "" || audTemplate == "" {
		return nil, false, nil
	}

core, err := coreapi.New()
	if err != nil {
		return nil, false, fmt.Errorf("entire-api routing needs the control plane: %w", err)
	}
	placement, err := resolveCurrentRepoPlacement(ctx, core)
	if err != nil {
		if errors.Is(err, errRepoNotRoutable) {
			return nil, false, nil
		}
		return nil, false, err
	}

baseURL, err := api.BuildEntireAPIBaseURL(baseTemplate, placement.Cell)
	if err != nil {
		return nil, false, fmt.Errorf("build entire-api base URL: %w", err)
	}
	audience, err := api.BuildEntireAPIAudience(audTemplate, placement.Jurisdiction)
	if err != nil {
		return nil, false, fmt.Errorf("build entire-api audience: %w", err)
	}

if insecureHTTP {
		auth.EnableInsecureHTTP()
	} else if err := api.RequireSecureURL(baseURL); err != nil {
		return nil, false, fmt.Errorf("entire-api base URL check: %w", err)
	}

token, err := auth.ResolveEntireAPIToken(ctx, audience)
	if err != nil {
		return nil, false, fmt.Errorf("resolve entire-api token: %w", err)
	}
	return api.NewClientWithBaseURL(token, baseURL), true, nil
}

// runAuthenticatedActivityAPI runs fn with an authenticated client for the
// activity/recap surface: the entire-api cell for the current repo when it is
// 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)
	if err != nil {
		return renderDataAPIAuthError(errW, err)
	}
	if !ok {
		return runAuthenticatedDataAPI(ctx, errW, insecureHTTP, fn)
	}
	return fn(ctx, client)
}
```

Acmd/entire/cli/entireapi_client.go+111

```
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

package cli

import (
	"context"
	"testing"

"github.com/entireio/cli/cmd/entire/cli/api"
)

// When the entire-api templates are unset, the client builder must short-circuit
// to (nil, false, nil) — signalling "fall back to the data API" — without
// touching the control plane. This is the default path for every user who
// hasn't opted into entire-api routing.
func TestNewEntireAPIClientForCurrentRepo_UnconfiguredFallsBack(t *testing.T) {
	// Cannot t.Parallel(): mutates process env via t.Setenv.
	t.Setenv(api.EntireAPIBaseURLTemplateEnvVar, "")
	t.Setenv(api.EntireAPIAudienceTemplateEnvVar, "")

client, 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)
	}
}

// Both templates are required: with only the base URL set, routing stays off
// (an audience-less token would be rejected by entire-api anyway).
func TestNewEntireAPIClientForCurrentRepo_RequiresBothTemplates(t *testing.T) {
	// Cannot t.Parallel(): mutates process env via t.Setenv.
	t.Setenv(api.EntireAPIBaseURLTemplateEnvVar, "https://{cell}.api.entire.io")
	t.Setenv(api.EntireAPIAudienceTemplateEnvVar, "")

_, ok, err := newEntireAPIClientForCurrentRepo(context.Background(), false)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if ok {
		t.Fatal("got ok=true with only the base template set, want false")
	}
}
```
