Re-home the proven first-wins race from sched.race(*Task) onto *Future handles
+ the Io protocol; the old Task-based race is REPLACED (ufcs overload-by-receiver
is rejected, and only 1821 used it).
- Protocol: add Io.current_park() -> ParkToken — the running fiber as a token,
captured WITHOUT parking — so race can register the SAME coordinator across N
futures' park slots, then park once via suspend_raw; any completion readies it.
Scheduler returns {self.current} (bails outside a fiber); CBlockingIo returns
{null} (race never parks there — futures are born .ready).
- race :: ufcs (io: Io, futures: $T) -> RaceResult(T), kept in sched.sx (it needs
meta.sx's make_enum/make_variant; pulling that into the io.sx prelude part-file
would cycle). Winner scan -> register/park/deregister -> make_variant the winner
-> Phase-3 cancel each still-.pending loser (no join). RaceResult reused
unchanged (*Future(R) projects field 0 'value' -> R).
- TRUE-cancel: parked losers stop at their next suspend (timers evicted by cancel's
wake), so race returns at WINNER-time, not slowest-loser-time.
- Adversarial review fixes: (1) an all-failing/all-cancelling racer set no longer
deadlock-aborts the scheduler — race bails loudly ('all futures settled without
a winner') when nothing is .ready and nothing is still .pending; (2) only
.pending losers are cancelled, so a loser that already .failed keeps its real
outcome label instead of being stomped to .canceled.
Re-point 1821 to context.io.async + context.io.race (winner a=111, losers
.canceled, completion log only 'task 1 @ 10ms', final clock 10ms — was 30 under
the old cooperative join). New 1826 locks the failing-loser case. Byte-identical
on aarch64-macOS + aarch64-linux. Suite 853/0; .ir churn is the current_park
vtable method.