# Hide Dedicated Topology Details from External Customers

## Background

Security and compliance review found that the Dedicated cluster Topology Details table exposes two kinds of internal infrastructure information to external customers: the PD component and detailed storage specifications. Clinic UI issue pingcap-inc/clinic-ui#735 requires the customer-facing table to remove the PD row and the Storage Info column while leaving the internal staff view unchanged.

The browser currently calls `GET /orgs/{oid}/clusters/{cid}/resource-pool/components`. That Clinic API response includes PD and storage details for authorized external Dedicated users, so hiding only the rendered cells would leave the same information visible in browser network tools. The change therefore needs both API response redaction and UI presentation filtering.

## Goal

- External users viewing a Dedicated cluster must not receive PD component rows or storage specifications from the browser-facing Clinic API.
- The customer-facing Clinic UI must not render the PD row or Storage Info column.
- Internal staff must continue to receive and see the complete topology.
- Premium, BYOC, resource-pool, and other non-Dedicated topology views must retain their current behavior.
- The cluster-internal Data Proxy facade must retain the complete component response.

## Current Behavior

- `pkg/api/cluster/cluster_nextgen.go` uses one component-loading implementation for the legacy browser route and the internal Data Proxy facade.
- The browser handler returns the loaded component objects without user- or tier-specific redaction.
- `packages/clinic-web/src/pages/cluster/cloud/components/ResourcePoolTopo.tsx` always renders every returned component and always includes the Storage Info column.
- Clinic UI already obtains the authoritative internal-user flag from `useUserStore((state) => state.user?.internal)` elsewhere on the same page.

## Target Contract

For `GET /orgs/{oid}/clusters/{cid}/resource-pool/components`:

- External user plus Dedicated cluster:
  - omit components whose normalized component type is `PD`;
  - retain non-PD component fields but clear the `storages` value so no storage type, size, IOPS, or throughput is returned;
  - keep all other component fields unchanged.
- Internal user or non-Dedicated cluster: preserve the existing response.
- Do not change `GET /internal/data-proxy/v1/orgs/{orgId}/clusters/{clusterId}/components`.

For the Clinic UI Dedicated topology table:

- Treat only an explicit `internal=true` profile as permission to show internal details.
- For every other profile state, remove the Storage Info column and filter both `PD` and `COMPONENT_TYPE_PD` representations.
- Preserve all non-PD rows and all other columns.
- Apply the presentation change only to Dedicated clusters.

## Affected Modules and Routes

### Clinic backend

- Go module: `github.com/pingcap-inc/clinic` at the repository root.
- Package: `pkg/api/cluster`.
- Browser route: `GET /clinic/api/v1/orgs/{oid}/clusters/{cid}/resource-pool/components`.
- Entrypoint: `cmd/server/main.go`.
- Docker build target: `docker_build_server`.
- Image: `clinic`.
- Clinic CD component: `clinic`.
- app-delivery application: `clinic-api`.
- Kubernetes workload: `Deployment/clinic`.

The shared internal facade is a compatibility boundary and is intentionally excluded from redaction. The `internal_api` image and `Deployment/internal-api` do not require deployment because the changed runtime behavior is confined to the browser handler registered by `cmd/server`.

### Clinic UI

- Non-Go tool: `packages/clinic-web` in `pingcap-inc/clinic-ui`.
- Component: `ResourcePoolTopo`.
- Artifact and image: the built `packages/clinic-web/dist` packaged as `clinic-ui`.
- Deployment workflow app input: `clinic-ui`.
- app-delivery application: `clinic-ui`.
- Kubernetes workload: `Deployment/clinic-ui`.

## Implementation Steps

1. Add a browser-response redaction helper in `pkg/api/cluster` that:
   - recognizes both Dedicated PD component type spellings after normalization;
   - creates shallow component copies so the original datasource result is not mutated;
   - excludes PD and clears storage details for retained components.
2. Apply that helper only for external Dedicated users in `GetResourcePoolComponents`, after authorization and component loading and before JSON serialization.
3. Add safe, trace-correlated lifecycle logging only when the request is in the external Dedicated redaction scope. Cover authorization failure, component-loading failure, and successful redacted completion without adding logs to internal or non-Dedicated traffic. Log only bounded counts, deployment type, redaction state, and necessary internal identifiers.
4. Add focused Go regression tests covering external Dedicated redaction, internal Dedicated preservation, non-Dedicated preservation, both PD type spellings, and non-mutation of the datasource result.
5. Update `ResourcePoolTopo` to derive the internal-user flag from the existing user store, gate the behavior on Dedicated tier, filter normalized PD rows, and conditionally include the Storage Info column.
6. Use the filtered rows for both the Ant Design table and its empty-state decision.

## Exclusions

- No change to component discovery or data returned by TiDB Management Service.
- No change to RBAC, login, organization access, or the definition of an internal user.
- No removal of ID, Phase, Image, resource counts, CPU, memory, TiProxy, or future non-PD component rows.
- No redaction for Premium, BYOC, or resource-pool topology responses.
- No OpenAPI schema regeneration; the response type remains compatible and its `storages` field is empty for the redacted view.
- No app-delivery manifest change; release workflows only bump the two image tags.

## Compatibility Concerns

- Dedicated component sources currently emit `PD`, while other component sources can use `COMPONENT_TYPE_PD`; normalization must support both without relying on display formatting.
- The UI must fail closed while the optional user profile field is unresolved: internal details appear only when `internal === true`.
- Backend redaction must not mutate shared result objects because other callers depend on the complete topology.
- The internal Data Proxy facade must continue to call the unredacted shared listing implementation.
- Clearing `storages` preserves the existing response schema while removing sensitive values.

## Test Plan

### Clinic backend

- Use the repository Go version through local GVM: `gvm use go1.24.2`.
- Run the isolated pure-function regression test first, avoiding the package-wide Docker-backed `TestMain`:
  - `go test pkg/api/cluster/resource_pool_redaction.go pkg/api/cluster/resource_pool_redaction_test.go`
- Compile the full package test binary without running its Docker-backed `TestMain`:
  - `go test -c ./pkg/api/cluster`
- Run `go test ./pkg/api/cluster` only in an environment with the required MySQL and OpenSearch Docker access.
- Run `gofmt` on changed Go files.

### Clinic UI

- `pnpm --filter @clinic/web type-check`
- `pnpm --filter @clinic/web lint`
- If dependencies and time permit, `pnpm build` for the complete generated artifact.
- Manually verify the table contract with representative internal/external and Dedicated/non-Dedicated data states if a runnable authenticated environment is available.

## Environment Verification

Staging is actively in use and the user explicitly excluded staging deployment and validation from this task. Do not dispatch Clinic CD, the clinic-ui deployment workflow, or an app-delivery bump, and do not call staging APIs as part of this implementation handoff.

The following checks are deferred until a separately authorized maintenance window or isolated environment is available:

1. With an external identity, call the browser components API for a Dedicated cluster and confirm that no PD row or storage specification is present; confirm the UI has no PD row or Storage Info column.
2. With an internal identity, confirm the same Dedicated cluster still returns and renders PD and Storage Info.
3. Confirm one non-Dedicated topology view is unchanged.
4. If logs are available, inspect the request trace and verify the redaction/completion event without recording customer data.

Do not substitute production evidence for the skipped staging checks.

## Release Boundary

- This implementation does not authorize commits, pushes, pull requests, workflow dispatches, merges, staging deployment, or production release.
- Backend runtime effect requires Clinic CD `components=clinic`, app-delivery `clinic-api`, and `Deployment/clinic` in each target environment.
- Frontend runtime effect requires the clinic-ui `global` image, app-delivery `clinic-ui`, and `Deployment/clinic-ui` in each target environment.
- No staging workflow is authorized in the current task. Any later joint environment validation of the two images must be scheduled separately before merge if required by repository policy.
- Production scope is `prod-cloud` only under the Clinic repository release policy. Production dispatch and every app-delivery merge require explicit authorization.
