# PiTR Add-Index Async Feature Gate Design

## Context

PiTR restore may spend significant time on synchronous add-index work before the job reaches the final serving state.
To reduce restore RTO, the workflow can ask BR to output add-index SQL and defer execution, by injecting:

`--pitr-add-index-sql-storage={log_backup_storage_path_with_restore_bucket}/restores/restore-{restore_id}/add-index-sql.sql`

This behavior is controlled by a feature gate.

## Scope and Decisions

1. Scope is **PiTR restore only** (`restore.Mode == pitr`).
2. The append check is **not** gated by `RestoreToClusterType` (`new`/`clone`/`exist`); current implementation applies to PiTR mode uniformly.
3. User-provided restore options are honored; if `--pitr-add-index-sql-storage=...` already exists, no duplicate is appended.
4. Keep current wiring on `FeatureGate` (no migration to `FeatureGateV2` in this PR).

## Detailed Design

### 1. Feature gate key

In `app/internal/feature_gate/interface.go`:

- `AddIndexAsyncForPiTR = "pitr-add-index-async"`

### 2. Append flow

In `app/workflow/action/restore/pitr/create/running_restore.go`:

1. `genRestoreOptionParam` builds base restore options from restore target type and BR version.
2. `appendPitrAddIndexSQLStorageOptionIfNeeded` post-processes the options:
   - Returns early when `restore.Mode != RestoreModePiTR`.
   - Checks gate `pitr-add-index-async` via `FeatureGate.IsFeatureEnabled`.
   - Checks restore image tag constraint `>= v8.5.4` before appending.
     - Image tag is normalized first (for example `v9.0.0-beta.1.pre` -> `v9.0.0`) before semver check.
   - Gate query uses:
     - `TenantID`, `ProjectID` from `restore`
     - `ClusterID` from source `logBackup.ClusterID`
     - `Region` from target `restore.Region`
     - `InfraProvider` from target restore `bucket.Provider`
   - If any option already starts with `--pitr-add-index-sql-storage`, skip append.
3. If append is needed, build the storage path via `bucketStoragePathForIndexFile`.

### 3. Storage path derivation

`bucketStoragePathForIndexFile` derives path from log backup storage + restore bucket:

1. `LogBackupRepo.Load(restore.LogBackupID)`
2. `BucketRepo.Load(restore.BucketID)`
3. Replace bucket part in log backup storage URI with restore bucket URI (`Bucket.URIInBucket`)
4. Append restore-scoped suffix:
   - `{converted_log_backup_storage}/restores/restore-{restore.ID}/add-index-sql.sql`

This path is no longer derived from backup-setting master bucket metadata.

### 4. Error handling

- If log backup / bucket lookup fails, return the underlying error.
- If `restore.LogBackupID` is empty in PiTR mode, return explicit internal error:
  - `log backup id is empty for restore id <id>`
- Gate-off path preserves original options unchanged.

## Affected Files

- `app/internal/feature_gate/interface.go`
- `app/workflow/action/restore/pitr/create/running_restore.go`
- `app/workflow/action/restore/pitr/create/running_restore_test.go`

## Test Plan

Primary unit coverage is in:

- `TestRunRestoring_appendPitrAddIndexSQLStorageOptionIfNeeded`
- `Test_supportPitrAddIndexAsyncByRestoreImageTag`

Key cases:

1. Snapshot mode skips gate path.
2. PiTR + gate off keeps options unchanged.
3. PiTR + gate on and image tag >= v8.5.4 appends one add-index storage option.
4. Existing add-index option is deduplicated.
5. Lower/invalid image tag skips appending.
6. Log-backup/bucket derivation failures return errors.

Verification commands:

1. `go test ./app/workflow/action/restore/pitr/create -count=1`
2. `make app-unit-test-local`

## Risks and Mitigations

1. **Risk:** URI conversion mistakes (bucket swap/path append) lead to invalid SQL output path.
   - **Mitigation:** Fail fast on log backup/bucket lookup and URI parse errors; cover conversion branches in unit tests.
2. **Risk:** Duplicate BR option conflicts with user custom options.
   - **Mitigation:** Prefix-based dedup before appending.
3. **Risk:** Unexpected behavior change for non-PiTR restores.
   - **Mitigation:** Early return by restore mode ensures snapshot path unchanged.

## Non-Goals

1. Migrating restore actions from `FeatureGate` to `FeatureGateV2`.
2. Changing restore DAG structure or execution ordering.
3. Introducing new public API fields.
