Skip to content

cli: cell-routing foundation — client factory, generic repo→cell resolver, multi-cell fan-out#1641

Open
Soph wants to merge 4 commits into
mainfrom
soph/cell-routing-foundation
Open

cli: cell-routing foundation — client factory, generic repo→cell resolver, multi-cell fan-out#1641
Soph wants to merge 4 commits into
mainfrom
soph/cell-routing-foundation

Conversation

@Soph

@Soph Soph commented Jul 5, 2026

Copy link
Copy Markdown
Collaborator

https://entire.io/gh/entireio/cli/trails/762

Stacked on #1592. Extracts the CLI's cell-routing plumbing into a shared foundation so multi-cell commands (code search, #1616, and anything after it) stop growing parallel copies of it. The backend model gives the CLI exactly three routing shapes, mirroring the entire.io BFF: repo-scoped → one cell, user-scoped /me → home cell, repo-set queries → fan out and merge client-side (no server-side aggregator exists). #1592 covers the second; this PR generalizes the first and builds the third.

Commits

  • auth.CellClientFactoryNewEntireAPICellClient resolved the login subject (discovery + refresh) and ran the RFC 8693 exchange on every call. Identity tokens are per-jurisdiction, not per-cell, so a fan-out over N cells was about to pay N× all of it. The factory resolves the subject once and mints at most one token per jurisdiction; NewEntireAPICellClient stays as the single-cell wrapper, existing callers unchanged.
  • cell_target.go — mechanical generalization of experts_cell_target.go (resolveRepoCellTarget, cellCoreClient, cellResolveTimeout): nothing in it was experts-specific, and it's the repo-scoped shape every cell-routed command needs. No behavior change.
  • cell_fanout.go — the repo-set shape, extracted from what cli: add code search with multi-region fan-out (ENT-993) #1616 currently inlines into search_cmd.go:
    • groupReposByCell: repo index → per-cell groups, deterministic order.
    • resolveCellBaseURLs: cluster-catalog join on ClusterSlugCluster.Slug. The catalog exposes no cell fieldcli: add code search with multi-region fan-out (ENT-993) #1616's current join of the cell name against Slug only works when the two happen to coincide; a test pins the correct key.
    • fanOutCells: parallel per-cell calls under a per-cell timeout through one shared CellClientFactory; partial failures are isolated per result slot, merge semantics stay with the command.
  • Documents the three routing shapes in CLAUDE.md.

Intended consumer

#1616 (code search) keeps its codesearch client, merge/sort/dedup semantics, and output rendering, and replaces its inline groupReposByCell/searchAllCells/searchCell orchestration with these helpers — fixing the slug join and the per-cell token minting along the way.

Testing

mise run test:ci green (unit + integration + both canary suites), -race on the fan-out tests. New tests: factory token reuse per jurisdiction, group determinism, slug-join pinning, per-cell failure isolation.

🤖 Generated with Claude Code


Note

Medium Risk
Touches authentication (RFC 8693 exchange caching) and cross-region API routing; behavior is largely refactored with tests, but fan-out and factory concurrency affect how multi-cell commands authenticate and fail partially.

Overview
Introduces shared entire-api cell routing so multi-cell commands can reuse one plumbing layer instead of duplicating BFF-style routing.

Auth: NewEntireAPICellClient now delegates to CellClientFactory, which resolves the login subject once and caches one jurisdictional identity token per jurisdiction (not per cell). NewEntireAPICellClient remains the single-cell entry point; existing callers are unchanged.

Repo-scoped routing: Experts-style resolution is renamed and generalized to resolveRepoCellTarget (from resolveExpertsCellTarget), with shared cellCoreClient / cellResolveTimeout naming. Behavior is unchanged; NewAuthenticatedEntireAPICellClient calls the new resolver.

Repo-set routing: New cell_fanout.go provides groupReposByCell, resolveCellBaseURLs (catalog join on ClusterSlugCluster.Slug, not cell name), and generic fanOutCells (parallel per-cell calls, per-cell timeout, isolated partial failures, one factory per operation).

Docs: CLAUDE.md documents the three routing shapes (repo-scoped, /me home cell, repo-set fan-out) and the per-jurisdiction token rule.

Minor: shared writeJSON test helper in corecmd_test.go; comment tweak in entireapi_client.go for timeout naming.

Reviewed by Cursor Bugbot for commit 73b1b2e. Configure here.

@Soph Soph requested a review from a team as a code owner July 5, 2026 16:28
Copilot AI review requested due to automatic review settings July 5, 2026 16:28

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 3 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

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

Reviewed by Cursor Bugbot for commit 73b1b2e. Configure here.

Comment thread cmd/entire/cli/cell_fanout.go
Comment thread cmd/entire/cli/cell_fanout.go
Comment thread cmd/entire/cli/cell_fanout.go

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

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 extracts and generalizes the CLI’s entire-api cell-routing plumbing into shared helpers, enabling future multi-cell commands (e.g., code search) to fan out across cells without duplicating routing/auth logic. It introduces a reusable cell client factory to avoid repeated subject resolution and redundant identity-token exchanges during multi-cell operations, and adds a generic repo→cell resolver plus fan-out grouping/join logic aligned with the BFF routing model.

Changes:

  • Introduces auth.CellClientFactory to build cell clients from one resolved subject and cache identity tokens per jurisdiction.
  • Generalizes the repo-scoped routing resolver into resolveRepoCellTarget and adds multi-cell fan-out helpers (groupReposByCell, resolveCellBaseURLs, fanOutCells).
  • Adds unit tests for token reuse, deterministic grouping/join behavior, and per-cell failure isolation; documents the three routing shapes in CLAUDE.md.

Reviewed changes

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

Show a summary per file
File Description
cmd/entire/cli/entireapi_client.go Updates timeout comment reference to the generalized cell resolver timeout.
cmd/entire/cli/corecmd_test.go Adds shared writeJSON helper for corecmd test stub handlers.
cmd/entire/cli/cell_target.go Renames/generalizes experts-specific repo→cell routing into shared resolver + interfaces.
cmd/entire/cli/cell_target_test.go Updates resolver tests to the generalized names; shares test constants/stubs.
cmd/entire/cli/cell_fanout.go Adds new multi-cell fan-out foundation: grouping, catalog join, parallel fan-out.
cmd/entire/cli/cell_fanout_test.go Adds tests for grouping determinism, correct catalog join key, fan-out semantics.
cmd/entire/cli/auth/cell_data_api.go Refactors NewEntireAPICellClient to use CellClientFactory; adds per-jurisdiction token caching.
cmd/entire/cli/auth/cell_data_api_test.go Adds coverage pinning “one token per jurisdiction” reuse behavior via the factory.
cmd/entire/cli/api_client.go Switches repo-scoped client construction to resolveRepoCellTarget.
CLAUDE.md Documents the three cell-routing shapes and the per-jurisdiction token rule.

Comment thread cmd/entire/cli/cell_fanout.go Outdated
Comment thread cmd/entire/cli/auth/cell_data_api.go
@Soph

Soph commented Jul 5, 2026

Copy link
Copy Markdown
Collaborator Author

Reworked the temporary test-helper commit to match #1640's new shape: the corecmd stub handlers now route through printJSON (the #1606 deslop convention) instead of reintroducing the deleted writeJSON. Still falls out on rebase once #1640 merges.

@Soph Soph force-pushed the soph/cell-routing-foundation branch from 73b1b2e to c20a5dc Compare July 5, 2026 16:53
@Soph Soph force-pushed the soph/cli-me-endpoints-to-entire-api branch from 32e8a17 to 86a21a8 Compare July 5, 2026 18:53
@Soph Soph force-pushed the soph/cell-routing-foundation branch from c20a5dc to 48d5ac5 Compare July 5, 2026 18:56
evisdren
evisdren previously approved these changes Jul 5, 2026

@evisdren evisdren left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

minor comment from claude, otherwise good:


  In tokenFor (cell_data_api.go), slot.mu.Lock() is held while exchangeJurisdictionToken makes an HTTP call. The comment on CellClientFactory.mu says "never
  held across I/O", but mintSlot.mu is held across I/O — that's the design (single-flight per jurisdiction). This is fine for correctness and the intent is
  correct (block concurrent callers for the same jurisdiction while one mints), but the comment on CellClientFactory.mu could mislead readers into thinking
  no mutex in the struct is held across I/O. Consider clarifying that the factory-level mu is never held across I/O, while slot-level mu intentionally is.

  File: cmd/entire/cli/auth/cell_data_api.go (around tokenFor)```

Soph and others added 4 commits July 6, 2026 08:41
…tion

NewEntireAPICellClient resolved the stored login subject (discovery +
login refresh) and ran the RFC 8693 exchange on every call. For a
single-cell command that's fine, but a multi-cell fan-out (one request
per cell hosting the caller's repos, the BFF's code-search pattern)
would pay all of it once per cell — even though identity tokens are
per-jurisdiction, not per-cell: every cell in a jurisdiction accepts
the same token.

CellClientFactory resolves the subject once at construction and caches
minted identity tokens by jurisdiction; ClientFor(target) reuses them
across cells. NewEntireAPICellClient stays as the single-cell wrapper
(factory of one), so existing callers are unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Entire-Checkpoint: 935f91398e25
resolveExpertsCellTarget was experts-named but nothing in it is
experts-specific: it maps any repo (ULID or owner/repo) to the entire-api
cell hosting it, exactly what every future repo-scoped or fan-out command
needs (code search is next, PR #1616). Rename the file and identifiers —
resolveRepoCellTarget, cellCoreClient, newCellCoreClient,
cellResolveTimeout — and keep experts as the documented example consumer.
Mechanical rename; no behavior change.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Entire-Checkpoint: 89b4d13bd2b0
The data plane has no server-side cross-cell aggregator: a query over all
of the caller's repos must be fanned out to each cell hosting them and
merged client-side (the entire.io BFF's code-search pattern). PR #1616
inlines that orchestration into search_cmd.go; this extracts the generic
layer so code search — and any later repo-set command — shares one
implementation:

- groupReposByCell: repo index → per-cell groups, deterministic order.
- resolveCellBaseURLs: cluster-catalog join on ClusterSlug↔Cluster.Slug.
  The catalog exposes no cell field, so joining the cell name against
  Slug (as #1616 currently does) only works when the two coincide.
- fanOutCells: parallel per-cell calls under a per-cell timeout, one
  shared auth.CellClientFactory (subject resolved once, one identity
  token per jurisdiction), partial failures isolated per result slot.
  Merge semantics stay with the command.

Also documents the three cell-routing shapes in CLAUDE.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Entire-Checkpoint: 842e421e74a8
- Bound resolveCellBaseURLs's catalog lookup with cellResolveTimeout,
  like resolveRepoCellTarget: a hung core must not stall the command
  before the fan-out even starts.
- Refuse a concrete baseURL without a jurisdiction: minting a
  home-jurisdiction token and dialing a foreign cell with it is exactly
  the mismatch resolveRepoCellTarget refuses; such a group stays on
  home routing.
- Key groupReposByCell on (cell, jurisdiction): blank-cell index rows in
  different jurisdictions no longer collapse into one group routed by
  whichever repo came first.
- Single-flight token mints per jurisdiction (mintSlot) instead of one
  factory-wide mutex held across the exchange: a slow region's mint no
  longer burns other cells' per-cell deadlines. Failed mints cache
  nothing, so the next caller retries.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Entire-Checkpoint: ab23b05bf985
@Soph Soph force-pushed the soph/cell-routing-foundation branch from 9ef8a5c to 299b273 Compare July 6, 2026 06:42
Base automatically changed from soph/cli-me-endpoints-to-entire-api to main July 6, 2026 07:44
@Soph Soph dismissed evisdren’s stale review July 6, 2026 07:44

The base branch was changed.

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.

3 participants