# Error Contract

## Atomic failure rule

Treat each invocation as one transaction. If any input, price, processing, or output-validation error occurs:

- do not return a formal daily workbook;
- do not return a formal monthly workbook;
- do not modify the supplied monthly base;
- remove candidates created by the failed invocation;
- write `异常清单.xlsx`, `result.json`, and (when the requested output path is valid) `structured-result.json` with failed status and `activation_eligible: false`.

The structured failure payload must still retain every reservation that was parsed, including whitelist exclusions, duplicate lineage, row validation failures, and price-unmatched rows. It must not reference a daily or monthly artifact.

## Exit codes

| Exit | Meaning |
|---:|---|
| `0` | Successful processing and independent output validation |
| `2` | Business-data failure, including price or workbook-contract failure |
| `3` | Invocation/input contract failure |
| `4` | Unexpected internal or dependency failure |

## Exception workbook columns

`ERROR_CODE`, `STAGE`, `SOURCE_LOCATION`, `COMPANY_NAME`, `RATE_CODE`, `EFFECTIVE_RATE_AMOUNT`, `CONFIRMATION_NO`, `MESSAGE`.

Leave unavailable location fields blank. Write one row per independently actionable error.

## Stable error-code families

- `INPUT_*`: missing file, wrong extension, unsafe XML declaration, unreadable XLSX, or output-path violation.
- `XML_*`: parse error, root/structure mismatch, missing rate code, multiple business dates, missing/invalid retained fields, or invalid dates. Same-day arrival/departure is valid; a departure before arrival is `XML_NEGATIVE_NIGHTS`.
- `PRICE_*`: price-header error, missing/non-numeric value, ambiguous company keyword identity, duplicate normalized key, or unmatched reservation.
- `MONTHLY_*`: missing standard sheet, 19/20-column header mismatch, ambiguous company keyword identity, mixed/wrong month, inconsistent company sheet, invalid existing row, or invalid derived monthly total/KB value.
- `OUTPUT_*`: missing output, header/type/formula/order/routing/count mismatch, derived daily or monthly total/KB mismatch, or filename mismatch.
- `STRUCTURED_*` / `OUTPUT_STRUCTURED_*`: invalid all-source reconciliation, derived key, artifact hash, file metadata, outcome, or Finance payload field.
- `INTERNAL_ERROR`: unexpected exception; include a concise diagnostic without secrets.

## Result invariants

- `status` is exactly `success` or `failed`.
- All output filenames are basenames relative to the caller-provided output directory.
- Daily-mode v2 success has a non-null daily filename, a null monthly filename, and a null exception filename.
- Legacy daily-monthly v1 success has non-null daily and monthly filenames and a null exception filename.
- Failure has null daily/monthly filenames and a non-null exception filename.
- `errors` is empty on success and non-empty on failure.
- `metrics.channels` is a deterministic array of `{ "worksheet": string, "rows": integer }` entries. Standard worksheets use the fixed workbook order; additional company worksheets follow in name order. This array form is required so Codex structured output can validate future company names without dynamic JSON keys.
- Never place guest detail in `message`; keep row-specific data inside the downloadable exception workbook and structured error entries only.
- `structured-result.json` success requires `activation_eligible: true`, zero `validation_failed/price_unmatched` rows, exact artifact hashes, and a balanced all-source outcome count.
- `structured-result.json` failure requires `activation_eligible: false`, `output_rows: 0`, no daily/monthly artifact, and a non-empty error array.

## Database atomicity boundary

This processor never writes or changes database state. A downstream adapter may begin a database transaction only after processor exit `0` and successful revalidation of `structured-result.json`. That transaction must insert all file/version/record/channel rows, perform exact Group Code lookup, and switch the current daily version as its final step. Any adapter failure rolls back completely and leaves the previous current version unchanged.
