# BYOC resource-pool management: `cluster-service-ng` detailed design

Status: proposed

Last updated: 2026-07-23

Scope: `cluster-service-ng` (CSNG) and its in-repository
`internal/dataplane-manager` module

Related designs:

- [High-level design](https://github.com/tidbcloud/tidb-management-service/blob/main/api/doc/20260715-byoc-resource-pool-management-high-level-design.md)
- [Public OpenAPI design](https://github.com/tidbcloud/tidb-management-service/blob/main/api/doc/20260715-byoc-resource-pool-open-api-design.md)
- [nextgen-global-service and tidb-management-service detailed design](https://github.com/tidbcloud/tidb-management-service/blob/main/api/doc/20260716-byoc-resource-pool-tidb-management-service-detailed-design.md)

## 1. Scope and decisions

This design adds the smallest regional extension needed by the new BYOC
ResourcePool OpenAPI. It keeps the existing RU-driven shared-pool and TiDB
instance behavior. It does not introduce a second topology-driven BYOC
provisioning path.

The decisions are:

1. A resource pool created through the new API remains `RCU_DRIVEN`, but a
   bootstrap create does not accept a caller-selected `request_unit`. CSNG
   derives a creation-only effective RCU range solely from the required TiKV
   node spec and count, then uses that range to size the complete shared-pool
   component set. A create without the bootstrap wrapper keeps the existing
   `request_unit` behavior for legacy callers.
2. Add one optional bootstrap-capacity wrapper outside
   `CreateSharedPoolRequest.capacity_spec`. It overrides only the initial
   `NodeSpec` and `node_count` of the default `TIKV` and
   `TIFLASH_COMPUTE` component groups.
3. The bootstrap wrapper requires TiKV and permits optional TiFlash Compute.
   When the wrapper is present and TiFlash Compute is omitted, create the
   normal Compute component scaffold with zero nodes. When the whole wrapper
   is absent, preserve the current RCU create behavior byte-for-byte for
   legacy callers.
4. Resolve the selected TiKV shape through the tenant `RuConfig`. Treat that
   shape's configured RU tier as a floor, allow the selected TiKV count's
   nominal CPU capacity to promote the request to a higher tier, and derive one
   effective lower/upper RCU range. Pass that range to the existing RCU
   component generator as the creation-time `RequestUnit`; do not add
   bootstrap-specific hidden-component or HPA sizing logic.
5. TiFlash Compute is an independent customer-selected bootstrap override. It
   does not participate in RCU inference and CSNG does not validate capacity
   compatibility between TiKV and TiFlash Compute. Existing shape, count,
   local-disk, and storage validation still applies to each supplied component.
6. Apply the exact TiKV and optional TiFlash Compute spec/count after generated
   quotas have been merged but before component storage is finalized. Persist
   the resolved pool-scoped `RuConfig` unchanged. HPA behavior rules, later RU
   reconciliation, the TiKV autoscaler, and component-specific autoscaling
   coverage remain unchanged; hidden components retain the normal RCU
   generator semantics for the inferred `RequestUnit`.
7. Add generic `ListNodeSpecs` for creation-time TiKV and TiFlash Compute
   choices. Logical CPU/memory shapes are the intersection of
   dataplane-manager (DPM) deployable shapes and the caller tenant's effective
   `RuConfig`. Each logical shape includes regional cloud and reserved local
   storage-size options. Cloud sizes come from deployment configuration and
   are soft catalog guidance only. TiKV's creation maximum comes from the
   effective `nodeCountQuota.tikv`; the count rule reuses the current
   validation helper.
8. Reuse `GetSharedPoolScaleConstraints`, `ScaleSharedPool`, and the current
   global `ScaleByocSharedComponents` flow for day-two TiKV and TiFlash
   Compute scaling. Do not add a second resize implementation or a
   capacity-mode branch.
9. BYOC TiDB instances continue to use the existing max-RCU create workflow.
   CSNG receives the existing RCU-driven `CreateTiDBCluster` and
   `CreateTiDBGroup` requests. Requiring an explicit `resource_pool_id` for
   BYOC is enforced by `tidb-management-service` and
   `nextgen-global-service`; no TiDB node-spec or TiProxy-topology change is
   required in CSNG.
10. Public TiFlash maps only to regional `TIFLASH_COMPUTE`. This mapping belongs
   to the new global adapter. Generic CSNG behavior, Premium behavior, and
   existing `TIFLASH_WRITE` components remain unchanged.
11. Keep prior O11Y-CIDR, custom-tag, and delete decisions:
    - add generic `enforce_o11y_network_non_overlap` to real create;
    - expose the per-deployment O11Y CIDR and available node zones through
      `GetSharedPoolProvisionConfiguration`;
    - reuse PR #2865 tag merge/reconciliation; and
    - reuse the current logical delete and asynchronous cleanup, without a
      public `DELETING` state.
12. Manage existing RCU-driven BYOC pools in place. Do not convert their
    capacity mode, promote them through a new runtime API, or backfill a
    second capacity record. Existing topology-driven resources remain on
    their current internal/compatibility surfaces and are outside the new
    ResourcePool API.

Tenant-wide pool-name and pool-VPC-CIDR uniqueness, `created_by`, public
lifecycle, public RBAC, and global operation records remain global-service
concerns. Metrics APIs and pause/resume remain outside this design.

## 2. Verified current implementation and required changes

| Concern | Existing implementation to reuse | Required CSNG change |
| --- | --- | --- |
| RCU pool creation | `CreateSharedPoolRequest.request_unit` and `domain/server/create_sharedpool.go` load tenant `RuConfig`, call `generatedSharedPoolComponent`, persist the pool-scoped `RuConfig`, and submit the existing create DAG | For bootstrap creates, omit caller `request_unit`, infer a creation-only RCU range from TiKV, and feed it into the existing RCU component path before applying exact TiKV/Compute capacity |
| Hidden components | `generatedSharedPoolComponent` derives PD, workers, resource manager, TiProxy, TiFlash scaffolding, and other internal components from RU configuration and feature flags | Feed the TiKV-inferred `RequestUnit` into this existing path without a bootstrap-specific sizing pass |
| HPA and autoscaling | `apply_hpa` renders `minReplicas`/`maxReplicas` from persisted component quotas; HPA behavior comes from the autoscale-policy bundle; RU actions and the TiKV autoscaler use persisted component quotas and `RuConfig` | None; retain the quotas produced by the normal RCU generator, including configured maximum counts |
| Node and storage choices | DPM `ListNodePoolConfigs` and `genNodeSpecMap` provide physical node shapes and local-disk sizes before a dataplane exists; tenant/default `RuConfig` defines component-supported CPU/memory shapes and node-count quotas | Add a read-only `ListNodeSpecs` view of logical CPU/memory shapes, regional cloud/local storage-size options, and creation count options |
| Runtime constraints | `GetSharedPoolScaleConstraints` reads persisted component `NodeQuota`, current DPM candidates, RU minimum-spec rules, storage limits, and blockers | None |
| Component scale | `ScaleSharedPool` already performs one absolute component mutation under the pool lock, validation, transaction, and DAG | None |
| Global multi-component scale | `ScaleByocSharedComponents` already coordinates TiKV, Write, and Compute actions and skips absent targets | New public adapter supplies only TiKV and Compute targets; no CSNG change |
| TiDB creation | Current global workflow calls RCU-driven `CreateTiDBCluster`/`CreateTiDBGroup` from max RCU and a selected shared-pool ID | None |
| Tags | `SharedPool.cloud_resource_tags` and `UpdateSharedPoolCloudResourceTags` from PR #2865 | Reuse; share nonempty key/value validation if create does not already enforce it |
| Regional read | `GetSharedPool` returns applied component groups, phase, dataplane name, and provider node zones; DPM owns the configured regional zone set | Reuse provider node zones for the applied public `zone_config`; expose DPM `regionConfig.zones` with the deployment-scoped O11Y configuration for pre-create zone selection |
| Delete | `DeleteSharedPool` soft-deletes metadata and submits `PlanDeleteSharedPool` | None |

The important distinction is that the inferred RCU range is a creation-time
sizing input, not a second persisted desired-state model. After creation, the
applied component groups, persisted `RuConfig`, current autoscaling
controllers, and existing scale APIs remain the only regional sources of
truth.

## 3. Boundaries and compatibility

```mermaid
flowchart LR
    Public["ResourcePool OpenAPI"]
    Global["nextgen-global-service\nownership, validation, workflows"]
    Specs["CSNG ListNodeSpecs\nTiKV/Compute creation choices"]
    Provision["CSNG provision configuration\nO11Y CIDR and available zones"]
    Existing["existing CSNG RCU APIs\nCreate/Get/Constraints/Scale/TiDB"]
    Repo["existing CSNG MySQL and DAGs"]
    DPM["dataplane-manager"]
    Runtime["Kubernetes and cloud resources"]

    Public --> Global
    Global --> Specs
    Global --> Provision
    Global --> Existing
    Specs --> DPM
    Existing --> Repo
    Repo --> DPM --> Runtime
```

### 3.1 Public-to-regional mapping

| Public input | Regional input/state | Meaning |
| --- | --- | --- |
| Resource-pool TiKV | default `TIKV` group | Initial node spec/count override; later read and resize use applied state |
| Resource-pool TiFlash | default `TIFLASH_COMPUTE` group | Optional initial override; omitted on create means zero Compute nodes |
| TiDB instance `max_rcu` | existing cluster/group `request_unit` | No TiDB node-spec selection |
| TiDB `resource_pool_id` | existing `shared_pool_id` | Required by the global BYOC adapter |

Public storage entries continue to map to the existing
`CreateSharedPoolRequest.InitSharedPoolConfig` storage fields. The regional
bootstrap wrapper carries only node spec and node count.

`TIFLASH_WRITE`, `TIFLASH_LEARNER`, and
`TIFLASH_COMPUTE_LEARNER` remain internal. The new global adapter does not
create or scale a Write target, but CSNG must not reject or rewrite Write for
Premium or existing callers.

### 3.2 Legacy pools

Global projects the default `TIKV` and `TIFLASH_COMPUTE` groups for in-scope
legacy `RCU_DRIVEN` BYOC pools. Existing `TOPOLOGY_DRIVEN` resources remain
on their current internal/compatibility surfaces; this feature adds no public
projection or constraint adapter for them. Compatibility rules for the
in-scope legacy pools are:

- no read-time mutation or capacity-mode conversion;
- no runtime promotion API;
- zero or absent Compute is public TiFlash disabled;
- a nonzero legacy Write group is ignored by the public capacity projection,
  left unchanged by new resizes, and still counted in regional vCPU usage;
- current raw fields remain available to existing internal callers; and
- old `/byocVcpuPools` mutations and new ResourcePool mutations use the same
  global operation serialization and the same regional pool lock.

Before enabling the new API, audit in-scope legacy RCU-driven pools for a
default TiKV group and a usable Compute group. Remediate historic outliers
operationally; the read path must not synthesize persistent component state.

## 4. Additive regional API contract

### 4.1 Generic node-spec discovery

Add `ListNodeSpecs` to
`cluster_service_ng.v1beta.NodeInstanceGroupService`:

```proto
rpc ListNodeSpecs(ListNodeSpecsRequest) returns (ListNodeSpecsResponse) {
  option (google.api.http) = {get: "/nodeSpecs"};
}

message ListNodeSpecsRequest {
  ComponentType component_type = 1
      [(google.api.field_behavior) = REQUIRED];
  string tenant_id = 2
      [(google.api.field_behavior) = REQUIRED];
}

message ListNodeSpecsResponse {
  repeated NodeSpecOptions node_spec_options = 1;
  NodeCountOptions node_count_options = 2;
}

message NodeSpecOptions {
  NodeSpec node_spec = 1;
  repeated StorageSizeOptions storage_options = 2;
}

message StorageSizeOptions {
  Component.Storage.Name name = 1;
  StorageMedium medium = 2;
  uint32 default_size_gi = 3;
  uint32 min_size_gi = 4;
  uint32 max_size_gi = 5;
}

enum StorageMedium {
  STORAGE_MEDIUM_UNSPECIFIED = 0;
  STORAGE_MEDIUM_CLOUD = 1;
  STORAGE_MEDIUM_LOCAL = 2;
}

message NodeCountOptions {
  uint32 default_node_count = 1;
  uint32 min_node_count = 2;
  uint32 max_node_count = 3;
  NodeCountRule rule = 4;
}

message NodeCountRule {
  NodeCountRuleType type = 1;
  uint32 value = 2;
}

enum NodeCountRuleType {
  NODE_COUNT_RULE_TYPE_UNSPECIFIED = 0;
  NODE_COUNT_RULE_TYPE_NONE = 1;
  NODE_COUNT_RULE_TYPE_MULTIPLE_OF = 2;
  NODE_COUNT_RULE_TYPE_FIXED = 3;
}
```

The endpoint is deployment-scoped, but its result depends on the caller
tenant's effective `RuConfig`; therefore the request carries `tenant_id`.
It has no region, provider, service-plan, pool, or BYOC field because global
has already routed the logical region to one CSNG deployment. Only `TIKV` and
`TIFLASH_COMPUTE` are accepted in this phase. TiDB and hidden components are
intentionally not part of this API.

`node_spec_options` uses the same DPM `ListNodePoolConfigs` source and
normalization as `genNodeSpecMap`, then intersects the normalized result with
the selected component's CPU/memory keys in the same resolved tenant/default
`RuConfig` that create will persist:

- retain deployable dedicated node-pool configurations;
- preserve CPU, memory, available CPU/memory, and local-disk characteristics
  internally for physical resolution and storage-option generation;
- reject malformed or unusable shapes;
- group physical DPM variants by nominal CPU and memory into one logical
  `NodeSpec`;
- retain every distinct local-disk size as a separate fixed local storage
  option instead of assuming one local size per logical shape;
- deduplicate storage options by
  `(name, medium, min_size_gi, default_size_gi, max_size_gi)`;
- exclude DPM shapes that are not configured for that component; and
- sort logical shapes and their storage options deterministically.

The returned logical `NodeSpec` uses CPU and memory as its identity. It does
not expose `with_local_disk` or `local_disk_size_gi`; storage medium and size
belong only to `StorageSizeOptions`. Other physical fields are retained
internally and are not part of the customer-selectable catalog key.

The intersection is intentional: DPM answers what can be deployed in the
regional infrastructure, while `RuConfig` answers what the service plan can
size and generate. An empty intersection is a failed precondition rather than
an empty customer catalog. List and create must call one shared effective
`RuConfig` resolver so a shape returned by list cannot be rejected because
create resolved a different configuration.

The response contains regional storage-size options, but no public key/display
policy, O11Y data, topology, or hidden-component policy. Global converts each
logical regional shape to the public view and omits every local storage option
in this release.

Emit cloud `DATA` only when the logical shape has a deployable nonlocal DPM
variant. Emit local `DATA` only for deployable local variants. Storage roles
that are always cloud, such as TiKV `RAFTLOG`, remain present for both kinds
of physical variant.

After global removes local options, it also removes any logical choice that
lacks the complete cloud storage-name set required by the public component.

Storage options are component-specific:

- A TiKV nonlocal variant contributes cloud `DATA` and cloud `RAFTLOG`. A
  local variant contributes fixed local `DATA` and cloud `RAFTLOG`. The
  logical option contains the union across all physical variants.
- A TiFlash Compute nonlocal variant contributes cloud `DATA`. A local variant
  contributes fixed local `DATA`.
- TiFlash Write remains internal and is not part of `ListNodeSpecs`.

A local option is exact:
`min_size_gi == default_size_gi == max_size_gi == local_disk_size_gi`.
One logical shape may therefore contain multiple `DATA`/`LOCAL` options with
different fixed sizes.

Cloud storage-size options come directly from CSNG deployment configuration:

```yaml
storageSizePolicy:
  tikv:
    data:
      defaultSizeGi: 500
      minSizeGi: 100
      maxSizeGi: 4000
    raftlog:
      defaultSizeGi: 50
      minSizeGi: 20
      maxSizeGi: 500
  tiflashCompute:
    data:
      defaultSizeGi: 500
      minSizeGi: 100
      maxSizeGi: 4000
```

Validate this configuration at startup with
`0 < min_size_gi <= default_size_gi <= max_size_gi`. These values are soft
catalog constraints used only by `ListNodeSpecs` to guide frontend choices.
They are not authoritative storage limits and are not applied by
`CreateSharedPool`, `ScaleSharedPool`, or
`GetSharedPoolScaleConstraints`. Existing provider, local-disk, and hard
storage validation remains unchanged.

`buildNodeCountConstraints` cannot be reused wholesale. It reads
`current`/`min`/`max` from an already persisted `NodeQuota` and adds
resize-only zero-crossing semantics. Add a creation helper that shares the
existing `buildNodeCountRule` and `ValidateDefaultNodeCount` validation. For
TiKV, obtain the maximum from the effective
`RuConfig.components["tikv"].max_node_count`, which is materialized from
`nodeCountQuota.tikv`, then round it down to the nearest legal multiple of
three. Reject an effective quota below the minimum instead of advertising a
count beyond the configured quota.

| Component | Default | Minimum | Maximum | Rule |
| --- | ---: | ---: | ---: | --- |
| `TIKV` | 3 | 3 | effective `nodeCountQuota.tikv`, rounded down to a multiple of 3 | multiple of 3 |
| `TIFLASH_COMPUTE` | 1 | 1 | current `maxInitTiFlashCount` (1) | none |

List and create must use the same resolved quota and creation-count validator
so their legal TiKV counts cannot drift. For a present Compute bootstrap, add
the positive-count check on top of the current validation, whose internal
TiFlash minimum is zero for runtime disablement. TiFlash disable is expressed
by omitting it from create, not by selecting zero from the positive creation
options. Day-two zero crossing continues to come from the existing runtime
constraints.

CSNG does not page this small DPM-backed result. Global owns public pagination.

### 4.2 Shared-pool provision configuration

Add a deployment-scoped singleton read to `SharedPoolService`:

```proto
rpc GetSharedPoolProvisionConfiguration(
    GetSharedPoolProvisionConfigurationRequest)
    returns (SharedPoolProvisionConfiguration) {
  option (google.api.http) = {
    get: "/sharedPoolProvisionConfiguration"
  };
}

message GetSharedPoolProvisionConfigurationRequest {}

message SharedPoolProvisionConfiguration {
  string o11y_cluster_cidr = 1
      [(google.api.field_behavior) = OUTPUT_ONLY];
  repeated string available_node_zones = 2
      [(google.api.field_behavior) = OUTPUT_ONLY];
}
```

The method is available before a pool exists. It returns the canonical masked
IPv4 prefix from the same parsed configuration dependency used by real create,
and all zones from DPM `regionConfig.zones`. CSNG obtains the zones through the
existing DPM `ListNodePoolConfigs` response instead of duplicating regional
topology configuration. Missing or invalid O11Y configuration returns
`FailedPrecondition` rather than an empty value; a DPM read failure returns
`Unavailable`.

### 4.3 Additive create fields

Keep existing fields 1 through 23 and the current `capacity_spec` oneof
unchanged. Preserve released `tiflash_mode = 23`; add two optional fields
outside the oneof at 24 and 25:

```proto
message CreateSharedPoolRequest {
  // Existing fields 1..23 and capacity_spec are unchanged.
  bool enforce_o11y_network_non_overlap = 24
      [(google.api.field_behavior) = OPTIONAL];

  SharedPoolBootstrapCapacity bootstrap_capacity = 25
      [(google.api.field_behavior) = OPTIONAL];
}

message SharedPoolBootstrapCapacity {
  BootstrapComponentCapacity tikv = 1 [
    (google.api.field_behavior) = REQUIRED,
    (buf.validate.field).required = true
  ];
  BootstrapComponentCapacity tiflash_compute = 2
      [(google.api.field_behavior) = OPTIONAL];
}

message BootstrapComponentCapacity {
  NodeSpec node_spec = 1 [
    (google.api.field_behavior) = REQUIRED,
    (buf.validate.field).required = true
  ];
  uint32 node_count = 2 [(google.api.field_behavior) = REQUIRED];
}
```

Validation rules are:

- omission of `bootstrap_capacity` preserves the current behavior for every
  existing caller;
- when present, effective `capacity_mode` must be `RCU_DRIVEN` and
  `capacity_spec` must be absent; in particular, the caller must not supply
  either `request_unit` or `topology_spec`;
- when absent, existing capacity-mode validation is unchanged, including the
  requirement for `request_unit` in effective `RCU_DRIVEN` mode;
- `tikv` is required;
- `tiflash_compute` is optional;
- each supplied node spec selects logical CPU/memory through the same
  DPM-backed data used by `ListNodeSpecs`; selected storage medium resolves the
  physical variant and must satisfy existing local-disk versus cloud
  compatibility; the soft configured size range is not create validation;
- each supplied count must satisfy the creation options in section 4.1; and
- no other component can be specified through the wrapper.

Do not use a repeated generic component list. The closed wrapper prevents
duplicate components and prevents callers from overriding hidden components
or `TIFLASH_WRITE`.

When `enforce_o11y_network_non_overlap` is true, reject overlap between the
pool VPC CIDR and configured O11Y CIDR before repository writes or DAG
submission. The flag applies independently of capacity mode and is not a BYOC
marker. Omission and false preserve current behavior.

### 4.4 TiKV-derived RCU and bootstrap merge semantics

For a bootstrap create, TiKV is the sole RCU-sizing signal. Resolve the exact
normalized TiKV CPU/memory shape in
`RuConfig.components["tikv"].node_spec_ru_range`, independently of its
local-disk variant. Let its configured interval establish `shape_tier`, where
an upper bound of zero means unbounded. Calculate capacity from the nominal
CPU field advertised to the customer, not DPM's allocatable/available CPU:

```text
count_capacity_ru = tikv.node_count * tikv.node_spec.cpu * tikvPerCpuRu
sizing_ru         = max(shape_tier.min, count_capacity_ru)
effective_tier    = the configured TiKV tier containing sizing_ru
effective_min_ru  = sizing_ru
effective_max_ru  = effective_tier.max
```

This makes the selected shape's tier a floor while permitting a sufficiently
large selected count to promote sizing into a higher tier. It never demotes a
larger configured shape merely because three nodes have a lower raw capacity.
For example, with `8c32g -> [0, 199999]` and
`16c64g -> [200000, unbounded]`:

| Bootstrap TiKV | Count capacity | Effective RCU range |
| --- | ---: | --- |
| `8c32g x 3` | 60,000 | `[60,000, 199,999]` |
| `8c32g x 9` | 180,000 | `[180,000, 199,999]` |
| `8c32g x 12` | 240,000 | `[240,000, unbounded]` |
| `16c64g x 3` | 120,000 | `[200,000, unbounded]` |
| `16c64g x 6` | 240,000 | `[240,000, unbounded]` |

Use checked arithmetic. Normalize TiKV intervals before lookup: deduplicate
identical intervals, sort deterministically, and reject non-identical
overlapping intervals because they make tier selection ambiguous. A gap that
contains `sizing_ru`, or the absence of the selected shape tier, is rejected
with `BOOTSTRAP_RCU_RANGE_UNAVAILABLE`. For an unbounded upper tier, keep the
upper bound logically unbounded; component calculations use their configured
maximum node count as the finite cap. TiKV's multiple-of-three rule and the
effective `nodeCountQuota.tikv` maximum remain independently authoritative.

Do not inspect the TiFlash Compute RU tier when deriving this range. A supplied
Compute capacity is validated and applied independently, even when it would
map to a different RU tier.

Use the effective range as the creation-time `RequestUnit` passed to the
existing shared-pool component generator:

- resolve each hidden component's node spec at `effective_max_ru` so the
  selected shape can serve the full inferred range, as the current generator
  already does for an ordinary RCU request;
- evaluate existing initial/minimum count rules from `effective_min_ru` where
  the current component generator does so;
- retain each component's existing configured `max_node_count`, rounding,
  fixed-replica invariants, and learner scaffolding; and
- do not run a bootstrap-specific post-processing pass over HPA bounds.

The resulting sources are:

| Field | Source when bootstrap is present |
| --- | --- |
| effective RCU range | required customer-selected TiKV spec/count, selected-shape tier floor, and capacity promotion through the resolved tenant `RuConfig` TiKV tiers |
| TiKV `NodeQuota.node_spec` / `node_count` | exact customer-selected TiKV capacity |
| supplied Compute `NodeQuota.node_spec` / `node_count` | exact customer-selected Compute capacity |
| omitted Compute `NodeQuota.node_count` | zero-node scaffold |
| hidden component node spec | inferred upper RCU bound through existing component `RuConfig` ranges |
| hidden component initial/minimum count | inferred lower RCU bound through existing component sizing rules |
| hidden HPA maximum count | existing component `RuConfig` maximum through the normal RCU generator |
| component storage | existing init-storage generation, evaluated using the final selected node spec |
| pool-scoped `RuConfig` | unchanged resolved tenant/default configuration |

The merge point still matters. For TiKV and Compute creation:

1. derive the effective RCU range from TiKV;
2. pass that range as `RequestUnit` to the existing complete-component
   generator;
3. complete the existing `compareNodeQuota` merge;
4. replace TiKV and supplied Compute node spec/count with the exact bootstrap
   selections;
5. for omitted Compute, set its count to zero after normal quota generation;
   and
6. generate/finalize storage from the resulting quota.

Do not seed a partially built component before `compareNodeQuota` and do not
post-patch after storage generation. The former can be overwritten by the
existing merge; the latter can leave local-disk/storage decisions inconsistent
with the selected shape.

The inferred range and overrides are creation-only. Later RU reconciliation,
HPA decisions, and manual component scaling read the persisted applied state
and `RuConfig` through their existing paths. No bootstrap or inferred-RCU field
is added to `SharedPool`.

### 4.5 Resize remains unchanged

No new regional resize, preview, status, or capacity-mode API is added.
Global continues to call:

```proto
GetSharedPool(GetSharedPoolRequest) returns (SharedPool)
GetSharedPoolScaleConstraints(GetSharedPoolScaleConstraintsRequest)
    returns (GetSharedPoolScaleConstraintsResponse)
ScaleSharedPool(ScaleSharedPoolRequest) returns (SharedPool)
```

The new global ResourcePool adapter selects only the default `TIKV` and
`TIFLASH_COMPUTE` entries and submits them through the existing
`ScaleByocSharedComponents` service. Existing RU minimum-spec validation,
persisted count bounds, DPM candidate resolution, storage checks, workload
checks, pool locking, and regional DAG behavior remain authoritative.

Omitted TiFlash in a resize request means unchanged. An explicit zero target
uses the current Compute zero-crossing behavior. No Write target is sent by the
new adapter; existing Write state is untouched.

### 4.6 TiDB creation remains unchanged in CSNG

No CSNG protobuf, component generator, node-spec API, TiProxy topology, or HPA
change is needed for TiDB creation.

The global BYOC flow requires and validates the public `resource_pool_id`,
then reuses the current workflow:

```text
max_rcu
-> existing RequestUnit conversion
-> CreateTiDBCluster(RCU_DRIVEN, shared_pool_id, request_unit)
-> CreateTiDBGroup(existing RU-driven request)
```

Non-BYOC plans keep their current pool selection and create behavior. CSNG
continues to validate that the selected shared pool and TiDB resources are
usable. The public rule that BYOC cannot omit `resource_pool_id` belongs at the
global boundary.

### 4.7 Tags and delete

Reuse `UpdateSharedPoolCloudResourceTags` and PR #2865 behavior:

- update input must be nonempty;
- keys and values must be nonempty after trimming;
- supplied keys add or overwrite;
- omitted keys are preserved; and
- empty value never means deletion.

Reuse the existing `DeleteSharedPool(prune_dataplane=true)` flow. Its successful
response means logical deletion and cleanup-DAG submission succeeded, not that
physical cleanup finished. Normal Get/List stop returning the pool immediately.
No regional deletion-status API or durable deletion model is added.

## 5. Regional configuration

Use these names consistently:

- YAML: `server.biz.o11yClusterCidr`
- Go: `BizConfig.O11yClusterCIDR string` with
  `yaml:"o11yClusterCidr"`

Startup validation parses the CIDR with `net/netip.ParsePrefix`, requires a
canonical masked IPv4 prefix for the initial release, and stores the normalized
immutable prefix in the server dependency graph. A BYOC-enabled deployment
requires a nonempty CIDR.

DPM already models the same network as `O11YConfig.VPCCIDR`. Deployment should
derive DPM and CSNG values from one Helm/Argo value and verify parity before
rollout.

There is no `byocNodeCatalog` and no hidden-component or TiProxy topology
configuration in this design. DPM remains the node-shape source. Existing
`RuConfig` ranges, component sizing formulas, and configured count limits
remain the only source for hidden components and their HPA replica bounds.
The bootstrap path adds only the TiKV-to-RCU inference described in section
4.4; it adds no second hidden-topology configuration.

Changing the O11Y CIDR in an existing deployment requires a precheck over live
pools in that deployment. Logically deleted pools do not participate.

## 6. Implementation changes

### 6.1 Protobuf and transport

Update `api/cluster-service-ng/v1beta/services.proto` with:

- `ListNodeSpecs`, logical node-spec options, storage-size options, and storage
  medium;
- `GetSharedPoolProvisionConfiguration`;
- create fields 24 and 25 plus the bootstrap messages.

Extend DPM `ListNodePoolConfigsResponse` with output-only
`available_node_zones` sourced from `regionConfig.zones`, which remains the
create-time authority for allowed node zones.

Regenerate server, client, gateway, Swagger, and mocks. Forward both new create
fields through `domain/app/app.go`, the server interface, and the concrete
handler. A protobuf-only edit is insufficient because the current app adapter
constructs the domain call explicitly.

### 6.2 Node-spec listing

Add `domain/server/list_node_specs.go` and factor a small normalization helper
from the path used by `genNodeSpecMap`. The handler:

1. requires `tenant_id` and accepts only `TIKV` or `TIFLASH_COMPUTE`;
2. resolves the effective tenant/default `RuConfig` through the same helper as
   bootstrap create;
3. calls DPM `ListNodePoolConfigs`;
4. normalizes, validates, deduplicates, and sorts deployable dedicated shapes;
5. intersects them with the component's configured CPU/memory shapes;
6. groups physical variants into logical CPU/memory shapes;
7. attaches configured cloud options and every distinct fixed local-disk
   option;
8. builds the quota-derived creation options in section 4.1; and
9. returns no public product or topology metadata.

A DPM failure maps to `Unavailable`. An empty usable result maps to
`FailedPrecondition`. Create performs the same node-spec and count validation
again; List is advisory and does not reserve a shape.

### 6.3 Bootstrap-derived RCU create

Add an internal value type mirroring the closed bootstrap wrapper and pass it
only through the RCU branch:

```go
type BootstrapComponentCapacity struct {
    NodeSpec  *entity.NodeSpec
    NodeCount uint32
}

type SharedPoolBootstrapCapacity struct {
    TiKV           BootstrapComponentCapacity
    TiFlashCompute *BootstrapComponentCapacity
}
```

The concrete names may follow local conventions, but the type must remain an
optional creation input rather than persisted pool state.

At the start of `CreateSharedPool`:

1. validate mode/oneof/bootstrap combinations;
2. load the same pre-dataplane node-spec map as current RCU creation;
3. load the resolved tenant/default `RuConfig` used by both list and create;
4. validate supplied shapes against the component's configured CPU/memory
   shapes and validate positive creation counts against its effective quota;
5. validate O11Y overlap when requested;
6. derive the effective RCU
   range exclusively from TiKV as specified in section 4.4; and
7. enter the existing lock, transaction, and create path without a caller
   `request_unit`.

Add a creation-only effective `RequestUnit` and pass it through the ordinary
component generator. Do not add a second hidden-component or HPA-sizing pass.
Keep the caller-supplied `RequestUnit` path intact for legacy create and later
RU reconciliation. Extend `genDefaultTiKVComponent` and
`genDefaultTiFlashComponent` with the exact creation-only overrides, applied
between quota merge and storage generation as defined in section 4.4.

When the wrapper is present:

- no caller `request_unit` is accepted;
- TiKV alone determines the effective RCU range;
- TiKV always receives its supplied override;
- supplied Compute receives its override;
- omitted Compute receives count zero after its inferred-RCU quota is
  generated;
- supplied or omitted Compute never constrains the inferred RCU range;
- hidden components are generated normally from the inferred `RequestUnit`,
  with configured HPA maximum counts unchanged;
- Write retains its current component-specific generation semantics using the
  inferred range when it exists in the selected TiFlash architecture; and
- `ruConfigRepo.SetRuConfig` stores the same `RuConfig` object as today.

When the wrapper is absent, do not infer an RCU range or run any overlay; use
the caller `request_unit` and the existing generator byte-for-byte. This is the
primary compatibility guarantee for existing callers.

### 6.4 Provision read

Add `domain/server/get_sharedpool_provision_configuration.go` to return the
parsed immutable O11Y prefix without a DPM or database call.

### 6.5 Explicitly unchanged areas

No change is required in:

- topology-driven shared-pool generation or validation;
- `NodeQuota` field behavior/annotations;
- `GetSharedPoolScaleConstraints`;
- `ScaleSharedPool` and its DAG;
- RU configuration persistence or update;
- HPA templates, behavior resolution, action coverage, or TiKV autoscaler
  behavior;
- TiDB cluster/group protos and generators;
- dataplane-manager APIs;
- shared-pool persistence schema; or
- delete workflow persistence.

## 7. End-to-end workflows

### 7.1 List creation choices

```mermaid
sequenceDiagram
    participant M as tidb-management-service
    participant G as nextgen-global-service
    participant C as CSNG
    participant D as dataplane-manager

    M->>G: ListNodeSpecs(org, BYOC logical region, component)
    G->>G: authorize and resolve regional deployment
    G->>C: ListNodeSpecs(tenant, TIKV or TIFLASH_COMPUTE)
    C->>C: resolve effective tenant/default RuConfig
    C->>D: ListNodePoolConfigs()
    D-->>C: deployable physical shapes and local disks
    C->>C: intersect shapes and build logical storage options
    C-->>G: logical options + soft storage sizes + creation counts
    G->>G: omit local storage; add public keys/display; paginate
    G-->>M: public node-spec choices
```

The endpoint works before a pool exists. It is used only for resource-pool
TiKV and TiFlash bootstrap selection.

### 7.2 Validate-only and create resource pool

Public `validate_only` remains global and mutation-free. Global:

1. resolves the BYOC logical region and target CSNG deployment;
2. loads TiKV and optional Compute choices through `ListNodeSpecs`;
3. loads the configured O11Y CIDR;
4. validates public node specs, node counts, storage entries, tenant-wide
   name/CIDR, and vCPU invariants;
5. builds an `RCU_DRIVEN` create request without `capacity_spec`;
6. maps public cloud storage entries into `init_shared_pool_config`; and
7. maps only TiKV and optional Compute spec/count into
   `bootstrap_capacity`.

Real create reuses the current path:

```mermaid
sequenceDiagram
    participant G as nextgen-global-service
    participant C as CSNG CreateSharedPool
    participant R as CSNG MySQL and DAG
    participant D as dataplane-manager

    G->>C: CreateSharedPool(RCU_DRIVEN, bootstrap, enforce O11Y)
    C->>D: ListNodePoolConfigs()
    C->>C: validate bootstrap and O11Y overlap
    C->>C: infer effective RCU range from TiKV only
    C->>C: run existing RCU component generation with inferred range
    C->>C: apply exact TiKV and optional Compute before storage finalization
    C->>R: persist pool and unchanged RuConfig; submit existing create DAG
    R->>D: existing dataplane/component creation
    C-->>G: SharedPool(CREATING)
```

Global keeps the current synchronous `CreateSharedPool` submission and its
existing event copy. This feature adds no regional-create workflow, frozen
request, or replay protocol. CSNG validates current DPM availability when it
handles each create request.

No caller supplies PD, workers, resource manager, TiProxy, learners, or Write
topology. The existing RU configuration and component sizing rules own them,
using the TiKV-derived range during bootstrap creation. The existing create DAG
applies the normally generated HPA replica bounds without bootstrap-specific
changes.

### 7.3 Read and constraints

`GetSharedPool` remains the applied-state read. Global projects default TiKV
and positive Compute capacity, omits zero Compute, and does not expose Write.
Public detail independently joins:

- persisted `dataplane_id` from existing global pool metadata;
- `zone_config.node_zones` from the existing
  `GetSharedPool.provider.node_zones` response;
- canonical O11Y CIDR from provision configuration;
- available node zones from provision configuration for pre-create selection;
- database vCPU fields from the existing vCPU-usage response; and
- immutable `created_by` from global audit storage.

No new CSNG API is required for `dataplane_id` or `zone_config`. Global already
persists the dataplane name returned by create, and the existing applied-state
read already exposes runtime node zones.

For constraints, global calls the existing
`GetSharedPoolScaleConstraints`, selects TiKV and Compute, and converts current
regional candidates/storage limits to the public contract. Generic
`ListNodeSpecs` is a creation-options read and is not a replacement for live
runtime constraints.

### 7.4 Resize resource pool

Global reuses the current `ScaleByocSharedComponents` flow:

1. acquire the existing global pool-operation guard;
2. load applied state, current constraints, and vCPU usage;
3. build absolute TiKV and optional Compute targets;
4. for validate-only, return the current and normalized target without a
   regional mutation;
5. otherwise persist and run the existing FastFlow DAG;
6. call `ScaleSharedPool` for each changed component; and
7. use current retry, convergence, and public-state handling.

The existing DAG may retain its TiKV -> Write -> Compute ordering. The Write
action receives no target from the new adapter and makes no regional call.
There is no cross-component regional transaction and no new regional plan
record.

### 7.5 Create BYOC TiDB instance

```mermaid
sequenceDiagram
    participant M as tidb-management-service
    participant G as nextgen-global-service
    participant C as CSNG

    M->>G: CreateTiDB(BYOC, resource_pool_id, max_rcu)
    G->>G: require and authorize explicit pool
    G->>G: convert max_rcu through existing RU logic
    G->>C: CreateTiDBCluster(RCU_DRIVEN, shared_pool_id, request_unit)
    C-->>G: cluster lifecycle
    G->>C: CreateTiDBGroup(existing RU-driven request)
    C-->>G: group lifecycle
```

No `ListNodeSpecs` call, TiDB node spec, or TiProxy topology is introduced.
Other service plans keep current behavior and do not require
`resource_pool_id`.

### 7.6 Tags and delete

Tag upsert continues to merge and reconcile the complete desired tag map
through PR #2865. Delete continues:

```text
global logical soft delete + existing FastFlow workflow
-> DeleteSharedPool(prune_dataplane=true)
-> CSNG logical soft delete + PlanDeleteSharedPool submission
-> existing asynchronous regional/cloud cleanup
```

Global releases the public name and CIDR after its logical-delete transaction
commits. Cleanup failure remains visible through existing workflow/DAG
diagnostics.

## 8. Persistence

No CSNG table or pool-column change is required.

- Bootstrap capacity is request-only.
- Applied `Component.NodeQuota` remains the capacity source.
- Existing pool-scoped `RuConfig` remains the autoscaling-policy source.
- O11Y configuration is deployment configuration.
- The storage-size policy is deployment configuration used only to build the
  `ListNodeSpecs` catalog.
- Existing soft-delete metadata and DAG persistence remain unchanged.

Do not add a logical capacity snapshot, bootstrap metadata column, catalog
table, promotion state, or deletion-status table.

## 9. Errors and concurrency

| Condition | gRPC code | Stable internal reason |
| --- | --- | --- |
| Unsupported list component or malformed bootstrap | `InvalidArgument` | field-specific |
| Bootstrap used with non-RCU mode or with any `capacity_spec` | `InvalidArgument` | `INVALID_BOOTSTRAP_CAPACITY_MODE` |
| Missing TiKV, node spec outside the component's effective `RuConfig`, count outside the effective quota, invalid positive count, or incompatible storage | `InvalidArgument` | `INVALID_BOOTSTRAP_CAPACITY` |
| TiKV spec/count cannot resolve to one deterministic configured RCU tier, including gaps or ambiguous overlaps | `FailedPrecondition` | `BOOTSTRAP_RCU_RANGE_UNAVAILABLE` |
| DPM node-spec discovery unavailable | `Unavailable` | `NODE_SPECS_UNAVAILABLE` |
| No usable node specs | `FailedPrecondition` | `NODE_SPECS_UNAVAILABLE` |
| O11Y enforcement requested but CIDR is absent/invalid | `FailedPrecondition` | `O11Y_CIDR_CONFIGURATION_MISSING` |
| Pool VPC overlaps configured O11Y CIDR | `FailedPrecondition` | `O11Y_CIDR_CONFLICT` |
| Component unavailable or scale DAG running | existing `FailedPrecondition` behavior | existing regional reason |
| Scale target violates current constraints | existing `InvalidArgument`/`FailedPrecondition` behavior | existing regional reason |

Node-spec and provision reads do not acquire a pool lock. Real create, scale,
tag update, and delete retain existing transaction and locking behavior.
Global uses one operation guard for old and new resource-pool mutations; the
regional pool lock remains the final guard.

## 10. Observability and operational safety

Add structured logs for:

- `ListNodeSpecs` component, result count, DPM error, and normalization error;
- bootstrap validation and selected TiKV/Compute spec/count;
- whether the create request used a bootstrap wrapper, without logging
  unrelated request secrets;
- enforced O11Y validation and conflict reason;
- existing create/scale/delete DAG identifiers.

Do not add feature-specific Prometheus metrics, dashboards, or alert rules in
this phase. Existing service and DAG health signals remain authoritative.

Do not label the generic regional APIs as BYOC internally. Operators diagnose
empty node-spec discovery, missing O11Y configuration, and bootstrap RCU-range
failures from the structured logs. Existing long-running DAG monitoring stays
unchanged; this repository adds no feature-specific alert rules.

The regional APIs remain service-to-service. Existing transport authentication
applies. Service-identity authorization for the additive methods remains a
rollout TODO; public two-layer RBAC is implemented in
`tidb-management-service`.

## 11. Rollout and compatibility

1. Merge or pin PR #2865 and verify tag propagation.
2. Configure the O11Y CIDR in one non-production CSNG deployment and verify
   DPM/CSNG CIDR parity.
3. Configure and sanity-check the `storageSizePolicy` values advertised by
   `ListNodeSpecs`.
4. Deploy CSNG with `ListNodeSpecs`, provision configuration, create fields 24
   and 25, TiKV-derived RCU bootstrap creation, and O11Y enforcement.
5. Verify legacy create callers omit `bootstrap_capacity` and produce
   unchanged component/storage/HPA output.
6. Audit legacy RCU-driven BYOC pools for default TiKV/Compute groups, record
   nonzero Write, and verify no pool VPC overlaps O11Y.
7. Deploy global/TMS ResourcePool and explicit BYOC pool-selection changes.
8. Canary ListNodeSpecs, validate-only create, real create with/without
   Compute, legacy Get/constraints, TiKV/Compute scale, TiDB max-RCU create,
   tags, and delete.
9. Expand region by region.

Rollback disables the new public entry points first. Existing pools remain
operable through current regional Get/constraints/scale/delete APIs. Do not
backfill or convert capacity modes, and do not modify legacy Write capacity.

## 12. CSNG test plan

### 12.1 Proto and compatibility

Required cases:

- existing field 2 `request_unit` and field 20 `topology_spec` fixtures decode
  unchanged;
- released field 23 remains `tiflash_mode`, and fields 24 and 25 remain outside
  `capacity_spec`;
- old callers omitting both fields retain golden request behavior;
- bootstrap requests omit `capacity_spec`, while no-bootstrap RCU requests
  still require `request_unit`;
- generated app/domain forwarding preserves both fields;
- `ListNodeSpecs` requires tenant identity, accepts only TiKV/Compute, and
  returns logical options from the intersection of regional and
  effective-`RuConfig` shapes, storage-size options, and quota-derived creation
  options;
- provision configuration returns canonical O11Y CIDR and DPM-configured
  available node zones;
- `NodeQuota` and topology-driven API annotations are unchanged;
- TiDB and DeleteSharedPool contracts are unchanged; and
- generated mocks include the additive methods.

Commands:

```bash
make proto
cd api && make proto && cd ..
make generate-mocks
git diff --check
```

Run `buf breaking` against the release baseline and report findings.

### 12.2 Node-spec and configuration tests

Add focused tests for:

- exact DPM normalization, invalid-shape exclusion, deduplication, and stable
  sort order;
- grouping local and nonlocal physical variants with the same CPU/memory into
  one logical shape;
- logical output leaves local-disk fields unset and represents medium only in
  storage options;
- configured TiKV cloud `DATA`/`RAFTLOG` and TiFlash Compute cloud `DATA`
  options with the component-specific local/nonlocal rules;
- multiple distinct fixed local `DATA` options on one logical shape, exact
  deduplication, and stable size ordering;
- local options satisfying `min == default == max == DPM local disk size`;
- invalid storage-policy configuration rejection;
- proof that the storage-size policy is read only by `ListNodeSpecs` and does
  not change create, scale, or runtime scale constraints;
- exclusion of DPM shapes absent from the component's effective `RuConfig`;
- exclusion of `RuConfig` shapes absent from DPM;
- equality between the effective `RuConfig` used by list and create;
- unsupported component rejection;
- TiKV options `{default:3, min:3, max:floor(nodeCountQuota.tikv/3)*3,
  multiple_of:3}` and rejection when the quota cannot admit the minimum;
- Compute options `{default:1, min:1, max:1, none}`;
- list/create sharing one count-options validator;
- proof that `buildNodeCountConstraints` runtime `current`, persisted bounds,
  and `allow_cross_zero` are not reused for creation;
- DPM failure and empty result error mapping;
- no public display, O11Y, or topology fields in `ListNodeSpecs`;
- existing `GetSharedPool.provider.node_zones` remains the public detail source
  without a new regional read API;
- missing/malformed/noncanonical/IPv6 O11Y CIDR;
- provision response equality with the prefix used by create enforcement.

### 12.3 RCU CreateSharedPool tests

Extend `domain/server/create_sharedpool_test.go` and generator tests:

- wrapper absent: existing RCU request produces identical component quotas,
  storage, `RuConfig`, HPA plan, and DAG input;
- wrapper present with non-RCU mode, `request_unit`, or `topology_spec` is
  rejected before writes;
- missing TiKV, unknown spec, invalid TiKV multiple/count, invalid positive
  Compute count, and spec/storage mismatch are rejected before writes;
- TiKV nominal CPU/count, its selected-shape tier floor, and capacity promotion
  produce the documented effective lower/upper RCU bounds with checked
  arithmetic, including `16c64g x 3` remaining in the second tier and
  `8c32g x 12` promoting to it;
- duplicate identical TiKV intervals are accepted once, while non-identical
  overlaps, gaps at the sizing point, and missing shape tiers fail before
  writes with `BOOTSTRAP_RCU_RANGE_UNAVAILABLE`;
- an unbounded TiKV tier uses configured component maxima as finite HPA caps;
- supplied TiKV spec/count are applied exactly after inferred-RCU generation;
- supplied Compute spec/count are applied exactly and never affect RCU
  inference;
- TiKV and Compute choices from different RU tiers are accepted when each is
  independently valid;
- omitted Compute under a present wrapper yields a zero-node scaffold with its
  inferred-RCU-generated spec and legal bounds;
- absence of the whole wrapper retains current nonzero/default Compute
  behavior;
- overlay happens after `compareNodeQuota` and before storage generation;
- local-disk/storage generation uses the selected bootstrap spec;
- hidden component specs are selected at the effective upper RCU bound;
- hidden initial/minimum counts are generated at the effective lower RCU
  bound where the normal RCU component rule is RU-sensitive;
- hidden HPA maximum counts retain their existing configured component limits;
- no bootstrap-specific hidden-component quota post-processing is introduced;
- component-specific rounding, fixed-count components, and learner scaffolding
  retain their existing invariants;
- the original tenant `RuConfig` is persisted unchanged;
- existing RCU HPA actions remain in the create plan;
- HPA behavior resolution and current TiKV autoscaler behavior remain
  unchanged;
- `TIFLASH_WRITE` behavior is unchanged for BYOC/Premium/legacy fixtures;
- enforced O11Y conflict fails before transaction/DAG, while disjoint and
  adjacent prefixes succeed; and
- omitted enforcement preserves old behavior.

### 12.4 Constraint, scale, and TiDB regression

Existing constraint/scale cases must continue to pass:

- RCU constraints still include current RU minimum specs, persisted bounds,
  storage limits, and blockers;
- TiKV and Compute vertical/horizontal scaling use existing validation;
- Compute zero-to-positive and positive-to-zero use current semantics;
- nonzero Write remains unchanged when TiKV/Compute scale;
- pool phase/lock, storage cooldown, workload, and provider guards remain
  unchanged; and
- no `ListNodeSpecs` call is introduced into `ScaleSharedPool`.

TiDB regression cases:

- RCU-driven cluster/group creation from request unit is unchanged;
- CSNG accepts the explicit existing shared-pool ID through the current
  request;
- no TiDB node spec or topology is required;
- TiDB HPA behavior is unchanged; and
- non-BYOC/Premium create paths remain unchanged.

### 12.5 Tags and delete regression

Tag cases:

- create accepts omission and rejects empty supplied key/value;
- update rejects an empty patch;
- add, overwrite, and omitted-key preservation work;
- empty value cannot delete; and
- provider reconciliation receives the merged map.

Delete cases:

- route, response, and `prune_dataplane` behavior are unchanged;
- success soft-deletes once and submits one existing cleanup DAG;
- Get/List hide the pool immediately;
- cleanup continues asynchronously; and
- no new deletion-status record/API exists.

### 12.6 Required commands

Focused loop:

```bash
go test ./config/... ./domain/server/... ./domain/workflow/action/...
```

Pre-merge:

```bash
make generate
cd api && make proto && cd ..
make fmt
make test
make lint
git diff --check
```

If the nested dataplane-manager module changes:

```bash
cd internal/dataplane-manager
make generate
make devselfcheck
go test ./...
```

## 13. Test-environment end-to-end plan

| Scenario | Expected oracle |
| --- | --- |
| List specs before pool exists | Tenant identity selects the effective `RuConfig`; the result contains logical TiKV/Compute shapes, configured soft cloud sizes, all distinct regional local sizes, quota-derived count options, stable order, and no topology fields |
| Validate-only create | No global row/workflow and no CSNG row/DAG/DataPlane |
| Create without Compute | Regional mode is `RCU_DRIVEN`; no request unit is supplied; selected TiKV is applied; Compute has zero nodes; the normal component generator uses the TiKV-derived `RequestUnit` |
| Create with Compute | Selected TiKV/Compute specs and counts are applied independently; only TiKV determines the inferred `RequestUnit`; storage matches selected shapes and configured HPA maxima remain unchanged |
| O11Y conflict | Global validation rejects; direct enforced regional create also rejects before mutation |
| Legacy create caller | Omitting wrapper yields pre-change component/storage/HPA output |
| Legacy pool read | Public TiKV/Compute equals applied groups; stored mode is unchanged |
| Legacy nonzero Write | Public capacity ignores Write, vCPU usage counts it, and new resize leaves it unchanged |
| Constraints and resize | Existing `ScaleByocSharedComponents` preview and real TiKV/Compute actions converge without a new regional API |
| TiFlash disable/re-enable | Existing scale API moves Compute positive-to-zero and zero-to-positive |
| BYOC TiDB create | Explicit pool is required globally; CSNG receives existing max-RCU-derived cluster/group requests; no topology is sent |
| Tags | Add/overwrite/preserve is visible in CSNG desired state and provider resources; removal is rejected |
| Delete | Public/global resource and reservation disappear after logical delete; existing regional cleanup continues |
| Restart/fault | Existing global FastFlow and CSNG DAG identities resume create/scale/delete without duplicate resources |

Collect global operation/workflow IDs, CSNG DAG IDs, applied component groups,
persisted `RuConfig`, HPA resources, DPM DataPlane/NodePool state, provider
tags, and before/after capacity mode.

## 14. Rollout gates

Before enablement:

1. Configure and verify the per-deployment O11Y CIDR.
2. Configure and validate the deployment storage-size policy advertised by
   `ListNodeSpecs`.
3. Complete the legacy TiKV/Compute/Write and CIDR audit.
4. Validate tenant-aware ListNodeSpecs filtering, storage options, and
   quota-derived creation options against bootstrap create validation in tests.
5. Confirm service-identity authorization for the additive regional methods.
6. Complete public portal/IAM and management-service permission-interceptor
   registration.

There is no node-spec, hidden-topology, TiProxy-topology, or autoscaling-policy
TODO in CSNG for this feature. Those behaviors use the existing DPM,
`RuConfig`, generators, and controllers.
