# 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.