# TiCDC Model Layer Agent Instructions

## Overview

This module manages **kernel-level TiCDC resources**: TiCDC clusters and changefeeds.
It is the lower-level layer called by `app/models/biz_changefeed/`.

## Critical Architecture: Regional vs Non-Regional

The most important concept to understand in this module. **Every operation has two code paths.**

### What is the difference?

| Aspect | Non-Regional | Regional |
|--------|-------------|----------|
| **TiCDC cluster deployment** | Managed by central control plane | Managed by regional control plane |
| **API communication** | Via `ClusterProxy` (central proxy to TiDB cluster) | Via `Regional API` (HTTP to regional service) |
| **Implementation** | `clusterModel` in `impl/changfeed.go` | `changefeedModelRegionalImpl` in `impl/changefeed_regional.go` |
| **Key client** | `clusterproxy.ClusterProxy` | `regionalapi.ChangefeedClient` |

### How to select the correct implementation

```go
// ChangefeedModelFactory selects based on IsRegional flag
factory.GetModel(bizChangefeedEnt.IsRegional).DeleteChangefeed(ctx, req)

// IsRegional is stored on both BizChangefeed and TiCDCCluster entities
type BizChangefeed struct {
    IsRegional bool // if the corresponding ticdc cluster is deployed by regional control plane
}
```

### Non-Regional: ClusterProxy path

```go
// impl/changfeed.go
proxy, err := c.clusterProxyModel.GetClusterProxy(ctx, projectID, clusterID)
proxy.DeleteChangefeed_v2(ctx, ticdcCluster.Name, changefeed.KernelChangefeedID)
```

### Regional: Regional API path

```go
// impl/changefeed_regional.go
region := &regionalapi.Region{
    TenantID:      ticdcClusterEnt.TenantID.Uint64(),
    Name:          ticdcClusterEnt.Region,
    CloudProvider: string(...),
}
m.changefeedCli.DeleteChangefeed(ctx, region, regionalReq)
```

## Async Operation Pattern

Changefeed operations are **asynchronous**. The API call only triggers the workflow;
the actual operation happens in the background.

```
User calls DeleteBizChangefeed
    └── BizChangefeed status: normal → deleting   (sync, immediate)
    └── Starts workflow DAG                        (sync, immediate)
         └── ActionDeleteChangefeed.Run()          (async, in worker)
              └── ticdc.DeleteChangefeed()         (actual deletion)
                   └── BizChangefeed status: deleting → deleted
```

**Do NOT expect the TiCDC changefeed to be deleted immediately after the API returns.**

## Retry Patterns for Network Failures

Regional API calls are susceptible to transient infrastructure failures:
- DNS lookup failures: `"dial tcp: lookup ticdc-xxx.svc ... no such host"`
- Connection refused: `"dial tcp ...: connect: connection refused"`
- 5xx server errors from Regional control plane

### Delete operations must use infinite retry

Delete is **idempotent** (safe to call multiple times), and infrastructure recovery
time is unpredictable. Use `InvokeRunActionWithInfinitelyRetry` for delete actions:

```go
// app/workflow/action/biz_changefeed/del/delete_changefeed.go
return action.MustNewActionWrapper(
    workflow.ActionBizChangefeedDeleteChangefeed,
    deleteAction,
    action.SetRunRetryFun(action.InvokeRunActionWithInfinitelyRetry),
)
```

The workflow task timeout (5 minutes) serves as the upper bound.

### Other operations: use default exponential retry

For non-idempotent operations (create, pause, resume), the default
`InvokeRunActionWithExponentialRetry` (8 attempts, ~45s) is appropriate.

## Key Files

```
app/models/ticdc/
├── interface.go                    # ChangefeedModel + ClusterModel interfaces
├── impl/
│   ├── changfeed.go               # Non-regional implementation (ClusterProxy)
│   ├── changefeed_regional.go     # Regional implementation (Regional API)
│   ├── factory.go                 # ChangefeedModelFactory (selects impl by IsRegional)
│   └── cluster.go                 # TiCDC cluster management
└── mock/                          # Auto-generated mocks (DO NOT EDIT)

pkg/regional-api/
├── regional_client.go             # HTTP client for Regional control plane
├── changefeed.go                  # Changefeed-specific Regional API calls
└── ticdc_cluster.go               # TiCDC cluster Regional API calls
```

## Common Pitfalls

### 1. Forgetting to handle both Regional and Non-Regional paths

When fixing a bug or adding a feature, always check both files:
- `impl/changfeed.go` (non-regional)
- `impl/changefeed_regional.go` (regional)

### 2. Assuming deletion is synchronous

After calling `DeleteChangefeed`, the TiCDC changefeed may still appear in
monitoring (Grafana, Datadog) until the async workflow completes.

### 3. Not handling "already deleted" as success

Both implementations handle this correctly:
```go
if bizErr.ErrCode() == bizerr.ErrCodeRecordNotFound {
    logger.Info("kernel changefeed is already deleted")
    return nil  // Treat as success, not error
}
```

### 4. Regional API client has no built-in retry

`pkg/regional-api/regional_client.go` uses a plain resty client without retry.
Retry must be handled at the action layer (see Retry Patterns above).
