Summary
Today the RoadFlare key handshake conflates two concerns:
- Privacy: who can decrypt my live location (Kind 30014).
- Function: who can effectively send me a ride offer.
These should be independent. The privacy gate is legitimate. The functional gate is an accidental consequence of the rider's UI showing a stale-key driver as offline. Independently, the lightweight mute (#80) does NOT actually filter offers — muted riders still appear in the driver's inbox. Combined effect: mute is too weak for what users expect, and stale-key state silently kills ride-on-demand.
This issue rolls two corrections into one refactor.
Background
Per ADR-0008, fare calculation no longer depends on driver location — fare_fiat_amount is computed by the rider from pickup→dropoff. So the driver's live location is purely a UX/proximity feature, not load-bearing for the ride flow. The strict key-required gate predates ADR-0008 and was sized for a stricter privacy model than the codebase actually maintains today.
After PR #81, two mute concepts coexist:
- Heavyweight ("Remove") — adds to
MutedRider, rotates RoadFlare key.
- Lightweight ("Mute") — sets
RoadflareFollower.mutedAt, suppresses key delivery.
Neither concept currently filters incoming Kind 3173 offers from the muted pubkey.
Design
Scope A: Mute filters at the receive side
Add a sender-pubkey check against both mute lists in:
Helper: RoadflareKeyManager.isAnyMuted(pubkey) returning true if pubkey is in state.muted OR any follower row has mutedAt != null for it. Replace ad-hoc combined checks across the codebase.
Scope B: Stale-key riders can still request rides
Today the rider detects stale-key via key_updated_at mismatch in Kind 30012 and treats the driver as offline. New behavior:
- New rider-side state:
LOCATION_STALE distinct from OFFLINE. UI shows the driver as available but with a "location pending" indicator.
- Allow the request flow from
LOCATION_STALE. Distance/ETA panel shows a placeholder until key recovers.
- When sending the request, atomically also send a Kind 3188 with
status="stale". Driver receives both — the offer goes into the inbox; the ack handler triggers a Kind 3186 re-delivery in the same beat. Rider has the new key by the time the ride starts.
- If driver is offline entirely, the offer sits unanswered like any other no-response offer (no special UX needed).
Edge case: rider has no key at all (never received one — fresh approval, key share lost). Same flow works — Kind 3188 with status="missing" instead of "stale".
Out of scope (separate issues)
- Android fiat fare wiring on receive side (drivestr) and roadflare-rider — see follow-up.
- Verifying Android fare calc never needs driver location — see follow-up.
- Renaming the heavyweight/lightweight mute APIs — naming refactor, deferred.
Acceptance criteria
Privacy + UX caveats
- Silent mute means the muted rider has no way to know they're muted. They keep retrying. This is the standard "soft block" UX (Twitter, Instagram). Documenting as a deliberate choice.
LOCATION_STALE showing as "available" gives slightly weaker offline-driver clarity. Mitigation: subtle UI hint ("location updating" sub-label). Definitely better than the current funnel-killing "offline."
- The Kind 3188 stale-signal-with-offer combo means a request can leak the rider's pubkey to a driver whose key is rotated specifically to exclude them. But: the rider was already going to send that signal as soon as their app comes back online and detects staleness — this just batches it with the offer. No new privacy leak.
Related
Summary
Today the RoadFlare key handshake conflates two concerns:
These should be independent. The privacy gate is legitimate. The functional gate is an accidental consequence of the rider's UI showing a stale-key driver as offline. Independently, the lightweight mute (#80) does NOT actually filter offers — muted riders still appear in the driver's inbox. Combined effect: mute is too weak for what users expect, and stale-key state silently kills ride-on-demand.
This issue rolls two corrections into one refactor.
Background
Per ADR-0008, fare calculation no longer depends on driver location —
fare_fiat_amountis computed by the rider from pickup→dropoff. So the driver's live location is purely a UX/proximity feature, not load-bearing for the ride flow. The strict key-required gate predates ADR-0008 and was sized for a stricter privacy model than the codebase actually maintains today.After PR #81, two mute concepts coexist:
MutedRider, rotates RoadFlare key.RoadflareFollower.mutedAt, suppresses key delivery.Neither concept currently filters incoming Kind 3173 offers from the muted pubkey.
Design
Scope A: Mute filters at the receive side
Add a sender-pubkey check against both mute lists in:
Helper:
RoadflareKeyManager.isAnyMuted(pubkey)returningtrueif pubkey is instate.mutedOR any follower row hasmutedAt != nullfor it. Replace ad-hoc combined checks across the codebase.Scope B: Stale-key riders can still request rides
Today the rider detects stale-key via
key_updated_atmismatch in Kind 30012 and treats the driver as offline. New behavior:LOCATION_STALEdistinct fromOFFLINE. UI shows the driver as available but with a "location pending" indicator.LOCATION_STALE. Distance/ETA panel shows a placeholder until key recovers.status="stale". Driver receives both — the offer goes into the inbox; the ack handler triggers a Kind 3186 re-delivery in the same beat. Rider has the new key by the time the ride starts.Edge case: rider has no key at all (never received one — fresh approval, key share lost). Same flow works — Kind 3188 with
status="missing"instead of"stale".Out of scope (separate issues)
Acceptance criteria
RoadflareWiringTestor new test pins this.isAnyMuted(pubkey)helper used by all four handlers (3173, 3187, 3188, 3189).LOCATION_STALEUI state shows driver as available but with location-pending indicator.requestRide()succeeds fromLOCATION_STALE; sends Kind 3188 stale-signal alongside the offer.key_updated_atis behind triggers Kind 3186 re-delivery.Privacy + UX caveats
LOCATION_STALEshowing as "available" gives slightly weaker offline-driver clarity. Mitigation: subtle UI hint ("location updating" sub-label). Definitely better than the current funnel-killing "offline."Related