# CDC E2E API Mode Integration Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Make Dedicated CDC E2E changefeed cases run under one shared case set with `-api-mode=consoleapi|openapi` (default `consoleapi`), remove standalone `open_api.go`, and route OpenAPI-supported operations through OpenAPI while preserving stable case names.

**Architecture:** Introduce a mode-aware CDC adapter in `test/e2e/internal/tidbcloud/dedicated/cdcapi` so cases and validator stop directly binding to Console API wrappers. Extend the OpenAPI wrapper to cover Create/Edit/Delete/Scale and use Create with `dryRun=true` for TestConnection simulation. Merge OpenAPI assertions into existing CDC cases (instead of a dedicated OpenAPI-only case file) and keep fallback explicit for unsupported operations.

**Tech Stack:** Go, go-swagger generated clients, testify, Makefile-based E2E runner.

---

## Planned File Structure

- **Create:** `test/e2e/internal/tidbcloud/dedicated/cdcapi/client_wrapper.go`  
  Mode-aware CDC adapter (`consoleapi` vs `openapi`) with explicit routing and fallback.
- **Create:** `test/e2e/internal/tidbcloud/dedicated/cdcapi/client_wrapper_test.go`  
  Route matrix and dry-run TestConnection conversion tests.
- **Create:** `test/e2e/internal/tidbcloud/dedicated/openapi/open_api_client_wrapper_test.go`  
  Unit tests for newly added OpenAPI wrapper methods and dry-run path.
- **Create:** `test/e2e/cases/dedicated/changefeed/openapi_assertion_helpers.go`  
  Reusable OpenAPI assertion helpers migrated from `open_api.go`.
- **Create:** `test/e2e/cases/dedicated/changefeed/openapi_assertion_helpers_test.go`  
  Unit tests for event-filter comparison helpers.
- **Modify:** `test/e2e/run_test.go`  
  Add `-api-mode` argument parsing and pass-through to config init.
- **Modify:** `test/e2e/internal/config/config.go`  
  Add `ApiMode` constants, field, validation, and CLI override handling.
- **Modify:** `test/e2e/internal/config/config_test.go`  
  Add tests for default/override/invalid API mode behavior.
- **Modify:** `test/e2e/Makefile`  
  Pass `API_MODE` into go test args.
- **Modify:** `test/e2e/internal/tidbcloud/dedicated/openapi/open_api_client_wrapper.go`  
  Add Create/Edit/Delete/Scale methods and dry-run create support.
- **Modify:** `test/e2e/internal/tidbcloud/dedicated/openapi/singleton_client.go`  
  Add trace-id logging round-tripper and context-friendly transport behavior.
- **Modify (generated):** `test/e2e/internal/tidbcloud/dedicated/openapi/changefeed/**`  
  Regenerated OpenAPI client for full CDC endpoint set.
- **Modify:** `test/e2e/cases/dedicated/common/changfeed_validator.go`  
  Switch validator CDC client dependency to `cdcapi`.
- **Modify:** `test/e2e/cases/dedicated/changefeed/*.go` (CDC case files that instantiate Console CDC wrapper)  
  Use `cdcapi.NewCDCClientWrapper`.
- **Modify:** `test/e2e/cases/dedicated/changefeed/kafka_smoke.go`  
  Integrate OpenAPI assertions from old standalone case.
- **Modify:** `test/e2e/internal/case_driver/cases_registry_dedicated.go`  
  Remove dedicated OpenAPI-only case registration/group references.
- **Delete:** `test/e2e/cases/dedicated/changefeed/open_api.go`
- **Modify:** `docs/e2e-test-user-guide.md`  
  Document new `api_mode` runner parameter.

## Task 1: Add top-level `api-mode` plumbing (TDD)

**Files:**
- Modify: `test/e2e/run_test.go`
- Modify: `test/e2e/internal/config/config.go`
- Modify: `test/e2e/internal/config/config_test.go`
- Modify: `test/e2e/Makefile`

- [ ] **Step 1: Write failing config tests for API mode default and invalid value**

```go
func TestConfigValidate_ApiModeDefaultAndOverride(t *testing.T) {
	cfg := config{}
	cfg.Validate()
	assert.Equal(t, ApiModeConsoleAPI, cfg.ApiMode)

	cfg.ApiMode = ApiModeOpenAPI
	require.NotPanics(t, func() { cfg.Validate() })
}

func TestConfigValidate_ApiModeInvalid(t *testing.T) {
	cfg := config{ApiMode: "bad-mode"}
	require.PanicsWithValue(t,
		"invalid apiMode 'bad-mode', must be one of: [consoleapi openapi]",
		func() { cfg.Validate() },
	)
}
```

- [ ] **Step 2: Run tests to verify failure**

Run: `cd test/e2e && go test ./internal/config -run 'TestConfigValidate_ApiMode(DefaultAndOverride|Invalid)' -count=1`  
Expected: FAIL (`ApiMode*` symbols not defined yet)

- [ ] **Step 3: Implement API mode constants, config validation, CLI parsing, and Makefile passthrough**

```go
// internal/config/config.go
const (
	ApiModeConsoleAPI = "consoleapi"
	ApiModeOpenAPI    = "openapi"
)

var AllAPIModes = []string{ApiModeConsoleAPI, ApiModeOpenAPI}

type config struct {
	// ...
	ApiMode string `yaml:"apiMode"`
}

func InitConfig(env, domain, apiMode string) {
	// existing load/merge logic ...
	GlobalConfig = &combinedConfig
	if domain != "" {
		GlobalConfig.Domain = domain
	}
	if apiMode != "" {
		GlobalConfig.ApiMode = apiMode
	}
	GlobalConfig.Validate()
}

func (t *config) Validate() {
	if t.Domain == "" {
		t.Domain = DomainCDC
	}
	if t.ApiMode == "" {
		t.ApiMode = ApiModeConsoleAPI
	}
	if !pie.Contains(AllAPIModes, t.ApiMode) {
		panic(fmt.Sprintf("invalid apiMode '%s', must be one of: %v", t.ApiMode, AllAPIModes))
	}
	// existing validation...
}
```

```go
// run_test.go
var apiMode string

func parseArgsWithDefaults() {
	flag.StringVar(&env, "env", "", "the environment of test")
	flag.StringVar(&domain, "domain", DomainCDC, fmt.Sprintf("the domain of test cases: %v", AllDomains))
	flag.StringVar(&apiMode, "api-mode", ApiModeConsoleAPI, fmt.Sprintf("api mode: %v", AllAPIModes))
	flag.Parse()
	// existing defaults...
}

func runTestSuite(t *testing.T, suiteName string, registerCases func()) {
	// ...
	InitConfig(env, domain, apiMode)
	// ...
}
```

```makefile
# test/e2e/Makefile
GOMAXPROCS=8 gotestsum ... ./run_dedicated_test.go ./run_test.go -args -env=$(ENV) \
  $(if $(DOMAIN),-domain=$(DOMAIN)) \
  $(if $(API_MODE),-api-mode=$(API_MODE))
```

- [ ] **Step 4: Run tests again**

Run: `cd test/e2e && go test ./internal/config -run 'TestConfigValidate_ApiMode(DefaultAndOverride|Invalid)' -count=1`  
Expected: PASS

- [ ] **Step 5: Verify compile for test runner entrypoints**

Run: `cd test/e2e && go test ./... -run TestNonExistent -count=1`  
Expected: PASS (compile-only signal, zero tests run)

- [ ] **Step 6: Commit**

```bash
cd test/e2e
git add run_test.go internal/config/config.go internal/config/config_test.go Makefile
git commit -m "feat(e2e): add top-level api-mode flag and config validation"
```

## Task 2: Regenerate OpenAPI CDC client and extend wrapper (TDD)

**Files:**
- Modify (generated): `test/e2e/internal/tidbcloud/dedicated/openapi/changefeed/**`
- Modify: `test/e2e/internal/tidbcloud/dedicated/openapi/open_api_client_wrapper.go`
- Modify: `test/e2e/internal/tidbcloud/dedicated/openapi/singleton_client.go`
- Create: `test/e2e/internal/tidbcloud/dedicated/openapi/open_api_client_wrapper_test.go`

- [ ] **Step 1: Write failing wrapper tests for new methods and dry-run create path**

```go
func TestCreateChangefeed_DryRunTrue(t *testing.T) {
	svc := &fakeChangefeedService{}
	w := newWrapperForTest(t, svc)
	_, err := w.CreateChangefeed(&changefeed_service.CreateChangefeedBody{
		Changefeed: &model.V1beta1Changefeed{},
		DryRun:     true,
	})
	require.NoError(t, err)
	require.True(t, svc.createCalled)
	require.True(t, svc.lastCreateParams.Body.DryRun)
}

func TestScaleChangefeed_CallsOpenAPI(t *testing.T) {
	svc := &fakeChangefeedService{}
	w := newWrapperForTest(t, svc)
	err := w.ScaleChangefeed("123", &changefeed_service.ScaleChangefeedBody{
		ReplicationCapacity: "4rcu",
	})
	require.NoError(t, err)
	require.True(t, svc.scaleCalled)
}
```

- [ ] **Step 2: Run tests to verify failure**

Run: `cd test/e2e && go test ./internal/tidbcloud/dedicated/openapi -run 'Test(CreateChangefeed_DryRunTrue|ScaleChangefeed_CallsOpenAPI)' -count=1`  
Expected: FAIL (missing wrapper methods/fake hook points)

- [ ] **Step 3: Regenerate OpenAPI changefeed client**

Run: `cd test/e2e && make generate-changefeed-open-api-client`  
Expected: PASS and regenerated files under `internal/tidbcloud/dedicated/openapi/changefeed/`

- [ ] **Step 4: Implement wrapper methods and context-aware transport logging**

```go
// open_api_client_wrapper.go
type changefeedServiceClient interface {
	CreateChangefeed(params *changefeed_service.CreateChangefeedParams, opts ...changefeed_service.ClientOption) (*changefeed_service.CreateChangefeedOK, error)
	EditChangefeedDownstreamConfig(params *changefeed_service.EditChangefeedDownstreamConfigParams, opts ...changefeed_service.ClientOption) (*changefeed_service.EditChangefeedDownstreamConfigOK, error)
	DeleteChangefeed(params *changefeed_service.DeleteChangefeedParams, opts ...changefeed_service.ClientOption) (*changefeed_service.DeleteChangefeedOK, error)
	ScaleChangefeed(params *changefeed_service.ScaleChangefeedParams, opts ...changefeed_service.ClientOption) (*changefeed_service.ScaleChangefeedOK, error)
	// existing methods...
}

func (c *ChangefeedOpenApiClientWrapper) ctx() context.Context {
	return log.WithLogger(context.Background(), c.lgr)
}

func (c *ChangefeedOpenApiClientWrapper) CreateChangefeed(body *changefeed_service.CreateChangefeedBody) (*changefeed_service.CreateChangefeedOK, error) {
	params := changefeed_service.NewCreateChangefeedParamsWithContext(c.ctx()).WithBody(body)
	resp, err := c.client.CreateChangefeed(params)
	if err != nil {
		c.lgr.Warn("create changefeed failed, params: %s, error: %s", misc.JSON(params), err.Error())
		return nil, err
	}
	c.lgr.Info("create changefeed successfully, params: %s, resp: %s", misc.JSON(params), misc.JSON(resp))
	return resp, nil
}
```

```go
// singleton_client.go
transport := httptransport.New(parsedURL.Host, parsedURL.Path, []string{parsedURL.Scheme})
transport.Transport = &digest.Transport{
	Username: GlobalConfig.OpenApi.PublicKey,
	Password: GlobalConfig.OpenApi.PrivateKey,
	Transport: &httputil.TraceIDLoggingRoundTripper{
		Proxied: http.DefaultTransport,
	},
}
```

- [ ] **Step 5: Run wrapper tests**

Run: `cd test/e2e && go test ./internal/tidbcloud/dedicated/openapi -run 'Test(CreateChangefeed_DryRunTrue|ScaleChangefeed_CallsOpenAPI)' -count=1`  
Expected: PASS

- [ ] **Step 6: Commit**

```bash
cd test/e2e
git add internal/tidbcloud/dedicated/openapi/open_api_client_wrapper.go \
  internal/tidbcloud/dedicated/openapi/open_api_client_wrapper_test.go \
  internal/tidbcloud/dedicated/openapi/singleton_client.go \
  internal/tidbcloud/dedicated/openapi/changefeed
git commit -m "feat(e2e): extend changefeed openapi wrapper and regenerate sdk"
```

## Task 3: Implement mode-aware CDC adapter with dry-run TestConnection (TDD)

**Files:**
- Create: `test/e2e/internal/tidbcloud/dedicated/cdcapi/client_wrapper.go`
- Create: `test/e2e/internal/tidbcloud/dedicated/cdcapi/client_wrapper_test.go`

- [ ] **Step 1: Write failing route-matrix and dry-run conversion tests**

```go
func TestPauseChangefeed_OpenAPIMode_RoutesToOpenAPI(t *testing.T) {
	f := newFakeDeps()
	w := newModeAwareWrapperForTest(config.ApiModeOpenAPI, f)
	err := w.PauseChangefeed(fakeCluster(), "cf-1")
	require.NoError(t, err)
	require.True(t, f.openapi.pauseCalled)
	require.False(t, f.console.pauseCalled)
}

func TestGetChangefeedKernelConfig_OpenAPIMode_FallsBackToConsole(t *testing.T) {
	f := newFakeDeps()
	w := newModeAwareWrapperForTest(config.ApiModeOpenAPI, f)
	_, err := w.GetChangefeedKernelConfig(fakeCluster(), "cf-1")
	require.NoError(t, err)
	require.True(t, f.console.getKernelCalled)
}

func TestTestConnectionWithParams_OpenAPIMode_UsesCreateDryRun(t *testing.T) {
	f := newFakeDeps()
	w := newModeAwareWrapperForTest(config.ApiModeOpenAPI, f)
	err := w.TestConnectionWithParams(fakeKafkaTestConnectorParams())
	require.NoError(t, err)
	require.True(t, f.openapi.createCalled)
	require.True(t, f.openapi.lastCreateBody.DryRun)
}
```

- [ ] **Step 2: Run tests to verify failure**

Run: `cd test/e2e && go test ./internal/tidbcloud/dedicated/cdcapi -run 'Test(PauseChangefeed_OpenAPIMode_RoutesToOpenAPI|GetChangefeedKernelConfig_OpenAPIMode_FallsBackToConsole|TestConnectionWithParams_OpenAPIMode_UsesCreateDryRun)' -count=1`  
Expected: FAIL (adapter package not implemented)

- [ ] **Step 3: Implement adapter wrapper and fallback policy**

```go
type CDCClientWrapper struct {
	mode    string
	console consoleClient
	openapi openapiClient
	lgr     log.SimpleLogger
}

func NewCDCClientWrapper(lgr log.SimpleLogger) *CDCClientWrapper {
	return &CDCClientWrapper{
		mode:    config.GlobalConfig.ApiMode,
		console: consoleapi.NewCDCClientWrapper(lgr),
		openapi: openapi.NewChangefeedOpenApiClientWrapper(lgr),
		lgr:     lgr,
	}
}

func (c *CDCClientWrapper) PauseChangefeed(tidbCluster *model.TiDBDedicatedEntity, changefeedID string) error {
	if c.mode == config.ApiModeOpenAPI {
		return c.openapi.PauseChangefeed(changefeedID)
	}
	return c.console.PauseChangefeed(tidbCluster, changefeedID)
}

func (c *CDCClientWrapper) TestConnectionWithParams(params *connector_service.TestConnectorParams) error {
	if c.mode != config.ApiModeOpenAPI {
		return c.console.TestConnectionWithParams(params)
	}
	body, err := buildCreateDryRunBodyFromTestParams(params)
	if err != nil {
		return err
	}
	_, err = c.openapi.CreateChangefeed(body)
	return err
}
```

- [ ] **Step 4: Re-run adapter tests**

Run: `cd test/e2e && go test ./internal/tidbcloud/dedicated/cdcapi -run 'Test(PauseChangefeed_OpenAPIMode_RoutesToOpenAPI|GetChangefeedKernelConfig_OpenAPIMode_FallsBackToConsole|TestConnectionWithParams_OpenAPIMode_UsesCreateDryRun)' -count=1`  
Expected: PASS

- [ ] **Step 5: Commit**

```bash
cd test/e2e
git add internal/tidbcloud/dedicated/cdcapi/client_wrapper.go \
  internal/tidbcloud/dedicated/cdcapi/client_wrapper_test.go
git commit -m "feat(e2e): add mode-aware cdcapi wrapper with dry-run test connection"
```

## Task 4: Switch validator and CDC cases from Console wrapper to `cdcapi` (TDD-style compile safety)

**Files:**
- Modify: `test/e2e/cases/dedicated/common/changfeed_validator.go`
- Modify: `test/e2e/cases/dedicated/changefeed/*.go` (files constructing `consoleapi.NewCDCClientWrapper`)

- [ ] **Step 1: Add a failing validator test proving it uses wrapper methods via injected client**

```go
func TestCreateDummyChangefeedWithPublicKafka_UsesClientCreate(t *testing.T) {
	fc := &fakeCDCClient{}
	v := NewChangfeedValidatorWithClient(testLogger(t), fc)
	_, err := v.CreateDummyChangefeedWithPublicKafka("n", "2rcu", fakeCluster(), fakeKafka(), []string{"db.*"}, "topic")
	require.NoError(t, err)
	require.True(t, fc.createCalled)
}
```

- [ ] **Step 2: Run test to verify failure**

Run: `cd test/e2e && go test ./cases/dedicated/common -run TestCreateDummyChangefeedWithPublicKafka_UsesClientCreate -count=1`  
Expected: FAIL (`NewChangfeedValidatorWithClient` missing)

- [ ] **Step 3: Implement validator client injection and default constructor path**

```go
type ChangfeedValidator struct {
	lgr  log.SimpleLogger
	cdcw cdcClient
}

func NewChangfeedValidator(lgr log.SimpleLogger) *ChangfeedValidator {
	return NewChangfeedValidatorWithClient(lgr, cdcapi.NewCDCClientWrapper(lgr))
}

func NewChangfeedValidatorWithClient(lgr log.SimpleLogger, cdcw cdcClient) *ChangfeedValidator {
	return &ChangfeedValidator{lgr: lgr, cdcw: cdcw}
}
```

- [ ] **Step 4: Replace case-level client construction with `cdcapi.NewCDCClientWrapper`**

```go
// before
cdcApiWrapper := consoleapi.NewCDCClientWrapper(k.Logger())
// after
cdcApiWrapper := cdcapi.NewCDCClientWrapper(k.Logger())
```

- [ ] **Step 5: Run validator tests + compile CDC changefeed cases**

Run: `cd test/e2e && go test ./cases/dedicated/common ./cases/dedicated/changefeed -run TestNonExistent -count=1`  
Expected: PASS (compile passes for all switched files)

- [ ] **Step 6: Commit**

```bash
cd test/e2e
git add cases/dedicated/common/changfeed_validator.go \
  cases/dedicated/common/changfeed_validator_test.go \
  cases/dedicated/changefeed
git commit -m "refactor(e2e): switch dedicated cdc cases to cdcapi wrapper"
```

## Task 5: Merge OpenAPI assertions into existing case and remove standalone `open_api.go`

**Files:**
- Create: `test/e2e/cases/dedicated/changefeed/openapi_assertion_helpers.go`
- Create: `test/e2e/cases/dedicated/changefeed/openapi_assertion_helpers_test.go`
- Modify: `test/e2e/cases/dedicated/changefeed/kafka_smoke.go`
- Modify: `test/e2e/internal/case_driver/cases_registry_dedicated.go`
- Delete: `test/e2e/cases/dedicated/changefeed/open_api.go`

- [ ] **Step 1: Write failing helper tests for event-filter comparison logic**

```go
func TestIsEventFiltersEquals(t *testing.T) {
	expected := []*openapiModel.V1beta1EventFilter{
		{TableMatchers: []string{"db.t1"}, IgnoredEvents: []string{"update"}},
	}
	actual := []*openapiModel.V1beta1EventFilter{
		{TableMatchers: []string{"db.t1"}, IgnoredEvents: []string{"update"}},
	}
	require.True(t, isEventFiltersEquals(expected, actual))
}
```

- [ ] **Step 2: Run helper tests to verify failure**

Run: `cd test/e2e && go test ./cases/dedicated/changefeed -run TestIsEventFiltersEquals -count=1`  
Expected: FAIL (helper not extracted/defined)

- [ ] **Step 3: Extract OpenAPI assertion helpers and integrate into `kafka_smoke.go` when `api-mode=openapi`**

```go
func runOpenAPIAssertionsIfNeeded(k *kafkaChangefeedSmokeCase, cluster *TiDBDedicatedEntity, cfID string, dbName string) error {
	if GlobalConfig.ApiMode != ApiModeOpenAPI {
		return nil
	}
	openAPIClient := openapi.NewChangefeedOpenApiClientWrapper(k.Logger())
	if _, err := openAPIClient.GetChangefeed(cfID); err != nil {
		return err
	}
	// set/get/clear event filters
	return verifyOpenAPIEventFilters(openAPIClient, cfID, dbName)
}
```

- [ ] **Step 4: Remove standalone OpenAPI case registration and delete `open_api.go`**

```go
// internal/case_driver/cases_registry_dedicated.go
// remove:
// awsChangefeedOpenApiCase := changefeed.NewChangefeedOpenApiCase(AWS)
// gcpChangefeedOpenApiCase := ...
// azureChangefeedOpenApiCase := ...
// addCases(awsChangefeedOpenApiCase, gcpChangefeedOpenApiCase, azureChangefeedOpenApiCase)
```

- [ ] **Step 5: Run changefeed package + case driver compile/tests**

Run: `cd test/e2e && go test ./cases/dedicated/changefeed ./internal/case_driver -run TestNonExistent -count=1`  
Expected: PASS

- [ ] **Step 6: Commit**

```bash
cd test/e2e
git add cases/dedicated/changefeed/openapi_assertion_helpers.go \
  cases/dedicated/changefeed/openapi_assertion_helpers_test.go \
  cases/dedicated/changefeed/kafka_smoke.go \
  internal/case_driver/cases_registry_dedicated.go
git rm cases/dedicated/changefeed/open_api.go
git commit -m "refactor(e2e): merge openapi assertions into shared cdc cases"
```

## Task 6: Documentation update and final regression pass

**Files:**
- Modify: `docs/e2e-test-user-guide.md`

- [ ] **Step 1: Update user guide with `api_mode` parameter and usage examples**

```markdown
| `api_mode` | | `consoleapi` | CDC API backend mode: `consoleapi`, `openapi` |
```

```markdown
**Run CDC smoke on dev with OpenAPI mode:**
- `domain`: `cdc`, `env`: `dev`, `case_group`: `smoke`, `api_mode`: `openapi`
```

- [ ] **Step 2: Run focused regression command set**

Run: `cd test/e2e && go test ./internal/config ./internal/tidbcloud/dedicated/openapi ./internal/tidbcloud/dedicated/cdcapi ./cases/dedicated/common ./cases/dedicated/changefeed ./internal/case_driver -count=1`  
Expected: PASS

- [ ] **Step 3: Run formatter on changed Go files**

Run: `cd test/e2e && gofmt -w internal/config/config.go internal/config/config_test.go internal/tidbcloud/dedicated/openapi/open_api_client_wrapper.go internal/tidbcloud/dedicated/openapi/singleton_client.go internal/tidbcloud/dedicated/cdcapi/client_wrapper.go internal/tidbcloud/dedicated/cdcapi/client_wrapper_test.go cases/dedicated/common/changfeed_validator.go cases/dedicated/common/changfeed_validator_test.go cases/dedicated/changefeed/kafka_smoke.go cases/dedicated/changefeed/openapi_assertion_helpers.go cases/dedicated/changefeed/openapi_assertion_helpers_test.go run_test.go`

Expected: command exits 0 and only formatting changes are produced.

- [ ] **Step 4: Re-run focused regression after formatting**

Run: `cd test/e2e && go test ./internal/config ./internal/tidbcloud/dedicated/openapi ./internal/tidbcloud/dedicated/cdcapi ./cases/dedicated/common ./cases/dedicated/changefeed ./internal/case_driver -count=1`  
Expected: PASS

- [ ] **Step 5: Commit**

```bash
git add test/e2e docs/e2e-test-user-guide.md
git commit -m "docs(e2e): document api-mode and finalize cdc openapi integration"
```

## Spec Coverage Check

- Top-level `-api-mode` flag (default consoleapi): covered by Task 1.
- Dedicated CDC scope only: tasks only touch `test/e2e/cases/dedicated/changefeed` + shared CDC test plumbing.
- Same case set in both modes with unchanged names: covered by Tasks 4-5.
- OpenAPI integration for `Create/Edit/Delete/Scale`: covered by Tasks 2-3.
- `TestConnection` via `Create + dryRun=true`: covered by Task 3.
- Remove standalone `open_api.go`, merge assertions into existing case(s): covered by Task 5.
- Validation and regression verification: covered by Task 6.

## Placeholder Scan

- No `TODO`, `TBD`, or deferred instructions remain.
- Every code-change step contains concrete code snippets.
- Every test/run step includes exact commands and expected outcomes.

## Type Consistency Check

- API mode constants are consistently named `ApiModeConsoleAPI` / `ApiModeOpenAPI`.
- CDC wrapper type used in cases/validator is consistently `cdcapi.CDCClientWrapper`.
- OpenAPI dry-run simulation uses `CreateChangefeed` request with `DryRun=true` consistently across adapter and tests.
