docs: upstream host & auth-context resolution model · Entire
docs: upstream host & auth-context resolution model
Document the "one core + resource servers" model, what control-plane context resolution now does, and the deferred entire.io well-known (entire-api.json) + audience token-exchange work for the data API family.
Sessions
Changes
Upstream Host & Auth-Context Resolution
How the CLI decides which host to dial and which login (auth context) to authenticate as for every upstream call. The goal is one mental model:
An auth context is a login to one core (the identity provider / login server). Every upstream call resolves to some host. That host either is a core — use the active context's core directly — or it is a resource server that advertises which cores it trusts via a
/.well-knownblob, so the CLI picks the context whose core is trusted and exchanges that context's token for the resource.
There is no separate "auth system" per service. There is one identity model (contexts.json, keyed on CoreURL) and a set of resource servers that accept a core's JWTs.
The pieces
| Role | Service (prod / staging) | Hit by | Trusted-core discovery |
|---|---|---|---|
| Core — IdP and control-plane API, co-located | entire-core (us.auth.entire.io) |
org / repo / project / grant, auth *, login |
none needed — the host is the core |
| Resource: git cluster | entire-server / entiredb |
git-remote-entire (clone/push) |
/.well-known/entire-cluster.json → core_urls |
| Resource: web/data API | entire.io (partial.to) |
activity / search / trail / dispatch |
none today — see Deferred |
contexts.json ($ENTIRE_CONFIG_DIR/contexts.json, shared with entiredb's CLIs) stores each login as {Name, CoreURL, Handle, KeychainService} plus a CurrentContext pointer. CoreURL is the JWT iss — the core that minted the token. entire auth use <ctx> flips CurrentContext.
Resolution per call type
Git cluster (done — internal/entireclient/clusterdiscovery)
ResolveContextForCluster(host) fetches+caches the cluster's /.well-known/entire-cluster.json, reads core_urls, then selects the context: active-context-wins if its CoreURL is among the cores, else the sole eligible context, else an error (zero → login hint; ambiguous → asks for auth use). The token is then exchanged for the cluster.
Control plane (done — this slice)
The host is a core, so there is no discovery. coreapi.New() consults auth.ResolveControlPlaneTarget():
ENTIRE_AUTH_BASE_URLset → that origin, verbatim; bearer via the singleton token manager (TokenForResource). The env var is an unconditional override, so split-host / local-dev invocations are untouched.- else active context → its
CoreURL, with a per-context refreshing bearer (auth.NewRefreshingLoginProvider): the token manager is keyed onc.CoreURLas issuer, so store reads and refresh/STS hit the right core, and an expired access token is silently re-minted from the stored refresh token. This is what makesentire auth use <ctx>actually retargetorg/repo/project/grant. - else (no active context) → the configured default origin +
TokenForResource— the pre-contexts behaviour.
Key files: cmd/entire/cli/auth/control_plane.go (resolver), cmd/entire/cli/auth/refresh.go (per-context refreshing provider), internal/coreapi/client.go (New() + providerSource), cmd/entire/cli/api/base_url.go (AuthBaseURLOverridden).
Why the per-context path and not the singleton manager: the singleton (auth/exchange.go:defaultManager) is built once with Issuer = api.AuthBaseURL(). When the active context lives on a different core, both its token-store reads and its STS/refresh endpoint are keyed on the wrong host. The per-context provider fixes that by keying on c.CoreURL.
Deferred: the web/data API (entire.io)
activity / search / trail / dispatch dial ENTIRE_API_BASE_URL (default entire.io) and statically resolve their token. entire.io is a resource server — it validates incoming JWTs against statically-configured trusted issuers (ENTIRE_CORE_BASE_URL + ENTIRE_CORE_TRUSTED_ISSUERS) and a fixed audience (ENTIRE_CORE_JWT_AUDIENCE, e.g. entire-web-api) — but it does not advertise any of this. So the CLI can't map an entire.io host back to a core/context the way it does for a git cluster.
To close the gap (so ENTIRE_API_BASE_URL=https://partial.to entire activity auto-selects the right context without also setting ENTIRE_AUTH_BASE_URL):
- Server:
entire.iogrows a/.well-known/entire-api.jsonadvertising its trust roots. Unlike the cluster blob (core_urlsonly), the API blob must also carry the audience the CLI exchanges for:{ "issuer": "https://us.auth.partial.to", "trusted_issuers": ["https://us.auth.partial.to", "https://eu.auth.partial.to"], "audience": "entire-web-api", "jwks_uri": "https://us.auth.partial.to/.well-known/jwks.json" } - CLI: generalize the cluster resolver into a shared "host → trusted issuers → pick context" path whose source of trusted issuers is pluggable (cluster.json / api.json / the core itself), then exchange the context's token for the advertised audience via
auth.TokenForResource. Wire it into theactivity/search/trail/dispatchconstructors (NewAuthenticatedAPIClient,dispatch.NewCloudClient,search.Search).