Skip to content

fix(cluster): send a terminated cluster's in-cluster bookmarks to its overview - #1954

Merged
dawsontoth merged 4 commits into
stagefrom
claude/1931-terminated-cluster-in-cluster-pages
Oct 14, 2026
Merged

dawsontoth merged 4 commits into
stagefrom
claude/1931-terminated-cluster-in-cluster-pages

Conversation

@dawsontoth

@dawsontoth dawsontoth commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

A fox park ranger points a line of woodland animals carrying old keys away from a collapsed treehouse and toward a notice-board hut

⊙ Problem

A sign-in remembered from before a cluster was terminated still opened its apps, databases, APIs, operations, status, logs and config pages (#1931). Those pages sent operations to hosts central manager had torn down. The overview also checked that sign-in against the dead host, and nothing ever forgot it.

🤔 Should every in-cluster page refuse a terminated cluster?

Yes. #1830 and #1926 already treat a terminated cluster as having nothing left to work with. These pages only talk to its instances. A removed self-hosted cluster needs a decision; see Alternatives.

💡 Solution

  • The in-cluster guard sends a deleted cluster to its overview before any sign-in work. With no sign-in, the overview replaces /sign-in as the landing page.
  • The overview no longer checks a deleted cluster's sign-in.
  • Studio forgets a terminated cluster's sign-ins, its own and every instance's, when termination succeeds and when any of its pages renders.
  • Live clusters look the same. The overview's self-hosted and setup branches, which never used the cluster sign-in, stop checking it too.

⚖️ Alternatives

  • Forget in the route guard: guards also run on hover preloads, so a hover would delete stored credentials.
  • Read the instances only after termination: one failed read would leave every instance's credential behind. The record is also read alongside the DELETE.
  • Check in the two route definitions instead of inside the guard: two call sites that must stay ahead of the sign-in work.

🤔 What about a self-hosted cluster removed from Fabric?

Its in-cluster pages redirect too, but its sign-ins are kept. Its servers may still run. Forgetting them locally would leave their sessions open after an app sign-out. Keeping them lets the next app sign-out log them out. Changing terminatedClusterSignIns would reverse it.

🤔 Should Studio forget nothing when it can't read the cluster?

I chose yes. If both reads around a successful terminate fail, Studio can't tell whether the cluster was self-hosted. It forgets nothing; the next visit to any of its pages clears the sign-ins. The alternative clears the cluster's own sign-in and risks dropping a live self-hosted session.

🔧 Changes

✅ Verification

End-to-end route test (b) renders the whole app. Not tried against a live terminated cluster on stage.

Fails-on-base and mutation checks
  • Guard redirect, against stage: all 112 route-matrix cases fail with expected undefined to be '/org-1/clu-1', and the router tests stay on the bookmarked page.
  • Overview, terminate and forget, against this PR's first head (349e6f8): the overview tests see one user_info call, the terminate and layout tests find every sign-in still stored, and the whole-app test finds them too.
  • Mutations, each turning at least one test red: removing the guard redirect; putting useInstanceAuth back in the overview's deleted branch; dropping the layout's forget; forgetting on TERMINATING; keying the forget on the polled record; dropping the self-hosted exception; dropping the read-back after the DELETE; clearing the cluster id when neither read succeeds; dropping the batch's dedupe and its per-id catch, which each have their own test; forgetting from the route guard.
The route matrix
  • Deleted shapes: TERMINATED, TERMINATING, REMOVED, FAILED with terminatedAt.
  • Pages: apps, databases, apis, operations, status, logs, config at the cluster level, and the same seven under instance/ins-1/ (apps is the instance index).
  • For each: a remembered direct sign-in lands on the overview without a sign-in check; a remembered Fabric Connect flag lands there without a reconnect.
  • Controls: RUNNING and FAILED without terminatedAt still render every page on the remembered sign-in.

Closes #1931

🤖 Generated with Claude Code

Related PRs: #1956 overlaps (both edit authStore.ts and the root DESIGN.md index; this adds a separate method beside signOutLocally)
Complexity: medium

Review-Coverage: authored=claude; ran=gemini,codex,cursor-composer; adjudicated=domain; declined=cursor-grok,cursor-kimi,cursor-muse,muse; rounds=5; full=1 @ dd9d36f

Review-Attention: study ~15m (sensitive: authStore.ts; decisions: unreadable-cluster-forgets-nothing, layout-owned-cleanup, selfhosted-keeps-signins, read-alongside-plus-retry, do-less-alternative; raised: degraded review) @ dd9d36f

… overview

A sign-in remembered from before a cluster was terminated let a bookmark
to its apps, databases, APIs, operations, status, logs or config page
(cluster level and under instance/$instanceId) past
checkClusterInstanceAuthenticationBeforeLoad, which returned early on any
existing sign-in. The page then sent operations to a host central manager
had already torn down.

The guard now calls redirectAwayFromDeletedCluster first, before it
re-establishes Fabric Connect, checks the remembered sign-in, or
auto-connects, so a deleted cluster lands on its overview, which says the
cluster is gone. The remembered sign-in is left in place: cleaning it up
belongs at cluster scope, not in a child guard that also runs on
hover-intent preloads.

Closes #1931

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

Coverage Report

Status Category Percentage Covered / Total
🔵 Lines 79.41% 14532 / 18299
🔵 Statements 79.36% 15423 / 19433
🔵 Functions 74.81% 3879 / 5185
🔵 Branches 74.05% 11317 / 15282
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
src/features/auth/store/authStore.ts 91.4% 82.93% 91.35% 91.86% 117, 203-214, 219-222, 270, 291, 312-323, 331, 336-339, 441-444, 814, 819, 896, 920
src/features/cluster/ClusterHome.tsx 43.02% 47.57% 40% 44.57% 46-49, 53, 63, 67, 73-85, 102-112, 117-119, 123-134, 234-376, 441-448
src/features/cluster/ClusterLayout.tsx 100% 100% 100% 100%
src/features/cluster/redirectAwayFromDeletedCluster.ts 100% 100% 100% 100%
src/features/cluster/terminatedClusterSignIns.ts 100% 75% 100% 100%
src/features/clusters/mutations/terminateCluster.ts 83.33% 66.66% 66.66% 83.33% 38-39
src/features/instance/instanceLayoutRoute.ts 86.11% 82.35% 75% 88.57% 22-23, 134-135
Generated in workflow #2369 for commit dd9d36f by the Vitest Coverage Report Action

@gemini-code-assist gemini-code-assist Bot 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.

Code Review

This pull request ensures that bookmarks or deep links to in-cluster pages (such as apps, databases, APIs, operations, status, logs, and config) for a terminated cluster are redirected to the overview page before any authentication checks or sign-in restorations occur. This prevents the application from attempting to connect to a torn-down host using remembered credentials. The changes include updating the design documentation, integrating the redirect guard into checkClusterInstanceAuthenticationBeforeLoad, and adding comprehensive unit tests to verify the redirection behavior for both direct page loads and deep links. No review comments were provided, so there is no feedback to address.

@dawsontoth
dawsontoth marked this pull request as ready for review October 14, 2026 16:22
@dawsontoth
dawsontoth requested a review from a team as a code owner October 14, 2026 16:22
@dawsontoth
dawsontoth marked this pull request as draft October 14, 2026 16:51
dawsontoth and others added 3 commits October 14, 2026 13:15
… them from its overview

The overview called useInstanceAuth(clusterId) before its deleted-cluster
branch, so opening a terminated cluster's overview with a remembered
sign-in sent user_info to the dead host. ClusterHome now mounts that
hook only in ManagedClusterHome, which renders for a live cluster.

Nothing ever cleared a terminated cluster's sign-ins either. A leftover
one costs a logout to the dead host at every app sign-out, and a stored
Basic Auth credential stays in localStorage for good.
authStore.signOutEntitiesLocally(ids) signs out each id locally, once
per unique id, and never throws. It is called:

- by terminateCluster, once the DELETE succeeds: the cluster at once,
  then each instance read back from the cluster record, since the
  organization's cluster list carries no instances;
- by ClusterTerminated on mount, for a terminate done in another tab,
  by another member or by central manager. It skips TERMINATING, and a
  route guard never does this, since guards run on hover preloads.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…et the rest from every page

Review follow-ups:

- A self-hosted cluster removed from Fabric may still be running. Forgetting
  its sign-ins locally, with no logout, left their cookie sessions open after
  an app sign-out. terminatedClusterSignIns now returns none for one, so they
  stay remembered and the next app sign-out still logs them out.
- terminateCluster reads the cluster record alongside the DELETE, and reads it
  back afterwards only if that read failed, so one failed read no longer
  leaves every instance's credential behind.
- The forget moves from ClusterTerminated into ClusterLayout, so every page of
  a terminated cluster runs it, Usage included. It is keyed on the IDs, not on
  the polled record.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… never fetch from ClusterLayout

- When neither cluster read succeeds, terminateCluster can't tell whether the
  cluster was self-hosted, so it now forgets nothing rather than the
  cluster's own sign-in. ClusterLayout forgets the sign-ins on the next visit.
- ClusterLayout reads the cluster query with enabled: false. It only needs
  what the route already loaded and what other observers poll, and an
  enabled observer would refetch on every window focus from in-cluster
  pages that never watched the cluster.
- Comments that restated the code are trimmed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@dawsontoth
dawsontoth marked this pull request as draft October 14, 2026 20:54
@dawsontoth
dawsontoth marked this pull request as ready for review October 14, 2026 21:00
@dawsontoth
dawsontoth added this pull request to the merge queue Oct 14, 2026
Merged via the queue into stage with commit 0b48a41 Oct 14, 2026
7 checks passed
@dawsontoth
dawsontoth deleted the claude/1931-terminated-cluster-in-cluster-pages branch October 14, 2026 21:04
dawsontoth added a commit that referenced this pull request Oct 14, 2026
… servers, then forget them

A self-hosted cluster's servers may outlive its removal from Fabric, so
#1954 kept its remembered sign-ins rather than forget them with no logout.
Removing one from Studio now ends those sessions instead.

authStore.signOutEntities(ids) starts one best-effort logout per
remembered id, built the way app sign-out builds them (the remembered
credential, a 10s timeout, no response interceptors), forgets every id
locally whatever the servers answer, and never rejects. terminateCluster
calls it for a self-hosted cluster's own sign-in and each instance's once
the DELETE succeeds, without making the removal wait. A Fabric cluster's
terminate is unchanged, and a removed self-hosted cluster is still never
cleared on render.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
dawsontoth added a commit that referenced this pull request Oct 15, 2026
… servers, then forget them

A self-hosted cluster's servers may outlive its removal from Fabric, so
#1954 kept its remembered sign-ins rather than forget them with no logout.
Removing one from Studio now ends those sessions instead.

authStore.signOutEntities(ids) starts one best-effort logout per
remembered id, built the way app sign-out builds them (the remembered
credential, a 10s timeout, no response interceptors), forgets every id
locally whatever the servers answer, and never rejects. terminateCluster
calls it for a self-hosted cluster's own sign-in and each instance's once
the DELETE succeeds, without making the removal wait. A Fabric cluster's
terminate is unchanged, and a removed self-hosted cluster is still never
cleared on render.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

A remembered sign-in still opens a terminated cluster's in-cluster pages

2 participants