# domain/workflow/action - DAG Workflow Actions

**53 Go files** | Atomic operations for cluster lifecycle management

## OVERVIEW

Workflow actions implement the `Action` interface for DAG-based execution. Each action is a single step in a workflow (create cluster, scale component, apply config, etc.).

## STRUCTURE

```
action/
├── *.go                   # Action implementations
├── *_test.go              # Table-driven tests
├── br/                    # Backup/Restore actions (48 files) → see br/AGENTS.md
├── registry.go            # Action registration
└── action.go              # Action interface definition
```

## WHERE TO LOOK

| Task | File | Notes |
|------|------|-------|
| Add new action | Create `<action_name>.go` | Implement Action interface |
| Register action | `registry.go` | Add to registry map |
| Understand interface | `action.go` | Action interface definition |

## CONVENTIONS

### Action Interface
```go
type Action interface {
    Name() string
    Invoke(ctx context.Context, args ActionArgs) error
}
```

### Action Implementation Pattern
```go
const ApplyTiDBGroupActionName = "ApplyTiDBGroup"

type ApplyTiDBGroupAction struct {
    dataplaneManager  dataplane.Manager
    sharedPoolRepo    repo.SharedPoolRepo
    tidbGroupRepo     repo.TiDBGroupRepo
}

func (a *ApplyTiDBGroupAction) Name() string {
    return ApplyTiDBGroupActionName
}

func (a *ApplyTiDBGroupAction) Invoke(ctx context.Context, args ActionArgs) error {
    // 1. Parse args
    // 2. Load entities
    // 3. Execute operation
    // 4. Update state
    return nil
}
```

### Naming Convention
- Action name constant: `<ActionName>ActionName` (e.g., `ApplyTiDBGroupActionName`)
- Action struct: `<ActionName>Action` (e.g., `ApplyTiDBGroupAction`)
- File name: `snake_case.go` (e.g., `apply_tidb_group.go`)

### Templates: One Kind Per `.templ` File

Actions that render Kubernetes manifests via `templateExecuteWithVersionResolver` (`apply_component_group.go`) must keep **exactly one Kind per `.templ`** file. Components that need multiple kinds split into multiple files and call the helper once per file.

```go
// Correct (apply_tici.go pattern):
templateExecuteWithVersionResolver(ctx, remoteCli, "tici_meta_configmap", "", args, false, ...)
templateExecuteWithVersionResolver(ctx, remoteCli, "tici_svc",            "", args, false, ...)
templateExecuteWithVersionResolver(ctx, remoteCli, "tici_meta",           "", args, false, ...)
```

The render path goes through `k8s.GenerateUnstructuredFromYaml`, which returns a single `*Unstructured`. Multi-document YAML (separated by `---`) used to be silently truncated to its first kind by `sigs.k8s.io/yaml.YAMLToJSON` and applied without an error - that regression is what cassandra-proxy hit in #2531 (#2553 split, #2555 helper guard). Since #2555 the helper rejects multi-doc input with `ErrMultiDocYAML`, so a violation now fails fast at apply time, but you should still split the templates up front to match the rest of the codebase.

Audited as of 2026-04-29: every file under `deployments/templates/*.templ` (56 files) contains exactly one Kind.

## ANTI-PATTERNS

- **NEVER** call other actions directly; use DAG composition
- **NEVER** return partial success; actions must be atomic
- **NEVER** skip logging action start/completion
- **NEVER** put more than one Kind in a `.templ` file (see "Templates: One Kind Per `.templ` File" above) - the apply helper rejects multi-doc input
