Skip to content

Label volunteer outcomes with the consumer's country - #475

Merged
myleshorton merged 3 commits into
mainfrom
feat/outcome-consumer-country
Oct 24, 2026
Merged

myleshorton merged 3 commits into
mainfrom
feat/outcome-consumer-country

Conversation

@myleshorton

@myleshorton myleshorton commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

freddie.producer.outcomes gets a consumer_country label: the country of the consumer behind each volunteer attempt. Its values are CN, IR, RU, US, other, unknown, or unreported.

Why

About 14% of volunteer attempts end nat_timeout with ICE connected: STUN got through, then DTLS/SCTP never finished. A censor that fingerprints the DTLS handshake would produce exactly that pattern, as Russia's TSPU did to Snowflake. Snowflake's Go side uses pion, as our consumer does. Freddie currently can't say which country these attempts come from, so it can't tell a censor from a slow network.

How

Freddie already derives each consumer's country server-side (Freddie.ConsumerCountry, from the CDN front's headers, used for genesis targeting). It knows the country for over 99.9% of consumer requests. This PR links that country to the volunteer's report:

  • freddie/freddie.go:
    • On each offer POST, Freddie caches the offer's request ID with the consumer's country.
    • It does this before publishing the offer, so a fast producer can't report the attempt first.
    • It drops the entry if the offer isn't delivered.
  • freddie/offercountry.go: the cache.
    • Entries live for 24 h, because a connected producer reports only when its session ends.
    • It's capped at 100k entries; at about 25 delivered offers a minute, a day is around 36k.
    • Entries are kept in insertion order, so expiry, eviction and lookup are all O(1). A flood of offers can only evict older entries, which then label as unknown.
  • clientcore/producer.go: the producer already receives that request ID as the offer's ReplyTo. It now echoes it back on its next genesis as prev-offer-id.
  • freddie/outcomes.go: Freddie looks the ID up once and labels the outcome.
  • common/outcome.go: the new field, and the mapping onto the bounded label.

Privacy: the producer only sends back an ID that Freddie created and already handed it. Freddie learns nothing it didn't have, and the volunteer learns nothing about the consumer. The country label is limited to four named countries plus other.

Rollout:

  • The widget auto-publishes on merge.
  • Freddie needs the usual lantern-cloud broflake bump.
  • Volunteers report unreported until their tabs reload.
  • The app's volunteers report unreported until a Lantern release includes this.

Side effect in lantern-cloud: lantern-cloud's ConsumerCountry hook increments freddie.consumer_country on each call. Offer POSTs now add to that count. That's a few hundred calls an hour against roughly 8M subscribes an hour, so negligible.

Testing

  • go test -race ./common/ ./clientcore/ ./freddie/: ok. New tests cover:
    • The cache: each offer is labeled once; entries expire at 24 h; a 3 h session keeps its country; a full cache evicts the oldest entry; expired entries are dropped when a new one is added.
    • End to end over HTTP: a delivered offer from an ru consumer gives the volunteer's outcome RU; a second report of the same offer gives unknown; an undelivered offer isn't cached.
    • The producer: the offer ID is reported, and doesn't carry over to the next attempt.
    • The label mapping.
  • GOOS=js GOARCH=wasm go build ./cmd: ok.
  • Not tested: the label in production. After the lantern-cloud bump deploys, check freddie.producer.outcomes by consumer_country.

Risk

Low. Freddie holds at most 100k small entries, and signaling behavior is unchanged. The riskiest file is freddie/freddie.go, which now calls the country hook on offer POSTs as well as subscribes.

How to revert

Revert the merge commit, and bump broflake in lantern-cloud again.

Pre-PR review (local Codex gate)

  • Rounds: 3 — final: CODEX GATE: verdict=approve critical=0 important=0 minor=0 preexisting=0 reviewed=9
  • Reviewed at: 3f9f6cb
  • Exit: approve
  • Step back: not needed (round 3 had no findings)
  • Fixed:
    • freddie/offercountry.go:12 — the 5-minute expiry left long connected sessions unlabeled, since they report only when the session ends. Now 24 h. (r1)
    • freddie/offercountry.go — a full cache scanned every entry on each add. Now insertion-ordered and O(1). (Flagged by the automated security review on r1's commit.)
    • freddie/freddie.go:841 — the country was cached after the offer was published, so a fast report could miss it. Now cached first. (r2)
  • Dismissed: none
  • Pre-existing (not introduced here, worth filing): none
  • Proposed AGENTS.md additions: none

🤖 Generated with Claude Code

https://claude.ai/code/session_019DnqgLJf7p4sK87dDvktjx

Summary by CodeRabbit

  • New Features
    • Producer outcome metrics now include a consumer-country label associated with the offer. Country values are grouped into supported categories, with separate labels for unreported and unknown countries.

myleshorton and others added 3 commits October 24, 2026 06:02
A producer now reports the ReplyTo of the offer its attempt answered, and
Freddie, which remembers the country it derived for each delivered offer,
labels freddie.producer.outcomes with consumer_country (CN, IR, RU, US,
other, unknown or unreported). That shows whether handshakes that pass ICE
and then stall are concentrated in one country, as a DTLS-fingerprinting
censor would make them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019DnqgLJf7p4sK87dDvktjx
A connected producer reports its outcome only when its session ends, so a
five-minute expiry left long successful sessions unlabeled. Remember offers
for a day instead, in insertion order, so expired and evicted entries come
off the front rather than a full cache scanning every entry on each offer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019DnqgLJf7p4sK87dDvktjx
Otherwise a producer that reports its attempt before the handler resumes
gets consumer_country=unknown.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019DnqgLJf7p4sK87dDvktjx
Copilot AI balanced review requested due to automatic review settings October 24, 2026 12:10
@coderabbitai

coderabbitai Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review.

Recent review info
Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 446b5bfb-9ef4-4471-8b02-506b14f9d5f5
Commits

Reviewing files that changed from the base of the PR and between d2f26b7 and 3f9f6cb.

Files selected for processing: 9
  • clientcore/outcome_test.go
  • clientcore/producer.go
  • common/outcome.go
  • common/outcome_test.go
  • freddie/freddie.go
  • freddie/offercountry.go
  • freddie/offercountry_test.go
  • freddie/outcomes.go
  • freddie/outcomes_test.go

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.

Walkthrough

Producer attempt reports now include the ID of the offer they answer. Freddie associates delivered offer IDs with consumer countries and uses that information to add a consumer_country label to producer outcome metrics.

Changes: Producer Outcome Country Attribution

Layer / File(s) Summary
Carry offer IDs into outcome reports
clientcore/producer.go, clientcore/outcome_test.go, common/outcome.go
Producer attempt state retains the answered offer ID and includes it in the genesis form. The common outcome contract defines the prev-offer-id field. A test checks that the ID appears in the report and is not retained for the next attempt.
Cache countries for delivered offers
freddie/freddie.go, freddie/offercountry.go, freddie/offercountry_test.go
Freddie caches a normalized country by offer ID before delivery and removes the entry if delivery fails. The cache uses a 24-hour TTL, limits capacity to 100,000 entries, and evicts expired or oldest entries. Tests cover cache behavior and offer delivery.
Label outcome metrics with country
common/outcome.go, common/outcome_test.go, freddie/outcomes.go, freddie/outcomes_test.go
Freddie consumes the cached country using the producer’s previous offer ID and adds a normalized consumer_country value to outcome metrics. Tests cover country normalization and missing-country labels.

Priority: Normal

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Feature

Sequence Diagram(s)

sequenceDiagram
  participant Consumer
  participant Freddie
  participant Producer
  participant OutcomeMetrics
  Consumer->>Freddie: POST offer with country
  Freddie->>Freddie: Cache country by offer ID
  Freddie->>Producer: Deliver offer with offer ID
  Producer->>Freddie: Report outcome with prev-offer-id
  Freddie->>Freddie: Consume cached country and normalize label
  Freddie->>OutcomeMetrics: Record outcome with consumer_country
Loading

Suggested labels: 572ccb, fcb2e5

Suggested reviewers: crosse, noahlevenson

Merge Risk: Minimal · up to 3f9f6

No confirmed issue currently blocks merging. Check country labels after deployment as planned.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 10.53% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 19 functions across 9 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly and concisely describes the main change: adding consumer-country labels to volunteer outcome metrics.
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Finishing Touches: 💡 1
📝 Generate docstrings: 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

No blocking issues were identified in the cache synchronization, offer correlation lifecycle, bounded labeling, or backward compatibility.

No open issues 🎉

What changed in this PR

Adds a bounded consumer_country label to Freddie’s volunteer outcome metrics, enabling country-level analysis of connection failures without exposing consumer country information to volunteers.

Changes:

  • Correlates offers with server-derived countries using a synchronized cache capped at 100,000 entries with a 24-hour expiry.
  • Echoes the previous offer ID in producer reports and maps countries to seven fixed label values.
  • Adds tests for cache behavior, offer delivery, report lifecycle, and label mapping.
File Description
freddie/​outcomes.go Adds the consumer country label to outcome metrics.
freddie/​outcomes_test.go Updates metric assertions for the new label.
freddie/​offercountry.go Implements the bounded, expiring offer-country cache.
freddie/​offercountry_test.go Tests expiry, eviction, one-time lookup, and delivery handling.
freddie/​freddie.go Initializes the cache and records countries before publishing offers.
common/​outcome.go Defines the report field and bounded country mapping.
common/​outcome_test.go Tests country label mapping.
clientcore/​producer.go Carries the offer ID into the next outcome report.
clientcore/​outcome_test.go Tests offer ID reporting and reset behavior.

Review effort: Balanced

@myleshorton
myleshorton merged commit 42b2ef0 into main Oct 24, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants