Combines builds 85→86 (six commits since v1.0-build85). Two themes:
faster recovery from edge-case iOS path events, and smarter cred pool
ramp-up that spreads expirations even better than build 85 did.
Path-change resilience
• iface=other PathMonitor events now extend pause-acquire 5s without
triggering smart-pause re-marking. iOS fires brief "satisfied
iface=other" events (~3s) during physical interface transitions,
where its os-default points at our own TUN device — recursive
routing fallback. Previous code ran smart-pause logic on these
events, which was no-op for marking but the 500ms pause expired
before the new real interface arrived, allowing conns to acquire
fresh slots that would then fail / 486 cascade. Observed in
vpn.over24h.log 2026-05-13 15:26 outage (10-minute pool dead).
• activeAllocationsWindow bumped 10m → 12m. VK TURN allocation
lifetime is 600s from last refresh. Pion refreshes every ~5min,
so worst-case allocation expiry post-conn-death is conn-death + 10m.
Previous threshold of 10m exactly matched VK's lifetime with zero
safety margin → slots whose lastUsedAt was just past 10m got
classified as "ghost expired" by smart-pause and skipped, while VK
could still hold active allocations, leading to 486 cascade. Same
15:26 outage observation — slots 3/4/5 were smart-pause-skipped at
event 1 then 486-cascaded after the path settled.
Grower cold-start state machine
• Cold-start target now scales with NumConns instead of pool ratio.
NumConns=30 → 3 fast-fill slots (was 6 at 50% of pool=12). Saves
user a few seconds at startup AND doubles the staggered portion of
the pool, smoothing expirations 8h later.
• Stagger window 60-240s → 120-300s for wider expiry spread (~3.5min
average gap between maintenance fills, was ~2.5min).
• Two-mode state machine with abort guard inside tryFill at TWO
checkpoints (before fetch + after fetch / before commit). The
post-fetch checkpoint catches the race where conn-driven fetches
in get() complete during the grower's 5-10s PoW window — without
it, grower systematically over-shot cold-start by 1-2 slots
(observed wifi.4.log: 0 → 5 instead of 0 → 3 for NumConns=30).
DNS empirical clarifications
• The "DNS doesn't work in extension" docstring was inherited from a
historical claim that nobody had actually tested. Probed in
pkg/proxy/dns_probe.go (since reverted) at both pre- and
post-bootstrap: cgo getaddrinfo via mDNSResponder XPC works fine
in BOTH windows. Pure-Go resolver fails universally (no usable
/etc/resolv.conf). vk_host_ips pre-resolution is now correctly
framed as latency optimization + defense-in-depth, NOT a hard
requirement. No code change to vk_host_ips path itself; just
docstring updates.
UI / diagnostic
• Connect / Disconnect button presses now log via NSLog +
SharedLogger. Helps distinguish iOS-side stops (NEProviderStopReason
.userInitiated fires for non-user reasons sometimes) from actual
user-initiated disconnects.