Skip to content

data-api: follow auth context via /.well-known/entire-api.json#1377

Draft
toothbrush wants to merge 4 commits into
mainfrom
cor-389-data-api-context-aware
Draft

data-api: follow auth context via /.well-known/entire-api.json#1377
toothbrush wants to merge 4 commits into
mainfrom
cor-389-data-api-context-aware

Conversation

@toothbrush
Copy link
Copy Markdown
Contributor

@toothbrush toothbrush commented Jun 5, 2026

activity / search / trail / dispatch now pick the right login automatically when you point only at a data-API host:

ENTIRE_API_BASE_URL=https://partial.to entire activity   # no ENTIRE_AUTH_BASE_URL needed

The CLI reads the host's /.well-known/entire-api.json to learn which login servers it trusts (and which audience to exchange for), then selects the matching auth context — same cluster semantics git clone already uses (active-wins-if-eligible → sole → explicit choice). This fixes the cross-core case: active context is a prod entire.io login, but the command targets partial.to.

Falls back to today's static token resolution when the host doesn't advertise discovery (404 / unreachable / 503), so nothing breaks before the server side ships.

Server side: entirehq/entire.io#2277.

Changes

  • clusterdiscovery: generalized to serve both entire-cluster.json and entire-api.json (shared selectContext); DiscoverAPI / ResolveContextForAPI.
  • auth: NewRefreshingResourceProvider (per-context, audience-aware RFC 8693 exchange) + ResolveDataAPIToken (discovery + fallback). Wired into NewAuthenticatedAPIClient, dispatch lookupResourceToken, search resolveSearchToken.
  • Doc: docs/architecture/upstream-host-resolution.md.

Note: audience = the data host origin (https://entire.io / https://partial.to), not an opaque string — confirmed against entire.io's ENTIRE_CORE_JWT_AUDIENCE and entiredb's api-access exchange.

🤖 Generated with Claude Code


Note

High Risk
Changes authentication for all data-API CLI entry points (token exchange, context selection, and discovery fallback), which are security-critical and affect multi-core staging vs prod workflows.

Overview
Data API auth now follows the target host, not only the active context and ENTIRE_AUTH_BASE_URL. Commands that dial ENTIRE_API_BASE_URL (activity, search, trail, dispatch, and NewAuthenticatedAPIClient) call auth.ResolveDataAPIToken instead of TokenForResource on an origin-only URL.

That path fetches /.well-known/entire-api.json, selects a local context with the same rules as git cluster discovery (active-if-eligible → sole eligible → explicit choice), and mints a bearer via NewRefreshingResourceProvider (per-context c.CoreURL, RFC 8693 exchange for the document’s audience). If discovery is missing or unusable (ErrDiscoveryUnavailable), behavior falls back to the old singleton TokenForResource path; real selection failures still surface to the user.

clusterdiscovery adds DiscoverAPI / ResolveContextForAPI, shares selectContext and a hardened fetchWellKnownJSON (HTTPS-only, no redirects) with cluster discovery, and updates login hints. refresh.go centralizes newContextTokenManager and reauthError so ErrNotLoggedIn still unwraps on the discovery path. Architecture docs mark the web/data API resolution slice as done.

Reviewed by Cursor Bugbot for commit f883714. Configure here.

activity/search/trail/dispatch now discover which login server the data
API host trusts (and which audience to exchange for) from its
/.well-known/entire-api.json, then pick the matching auth context and
exchange that context's token — the same cluster semantics git already
uses. So `ENTIRE_API_BASE_URL=https://partial.to entire activity`
authenticates as the partial.to login without also setting
ENTIRE_AUTH_BASE_URL.

Falls back to today's static token resolution when the host doesn't
advertise discovery (404/unreachable/503), so nothing breaks pre-deploy.

- clusterdiscovery: generalized to serve both entire-cluster.json and
  entire-api.json (shared selectContext); DiscoverAPI/ResolveContextForAPI
- auth: NewRefreshingResourceProvider (per-context, audience-aware
  exchange) + ResolveDataAPIToken (discovery + fallback)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: 42efb4b17beb
Copilot AI review requested due to automatic review settings June 5, 2026 02:47
Comment thread cmd/entire/cli/auth/refresh.go Outdated
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds data-API “auth context discovery” so commands that target a data host (activity/search/trail/dispatch + the authenticated API client) can automatically pick the correct login context by fetching /.well-known/entire-api.json from the data host, then exchanging for the advertised audience. It preserves existing behavior by falling back to the current static token resolution when discovery is unavailable.

Changes:

  • Generalize cluster discovery selection logic to also support API-host discovery (/.well-known/entire-api.json) and reuse the same context-selection semantics.
  • Introduce data-API token resolution (auth.ResolveDataAPIToken) and a per-context, audience-aware token exchange provider (auth.NewRefreshingResourceProvider), wiring it into search/dispatch and the authenticated API client.
  • Update architecture documentation to reflect the new discovery mechanism and audience semantics.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
internal/entireclient/clusterdiscovery/resolve.go Generalizes context selection messaging to work for both clusters and API hosts.
internal/entireclient/clusterdiscovery/discovery.go Extracts shared well-known JSON fetch helper and refactors cluster discovery to use it.
internal/entireclient/clusterdiscovery/api_discovery.go Adds API-host discovery parsing and context resolution using trusted_issuers + audience.
internal/entireclient/clusterdiscovery/api_discovery_test.go Adds tests for API discovery behavior, error folding, and context selection semantics.
docs/architecture/upstream-host-resolution.md Documents the new /.well-known/entire-api.json flow and audience exchange behavior.
cmd/entire/cli/search_cmd.go Switches search token resolution to the new discovery-aware resolver.
cmd/entire/cli/dispatch/mode_local.go Switches dispatch token lookup wiring to use the discovery-aware resolver.
cmd/entire/cli/auth/refresh.go Adds NewRefreshingResourceProvider to exchange a context’s login JWT for a resource audience.
cmd/entire/cli/auth/data_api.go Implements ResolveDataAPIToken (discovery + selection + exchange + fallback).
cmd/entire/cli/auth/data_api_test.go Adds tests covering fallback behavior, surfacing selection errors, and exchange parameters.
cmd/entire/cli/api_client.go Updates the authenticated API client to use ResolveDataAPIToken instead of static resolution.

Comment thread internal/entireclient/clusterdiscovery/discovery.go Outdated
Comment thread cmd/entire/cli/auth/refresh.go Outdated
toothbrush and others added 3 commits June 5, 2026 11:47
NewRefreshingLoginProvider and NewRefreshingResourceProvider shared ~30
near-identical lines (validation, tokenmanager.New, the reauth error
switch). Extract newContextTokenManager + contextReauthError; the two
providers now differ only in Refresh() vs Token(req) and the residual
error wording.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: 6f9136693ab0
The "where do I log in" hints now just say `entire login` (plus
`entire auth use` to switch between existing logins) instead of
`ENTIRE_AUTH_BASE_URL=<url> entire login`. The env-var override stays a
power-user mechanism; fully sunsetting it (+ `entire login --server`) is
tracked in COR-393.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: 93d30f60bf97
…very redirects

Two reviewer findings:

- ErrNotLoggedIn lost after discovery (cursor + Copilot): contextReauthError
  returned a plain string, so callers that branch on errors.Is(err,
  ErrNotLoggedIn) (NewAuthenticatedAPIClient/search/dispatch) fell through to
  their generic error — a regression vs the pre-discovery TokenForResource
  path. Wrap the sentinel via a reauthError type that keeps the friendly
  context-named message while unwrapping to the tokenmanager sentinel.

- Redirect-following in fetchWellKnownJSON (Copilot): a trust-root fetch must
  not follow a 3xx to another origin/plaintext. Refuse redirects on a
  shallow-copied client (so the caller's redirect policy is untouched). Low
  real exploitability — the token is never sent to the redirect target — but
  cheap hardening that covers the cluster path too.

Tests: provider error unwraps to ErrNotLoggedIn; cross-origin redirect (to a
server serving a valid doc) is refused rather than followed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: d24466d6862b
@toothbrush
Copy link
Copy Markdown
Contributor Author

@cursor review

Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit f883714. Configure here.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated 4 comments.

Comment thread internal/entireclient/clusterdiscovery/discovery.go
Comment on lines +157 to +158
fmt.Fprint(&b, "\nLog in with `entire login`, then re-run your command.\n"+
"If you already have a login on one of those servers, switch to it with `entire auth use <context>`.")
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Intentional: ENTIRE_AUTH_BASE_URL is being phased out this week in favour of entire login --server (COR-393), so the hint stays as plain entire login for now.

Comment on lines +168 to +172
case errors.Is(err, tokenmanager.ErrReauthRequired):
return &reauthError{
msg: fmt.Sprintf("login session for %q (%s) expired; run `entire login` to re-authenticate", c.Name, coreURL),
sentinel: tokenmanager.ErrReauthRequired,
}
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Intentional — same as above: env-var form is going away in favour of entire login --server (COR-393).

Comment on lines +173 to +177
case errors.Is(err, tokenmanager.ErrNotLoggedIn):
return &reauthError{
msg: fmt.Sprintf("no usable login for %q (%s); run `entire login`", c.Name, coreURL),
sentinel: tokenmanager.ErrNotLoggedIn,
}
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Intentional — same as above: entire login --server will make this per-core actionable (COR-393).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants