fix(experts): route to the repo's entire-api cell with an identity token#1588
Merged
Conversation
entire-api repo-scoped routes require a jurisdictional identity token (scope=openid), not the narrowed api-access bearer minted for entire.io. When the data host is the BFF, resolve the home cell from core clusters, exchange the login JWT for the jurisdiction audience, and dial the cell directly (COR-666). Co-authored-by: Cursor <cursoragent@cursor.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Routes the entire experts CLI command to a jurisdiction-specific entire-api cell by exchanging the login JWT for a jurisdictional identity token (openid scope) and dialing the cell directly, addressing prod 401s caused by using the narrower api-access bearer against repo-scoped entire-api routes.
Changes:
- Switch
entire expertsto construct a cell-routed API client instead of the generic data API client. - Add
auth.NewEntireAPICellClientto discover trusted login context, derivehome_jurisdiction, resolve the correct cell base URL (when starting from the entire.io BFF), and perform the token exchange. - Add unit tests covering routing behavior and BFF detection.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| cmd/entire/cli/experts_cmd.go | Uses the new cell-routed authenticated client for experts endpoints. |
| cmd/entire/cli/auth/cell_data_api.go | Implements cell discovery + jurisdiction token exchange flow for entire-api access. |
| cmd/entire/cli/auth/cell_data_api_test.go | Adds tests validating home-cell routing and direct-cell base URL behavior. |
| cmd/entire/cli/api_client.go | Exposes a CLI-level constructor for the new entire-api cell client. |
… auth
The experts cell client had several defects surfaced by an adversarial audit
against the entire-api cell + entire.io BFF ground truth:
- Cross-jurisdiction routing: it dialed the CALLER's home cell (login JWT
home_jurisdiction), so a repo homed in another region always failed with a
misleading "not onboarded/no access" error. Now the cli layer resolves the
repo's OWN cell + jurisdiction from the control plane (coreapi mirrors for
owner/repo, GetRepo for a ULID; mapped to a cell apiUrl via ListClusters) and
passes it as an explicit CellTarget — mirroring how the BFF mints one token
per target jurisdiction and fans out to the repo's cell. Resolution is
best-effort: any failure falls back to home-jurisdiction routing, so the
common same-region path can never regress.
- Staging (partial.to) was unsupported: isEntireIOBFF only matched *.entire.io
and the audience/core templates were hardcoded to prod. Cell-vs-BFF detection
is now environment-agnostic (a direct cell is any host with ".api."), and the
audience/core follow the environment family (entire.io vs partial.to) derived
from the configured data host, with the discovered core as fallback.
- Local dev sent the exchange to PRODUCTION core (the prod template ignored the
discovered local core). A loopback discovered core is now honored verbatim so
the local login JWT is exchanged at the local core.
- Security: the login JWT / identity token targets (core + cell apiUrl) are now
gated to https (loopback/--insecure-http-auth excepted), and home_jurisdiction
is validated as a DNS label before templating into URLs.
- Clearer errors: a cross-region 404 ("repo not in this region") and the
repo-not-found path now tell the user the repo may be homed in another region
instead of implying it isn't onboarded / accessible.
Adds unit tests for per-repo cell resolution, cross-jurisdiction routing,
environment-family templates, loopback core, and jurisdiction-label validation.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Address the second adversarial audit pass over the cell-routing fix: - Bound the best-effort control-plane resolution with a 5s deadline so a reachable-but-hung control plane degrades to home-jurisdiction routing instead of stalling the command (the "any failure falls back" contract must hold for slow cores, not just erroring ones). - Skip failed/suspended mirror placements (not just archived) when choosing the repo's cell, so an unhealthy placement can't manufacture false cross-region ambiguity or become a sole "active" host. - Apply ENTIRE_CORE_BASE_URL_TEMPLATE unconditionally (placeholder-less = single fixed core), matching the BFF and the audience handler. - Lowercase the catalog jurisdiction before the auth-layer label check (DNS is case-insensitive) so a non-lowercase value routes instead of hard-failing. - requireSafeExchangeURL now affirmatively requires https (not merely "not http"), rejecting ftp/ws/scheme-relative/empty targets for the login JWT. - Introduce an injectable control-plane seam (expertsCoreClient + newExpertsCoreClient; listMirrorsForRepo takes a mirrorLister interface) so the resolver is unit-testable against a fake control plane. Tests: requireSafeExchangeURL guard table; prod + fixed-template core URLs; resolveExpertsCellTarget across ULID / owner-repo / multi-region-fallback / no-match / lowercased-jurisdiction; a real cluster-host join; and a non-tautological direct-cell test (asserts exchange happens, no cluster resolution). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Live prod testing showed the entire-api cell returns a bare "Service Unavailable" (no "code search" text) when a natural-language query hits a cell without code search configured. The previous substring match missed that and surfaced a raw "fetch experts: API error" wrap. Treat any 503 on a query as the code-search-unavailable case (path scopes still fall through to the generic error). Adds a test for the bare-503 query case. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- allowInsecure gates only on selected.CoreURL (+ --insecure-http-auth), not the data origin, matching the sibling ResolveDataAPIToken; a loopback data API no longer relaxes HTTPS for a non-loopback core. - Drop the duplicate "refresh login token:" wrap; NewRefreshingLoginProvider already prefixes it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Soph
approved these changes
Jul 1, 2026
Soph
added a commit
that referenced
this pull request
Jul 1, 2026
Reconcile onto the cell-routing client that landed with the experts work (#1588) instead of a parallel mechanism. `activity` and `recap` call the /me/* endpoints entire-api serves, so they now go through auth.NewEntireAPICellClient (home-jurisdiction routing, target=nil) — one routing/token path across the CLI. Cell routing is a best-effort upgrade: any failure building the cell client (no cell for the region, not logged in, discovery/exchange error) falls back to the data API, which also serves /me/* and yields the canonical auth errors — so existing users are unaffected until their region has a cell. recap keeps its render-through-401 behaviour via that fallback. recap's team column still needs the repo ULID for /me/recap?repo=; currentRepoID resolves it best-effort from the mirror id (which entire-api treats as the repo_id), empty → personal recap only. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Entire-Checkpoint: dc4fb6be0484
Soph
added a commit
that referenced
this pull request
Jul 5, 2026
Reconcile onto the cell-routing client that landed with the experts work (#1588) instead of a parallel mechanism. `activity` and `recap` call the /me/* endpoints entire-api serves, so they now go through auth.NewEntireAPICellClient (home-jurisdiction routing, target=nil) — one routing/token path across the CLI. Cell routing is a best-effort upgrade: any failure building the cell client (no cell for the region, not logged in, discovery/exchange error) falls back to the data API, which also serves /me/* and yields the canonical auth errors — so existing users are unaffected until their region has a cell. recap keeps its render-through-401 behaviour via that fallback. recap's team column still needs the repo ULID for /me/recap?repo=; currentRepoID resolves it best-effort from the mirror id (which entire-api treats as the repo_id), empty → personal recap only. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Entire-Checkpoint: dc4fb6be0484
Soph
added a commit
that referenced
this pull request
Jul 5, 2026
Reconcile onto the cell-routing client that landed with the experts work (#1588) instead of a parallel mechanism. `activity` and `recap` call the /me/* endpoints entire-api serves, so they now go through auth.NewEntireAPICellClient (home-jurisdiction routing, target=nil) — one routing/token path across the CLI. Cell routing is a best-effort upgrade: any failure building the cell client (no cell for the region, not logged in, discovery/exchange error) falls back to the data API, which also serves /me/* and yields the canonical auth errors — so existing users are unaffected until their region has a cell. recap keeps its render-through-401 behaviour via that fallback. recap's team column still needs the repo ULID for /me/recap?repo=; currentRepoID resolves it best-effort from the mirror id (which entire-api treats as the repo_id), empty → personal recap only. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Entire-Checkpoint: dc4fb6be0484
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
https://entire.io/gh/entireio/cli/trails/715
Problem
entire experts --repo owner/repofails with401 Not authenticated, even while logged in and whileentire activity/entire searchwork. Repo-scoped entire-api routes (GET /api/v1/repos,POST /api/v1/repos/{id}/experts) require a jurisdictional identity token (scope=openid,aud= the cell's jurisdiction host, signed by Core JWKS) — not theentire:api-accessbearer minted for theentire.ioBFF. The BFF also does not proxy these routes for bearer callers, so the CLI must dial the entire-api cell directly.Fix
Mint the identity token (token-exchange from the login JWT →
audience=<jurisdiction host>,scope=openid— the exact shape the entire.io BFF'smintJurisdictionTokenuses) and dial the cell directly.coreapimirrors forowner/repo;GetRepofor a ULID) and maps it to a cellapiUrl+ jurisdiction viaListClusters— mirroring how the BFF mints one token per target jurisdiction and fans out to the repo's cell. Resolution is best-effort with a 5s deadline: any failure/timeout/ambiguity falls back to home-jurisdiction routing, so the common same-region path can't regress..api.); the audience/core follow theentire.io/partial.tofamily of the configured host.apiUrl) are affirmatively gated tohttps(loopback /--insecure-http-authexcepted);home_jurisdictionis validated as a DNS label before URL templating.404and the repo-not-found path tell the user the repo may be homed in another region; a natural-language query against a cell without code search surfaces a clean message.Verified live against production
Ran the branch binary with a real login (read-only):
experts . --repo entireio/cli --json{"repo_full_name":"entireio/cli","source":"db","profiles":[]}— repo resolved + cell reached, 401 goneexperts cmd/entire/cli/ --repo entireio/cliexperts "…query…" --repo entireio/cliexperts . --repo entireio/does-not-existEmpty provenance + query-503 are backend-side (entire-api/entiredb work in progress), not this CLI change. The genuine cross-region path routes correctly in code and falls back safely, but wasn't exercised live (no repo homed in another region available).
Checks
go build ./...clean ·mise run lint0 issues ·mise run test:cigreen (unit + integration + e2e canary)Entire trail: https://entire.io/gh/entireio/cli/trails/715
🤖 Generated with Claude Code