HANDOFF NOTE: project "scheduler" (target only; archive-* records are unrelated and were discarded)

1. Interface contract (fixed)
evaluate(payload) -> {selected:[id], blocked:[id], rejected:[id]}
- payload.now: offset-aware ISO datetime.
- payload.appointments: each has id, start (offset-aware ISO), duration (positive minutes), priority (integer), cancelled (boolean).
- No mutation of input. Use zoneinfo or equivalent for timezone conversion.

2. CURRENT configuration (after Updates 1-5)
- timezone = "America/New_York"
- cutoff_hour = 17
- cutoff_inclusive = true
- lead_minutes = 30
- adjacency_allowed = true (Update 4 overrode Update 2's false)
- cancelled_blocks = true (Update 5 overrode Update 2's false)
- duplicate_policy = "first" (Update 5 overrode Update 2's "last")
- duration_max = 120 (Update 4 overrode Update 2's 90)
- capacity = 1 (Update 3)
- report_order = "id" (Update 3)
- blackout_day = "2026-11-02" (Update 5)
- priority_high_first = false (Update 5), so priority is processed ascending

3. Algorithm contract
1. Deduplicate by id, keeping the first insertion position. The policy is "first", so keep the first row's values. The retained row's original index is the actual original index of the row whose values are kept. (With "last", the index would be the last row's index.)
2. Process stably by priority (ascending under the current setting). Ties are broken by original index.
3. Time arithmetic uses UTC elapsed minutes, not local wall-clock minutes. This matters across the DST change on 2026-11-01 in New_York.
4. Validity checks. Failing any one puts the id in rejected:
   - duration > 0 and <= duration_max (120).
   - start >= now + lead_minutes (30).
   - Convert start to the timezone. If its local date is blackout_day, reject.
   - Cutoff is the local calendar day of start at cutoff_hour:00 in that zone (17:00). End must be <= cutoff when cutoff_inclusive is true (current), otherwise < cutoff.
5. Cancelled appointments:
   - If cancelled_blocks is false, reject them.
   - If true (current), they may reserve capacity and are reported in blocked, not selected.
   - Open question: whether a cancelled appointment must also pass the validity checks before reserving. The contract implies checks apply to all, and invalid ones go to rejected. Confirm this.
6. Capacity and conflicts:
   - Accept an appointment only if the number of already accepted intervals that intersect it is < capacity (1).
   - Count intersecting accepted intervals, not peak concurrency.
   - With adjacency_allowed = true, intervals sharing only an endpoint do not overlap. With false, they do.
   - A conflicting appointment goes to rejected.
   - Accepted cancelled ones (blocked) count as accepted intervals.
7. Output:
   - Each list is sorted by report_order, currently "id". The alternative "input" means retained original index.
   - Id sorting is presumably by plain string comparison. Confirm if ids may be non-strings or numeric.
   - Every invalid or conflicting id goes to rejected.

4. Unresolved or to-confirm items
- Whether the blackout_day is a local date compared to the converted start's local date (assumed yes).
- Whether cutoff applies to cancelled appointments (assumed yes, same validity pipeline).
- Behavior for malformed payloads (naive datetimes, non-integer priority) is unspecified. The contract assumes valid input.
- No later updates are known yet. Later updates replace only the keys they name and leave the others unchanged.