# Import-Into Schema-Only Tasks Complete Successfully

## Analysis

The premium import-into worker currently treats a table with a schema file but no data files as a terminal failure during schema preparation. That blocks the intended `CreateSchema`-only workflow and forces the whole task into `failed` even when no real import error occurred.

| Area | Impact | Verification | Success Criteria |
| --- | --- | --- | --- |
| Schema-only subtask classification | `internal/model/import/import_into.go` | `go test ./internal/model/import -run '^(TestCollectImportIntoPendingSchemas|TestApplyImportIntoSchemaBatchSuccess)$' -count=1` | A subtask with `schema file + no data files` is routed through `CreateSchema` and no longer fails with `Nothing to import` |
| Preparing-stage completion | `internal/model/import/import_into.go` | `go test ./internal/model/import -run '^(TestResolveImportIntoEstimatePreparationDecision|TestApplyImportIntoEstimatePreparationDecision.*|TestRunImportIntoPreparingStepsOrchestration)$' -count=1` | When all surviving subtasks are schema-only terminal successes, the task completes with the normal success message instead of failing |
| Submission/import decision helpers | `internal/model/import/import_into.go` | `go test ./internal/model/import -run '^(TestResolveImportIntoSubmissionState|TestResolveImportIntoImportingDecision)$' -count=1` | `finished` subtasks with `jobID == 0` are treated as valid schema-only terminal success and skipped by later import phases |
| Regression coverage | `internal/model/import/import_into_prepare_test.go` | `make app-unit-test-local` | Focused helper tests and the local unit suite pass without changing API contracts |

Symbols to adjust:
- `collectImportIntoPendingSchemas`
- `applyImportIntoSchemaBatchSuccess`
- `resolveImportIntoEstimatePreparationDecision`
- `applyImportIntoEstimatePreparationDecision`
- `resolveImportIntoSubmissionState`
- `resolveImportIntoImportingDecision`

Symbols to add:
- constant for the normal import success message
- constant for the schema-only subtask success message
- helper that detects `schema file + no data files`
- helper that marks the overall import-into task completed with the normal success message

Symbols to preserve:
- existing schema creation failure handling
- existing import submit/progress failure handling
- existing `import data is empty` behavior for true no-data imports without schema-only success semantics

## Execution Steps

1. Add explicit schema-only helpers and success messages.
   - Introduce one shared constant for the existing normal task success message.
   - Introduce one shared constant for schema-only subtask completion text: `Schema created successfully. No data files found.`
   - Add a helper that identifies only the intended case: schema file exists and file type is `UNKNOWN_TYPE`.
   - Verification:
     - `go test ./internal/model/import -run '^(TestCollectImportIntoPendingSchemas)$' -count=1`
   - Expected:
     - Only schema-only subtasks take the new path; unrelated no-data edge cases do not silently change behavior.

2. Route schema-only subtasks through schema creation and mark them terminal-success on success.
   - Update `collectImportIntoPendingSchemas()` to stop failing schema-only subtasks.
   - Keep schema-only subtasks in the batch `CreateSchemasTask`.
   - Update `applyImportIntoSchemaBatchSuccess()` so schema-only subtasks become `finished` with no `jobID` and the new success message; data-bearing subtasks remain `pending`.
   - Verification:
     - `go test ./internal/model/import -run '^(TestCollectImportIntoPendingSchemas|TestApplyImportIntoSchemaBatchSuccess)$' -count=1`
   - Expected:
     - Schema-only subtasks are skipped by later import submission logic because they are already terminal-success.

3. Complete the overall task when schema preparation leaves no pending imports.
   - Replace the current “all subtasks terminal after schema prep => fail” branch with:
     - fail if any subtask is failed/canceled,
     - otherwise complete the whole task with the same success message used by the normal importing-complete path.
   - Ensure the completion helper also clears the stored target password for terminal success consistency.
   - Verification:
     - `go test ./internal/model/import -run '^(TestResolveImportIntoEstimatePreparationDecision|TestApplyImportIntoEstimatePreparationDecision.*|TestRunImportIntoPreparingStepsOrchestration)$' -count=1`
   - Expected:
     - A schema-only import ends in `completed` before scaling/importing, but uses the normal task success message.

4. Confirm later-phase aggregate helpers preserve schema-only semantics.
   - Keep `finished + jobID == 0` as a valid terminal state in submission/import decisions.
   - Do not requeue or fail schema-only subtasks in `ScalingUp` or `Importing`.
   - Verification:
     - `go test ./internal/model/import -run '^(TestResolveImportIntoSubmissionState|TestResolveImportIntoImportingDecision)$' -count=1`
   - Expected:
     - Mixed tasks continue importing only the data-bearing subtasks, and all-schema-only tasks never reach submit/import loops.

5. Run the required regression commands.
   - `go test ./internal/model/import -run '^(TestCollectImportIntoPendingSchemas|TestApplyImportIntoSchemaBatchSuccess|TestResolveImportIntoEstimatePreparationDecision|TestApplyImportIntoEstimatePreparationDecision.*|TestResolveImportIntoSubmissionState|TestResolveImportIntoImportingDecision|TestRunImportIntoPreparingStepsOrchestration)$' -count=1`
   - `make app-unit-test-local`
   - Expected:
     - Focused helper coverage passes.
     - The local unit suite passes, or any unrelated pre-existing failures are called out separately.

## Risks

- If schema-only detection is too broad, true no-data/no-schema cases could accidentally look successful. Restrict the new path to `schema file exists + no data files`.
- The task now completes from `Preparing`; completion must reuse the existing success message so downstream clients do not see a new task-level success variant.
- Clearing `TargetPassword` for early completion must still persist correctly through the common state persistence helper.

## Follow-up Fix

The first implementation left a reentry gap in `runImportIntoPreparingSteps()`: if file discovery was already prepared and `resolveImportIntoPreparingDecision()` immediately returned `Failed` or `Completed`, the function returned before applying the side effects that actually moved the task into its terminal state.

| Area | Impact | Verification | Success Criteria |
| --- | --- | --- | --- |
| Preparing reentry terminal handling | `internal/model/import/import_into.go` | `go test ./internal/model/import -run '^(TestRunImportIntoPreparingStepsOrchestration)$' -count=1` | Reentered `Preparing` tasks with already-terminal subtasks transition to `completed` or `failed` instead of staying in `preparing` |

Follow-up execution step:
1. Apply terminal side effects before the early return when the initial preparing decision is `Failed` or `Completed`.
   - Verification:
     - `go test ./internal/model/import -run '^(TestRunImportIntoPreparingStepsOrchestration)$' -count=1`
     - `make app-unit-test-local`
   - Expected:
     - Schema-only completed subtasks and already-failed subtasks both reconcile correctly on reentry without rerunning schema or estimate hooks.

## Follow-up Fix 2

The reservation-enabled preparing flow still used `imp.TotalSize == 0` as the sentinel for "estimate not finished yet". That mixes task state with estimate data and makes `0` ambiguous between "not prepared" and "prepared result is zero".

| Area | Impact | Verification | Success Criteria |
| --- | --- | --- | --- |
| Reservation-enabled estimate gating | `internal/model/import/import_into.go` | `go test ./internal/model/import -run '^(TestResolveImportIntoPreparingDecision|TestRunImportIntoPreparingStepsOrchestration)$' -count=1` | `Preparing` no longer depends on `TotalSize == 0` to revisit estimate work; estimate outcome itself drives the transition to `ScalingUp` |

Follow-up execution step:
1. Make reservation-enabled `Preparing` always route through the estimate branch, and let `prepareImportIntoEstimate()` return an explicit outcome (`wait`, `failed`, `ready`) instead of inferring readiness from `TotalSize`.
   - Verification:
     - `go test ./internal/model/import -run '^(TestResolveImportIntoPreparingDecision|TestRunImportIntoPreparingStepsOrchestration)$' -count=1`
     - `make app-unit-test-local`
   - Expected:
     - Reservation-enabled tasks can re-check the remote estimate task every tick without relying on `TotalSize` as a sentinel, and a successful estimate can advance directly to reservation creation and `ScalingUp`.
