Skip to content

feat(v1.4): Trust & safety — Remote Config balance, GDPR + abuse finish, map friends toggle#170

Open
code418 wants to merge 1 commit into
masterfrom
feat/v1.4-trust-safety
Open

feat(v1.4): Trust & safety — Remote Config balance, GDPR + abuse finish, map friends toggle#170
code418 wants to merge 1 commit into
masterfrom
feat/v1.4-trust-safety

Conversation

@code418

@code418 code418 commented Jul 9, 2026

Copy link
Copy Markdown
Owner

Implements v1.4 "Trust & safety" from docs/plans/ROADMAP.md. Two of the three workstreams (GDPR deletion, shadow-mode abuse) were already largely shipped; this PR builds the one real ship-gate gap (Remote Config game balance), finishes/audits the other two, and adds a requested scores-tab map toggle.

Ship gate

  • Remote Config drives claim_radius_meters + points_by_monarch end-to-end (client + Cloud Functions) with safety bounds enforced
  • Impossible-travel detector in shadow mode (logging, not blocking) — SHADOW_MODE stays true
  • Account-deletion flow tested end-to-end in staging (manual — see below)

Workstreams

A — Remote Config game balance (ship-gate pair)

  • New functions/src/_config.ts: reads the RC server template (getServerTemplate + evaluate), 5-min in-memory cache, never throws. Pure sanitiseClaimRadius / parsePointsOverride / resolvePointsForMonarch enforce bounds (radius [10,100], points [1,50]) and fall back to the hard-coded constants. Wired into startScoring + _recomputeScores.
  • RemoteConfigService gains claimRadiusMeters / pointsForCipher / pointsByMonarch (defaults = current constants) + force-refetch on login; functional radius + points-display consumers route through them.
  • Hard-coded constants stay canonical fallbacks — cross_language_sync_test now also pins RC defaults == constants and client bounds == server bounds.

B — GDPR deletion (audit + finish) — strips the new deviceIdHash on anonymisation; future-proof claims-photos/ prefix delete via testable storagePrefixesForUser; added unit tests for deleteUserDocs + storagePrefixesForUser. Kept self-contained (no Delete-User-Data extension — it can't anonymise claims).

C — Abuse device-id signal (stays shadow mode) — new repeatedDeviceSignal + pure evaluateClaimSignals; onClaimCreated counts distinct accounts per device hash over 24h (composite index added); client sends a dependency-free persisted-random deviceIdHash (DeviceIdService). Enforcement/voiding stays Phase 2.

D — Friends-only toggle on the scores-tab mapCountyHeatmap.friendsOnly (pure pickCountyLeaderEntry); the Map tab now honours the same Friends-only switch (off = global county leader). Client-only, no backend change.

Verification

  • Functions: build + lint clean, 463 TS tests passing.
  • Flutter: analyze clean, 439 Dart tests passing. All logic written test-first.

Manual steps before ship

  1. Remote Config console: publish claim_radius_meters + points_by_monarch to both the client and server templates (server template is separate) with values equal to the code defaults. Until then everything safely uses the constants.
  2. Staging account-deletion e2e: create a user with claims + a report photo + a friend, delete via Settings, assert no residuals.
  3. Confirm SHADOW_MODE === true in the deployed abuse detector.

Deliberate roadmap divergences (documented in ROADMAP.md)

  • GDPR is a self-contained onUserDeleted function, not the extension.
  • Abuse enforcement/voiding deferred to Phase 2 (ship gate requires shadow mode).
  • Device-id uses a persisted-random install id rather than firebase_app_installations, to avoid the firebase_core version ceiling.

🤖 Generated with Claude Code

…sh, map friends toggle

Implements v1.4 "Trust & safety" from docs/plans/ROADMAP.md.

Remote Config game balance (ship-gate pair):
- New functions/src/_config.ts reads the RC server template (getServerTemplate +
  evaluate) with a 5-min cache and never throws; pure sanitisers enforce safety
  bounds (radius [10,100], points [1,50]) and fall back to the hard-coded
  constants. Wired into startScoring + _recomputeScores.
- RemoteConfigService gains claimRadiusMeters / pointsForCipher / pointsByMonarch
  (defaults = current constants) + force-refetch on login; functional radius +
  points-display consumers route through them. cross_language_sync_test now pins
  the RC defaults == constants and client bounds == server bounds.

GDPR account deletion (audit + finish):
- Strip the new deviceIdHash on claim anonymisation; future-proof claims-photos/
  prefix delete via the new testable storagePrefixesForUser; unit tests for
  deleteUserDocs + storagePrefixesForUser. Kept self-contained (no extension).

Abuse detection (stays shadow mode):
- New repeatedDeviceSignal + pure evaluateClaimSignals combiner; onClaimCreated
  counts distinct accounts per device hash over 24h (composite index added).
  Client sends a dependency-free persisted-random deviceIdHash (DeviceIdService).
  SHADOW_MODE stays true (enforcement is Phase 2).

Scores-tab county map:
- CountyHeatmap.friendsOnly (pure pickCountyLeaderEntry): the Map tab now honours
  the same Friends-only switch — off shows the global county leader.

Gates: functions build + lint clean, 463 TS tests; flutter analyze clean, 439
Dart tests. Version 1.3.2+16 -> 1.4.0+17.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
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.

1 participant