### Current requirements
- Python 3.12 standard library only; public signature: `plan(rows, budget_cents, excluded_channels=())`.
- Never mutate rows or exclusions; never use floating-point arithmetic for money.
- Budget must be a nonnegative integer, excluding booleans; otherwise raise `ValueError`.
- Deduplicate by id before filtering, validation or ordering: only the first occurrence represents an id.
- Ignore archived rows; missing `archived` means false.
- Normalize channels and exclusions with `strip().casefold()`. Missing channel means `""`; empty exclusions exclude nothing. Exclusions are a list or tuple of strings.
- Validate costs only for eligible rows, before budget selection. Invalid costs in duplicate, archived or excluded rows are ignored.
- If `price` exists, require a finite nonnegative decimal string in dollars, allowing surrounding whitespace. Convert each row separately to integer cents using `Decimal` and `ROUND_HALF_UP`.
- Otherwise require nonnegative integer `cost_cents`, excluding booleans. Missing or invalid legacy costs and invalid prices raise `ValueError`.
- Traverse eligible proposals by descending integer priority; missing priority is zero. Ties retain input order.
- Select affordable proposals; skip expensive ones and continue.
- Return exactly `selected_ids`, `spent_cents`, `remaining_cents`, and `skipped_ids`. Both id lists follow traversal order. Empty input yields empty lists, zero spent and the unchanged budget.

### Explicitly superseded
Original input-order traversal was replaced by stable descending-priority traversal.

### Specific cases
- **First duplicate archived:** later occurrences remain ignored.
- **Budget equals cost:** select it; remaining budget becomes zero.
- **Both prices supplied:** `price` takes precedence; `cost_cents` is ignored.