Files
amethyst/tools/r8-verify/reflection-contract.txt
Claude c3f60fd8af fix: address the audit of the shared UI settings and display locals
- UiSettings (enums) and UiSettingsFlow move on to headless commons, and
  UiSettingsState to commons.state. Their StringResource labels become
  `resourceId` extension properties in commonsUI (the TorType pattern).
  commons applies the serialization plugin, so UiSettings gets its
  kotlinx serializer back: in commonsUI it had none, and the
  encrypted-preferences fallback (JsonMapper.fromJson<UiSettings>) would
  silently drop a user's old settings. New UiSettingsSerializationTest
  round-trips it.
- tools/r8-verify/reflection-contract.txt names the eight persisted
  settings enums by their new package; the release check would fail.
- LocalDisplaySettings is a compositionLocalOf: it follows Wi-Fi/metered
  changes, and as a static local it recomposed everything under the theme.
  It drops the three fields nothing read.
- The remaining avatar sites (drawer, account switcher, top bar, group
  cards, ...) read LocalDisplaySettings instead of a one-off snapshot of
  accountViewModel.settings, so they follow setting changes like the feed.
- 13 loaders left with an unused accountViewModel parameter after the
  LocalCache switch drop it (206 call sites).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0168wY9t7i9NC5u3svyMxEz6
2026-09-24 15:51:48 +00:00

161 lines
11 KiB
Plaintext

# What must survive R8 with its ORIGINAL name, and why.
#
# R8 cannot see reflection. Every line here is a place where something outside
# the DEX — a .so, a manifest attribute, a JSON file on disk, WorkManager's
# database, a DataStore value written by an older install — looks a name up at
# runtime. Rename or remove it and the app compiles, ships, and fails only on a
# user's device.
#
# `verify_reflection_contract.py` checks each line against a release build's
# mapping.txt, so a keep rule that silently stops matching (a moved class, a
# renamed package, a deleted rule) fails the release instead of the user.
#
# Adding reflection? Add the keep rule in amethyst/proguard-rules.pro AND a line
# here. A rule with no line here is unverified; a line here with no rule fails.
#
# CAN THE REFLECTION ITSELF BE REMOVED?
# Reviewed entry by entry. A keep rule you do not need is better than one you
# verify, so the standing answer per mechanism:
#
# JNI class+method names IRREDUCIBLE. `Java_<class>_<method>` is
# compiled into libarti_android.so. Costs us
# nothing anyway — AGP's default
# `native <methods>` rule covers it.
# Rust -> Kotlin callback Removable only by inverting the flow (Kotlin
# polls a native queue instead of Rust pushing
# by GetMethodID). One interface, one method —
# not obviously worth the threading change.
# Cast OptionsProvider IRREDUCIBLE. Play Services reads the class
# name out of a manifest <meta-data> value and
# Class.forName()s it. Google's API, not ours.
# WorkManager workers ALREADY GONE. androidx.work ships the keep
# itself; our duplicate rule was deleted and
# these entries now verify the library's.
# Jackson NWC + CLINK DONE — both package keeps deleted. The types
# route at the hand-written kotlinx serializers
# on every target now, and the Jackson
# (de)serializers for them are gone.
# Class name as a value REMOVED. requireInProcessSigner() compared
# `signer::class.qualifiedName` against the FQN
# of NostrSignerExternal. R8 renames that class,
# so the branch never ran in a release build. It
# is a plain `is` check now — nothing to keep.
# Platform-class reflection IRREDUCIBLE AND FREE. quic's
# JdkCertificateValidator probes the JDK/Android
# trust manager for the 3-arg
# checkServerTrusted(chain, authType, host).
# That class ships in the platform, not in our
# DEX, so R8 never renames it — no rule needed.
# libscrypt / NetCipher / GONE. Their keep rules matched zero classes:
# LazySodium / JNA libsodium is a pure-Kotlin implementation now
# and Tor runs through arti. Rules deleted.
# Jackson on-disk stores REMOVABLE. Plain data classes; @Serializable
# + kotlinx gives compile-time literal names.
# App-private files, so no interop risk.
# Enum constants REMOVABLE WITH NO MIGRATION. Give each
# persisted enum an explicit `val code: String`
# and store that instead of `.name`. Set the
# codes equal to today's constant names and
# every existing DataStore value keeps working,
# while the literal is untouchable by R8. That
# deletes the one blanket rule left
# (`-keepclassmembers enum *`), which today
# blocks enum unboxing across all 707 enums.
#
# Format — one per line, `#` comments to end of line:
# [@play|@fdroid] optional leading scope: check this line only on
# that flavor. Play-only code is absent from the
# F-Droid APK, and "absent" reads as "R8 deleted
# it" — an unscoped line would fail that release.
# class <fqn> class must keep its exact name
# method <fqn> <name>... those methods must keep their names
# fields <fqn> class name AND every field name preserved
# enum <fqn> <CONST>... those constants keep their names (class may be renamed)
# --- JNI: the symbol name Java_<class>_<method> lives in libarti_android.so ---
class com.vitorpamplona.amethyst.ui.tor.ArtiNative
# Rust calls back by name: GetMethodID("onLogLine") in tools/arti-build/src/lib.rs
method com.vitorpamplona.amethyst.ui.tor.ArtiLogCallback onLogLine
# zxing-cpp's JNI half, vendored into amethyst/src/main/java/zxingcpp. The .so
# exports Java_zxingcpp_BarcodeReader_readYBuffer, so the class name and both
# native method names are the lookup. AGP's default
# `-keepclasseswithmembernames class * { native <methods>; }` should cover this on
# its own and the explicit `-keep class zxingcpp.**` is belt-and-braces; these
# lines are what proves at least one of them still matches. A rename fails
# silently — no build error, no warning, the QR scanner just never starts.
class zxingcpp.BarcodeReader
method zxingcpp.BarcodeReader readYBuffer readBitmap
# --- Named from outside the DEX ----------------------------------------------
# No keep rule of ours backs the three workers below: androidx.work's own
# consumer rules do. They stay listed so that if the library ever drops them,
# the release fails here instead of on a user's phone.
# <meta-data android:value> in the play manifest; the Cast framework
# Class.forName()s it. AGP generates keeps for component android:name, not for
# meta-data values, so nothing but an explicit rule protects this one.
@play class com.vitorpamplona.amethyst.service.cast.chromecast.AmethystCastOptionsProvider
# WorkManager stores the class name in its own DB and instantiates it by name on
# a later process start — including after an update that reshuffled the mapping.
class com.vitorpamplona.amethyst.service.scheduledposts.ScheduledPostWorker
class com.vitorpamplona.amethyst.service.notifications.NotificationCatchUpWorker
class com.vitorpamplona.amethyst.service.calendar.CalendarReminderWorker
# The AppFunctions bridge is kept whole by a package rule, so this asserts the
# rule still matches something. androidx.appfunctions reflects over the generated
# inventories and @AppFunctionSerializable types; play-only source set.
@play class com.vitorpamplona.amethyst.appfunctions.AmethystAppFunctions
# --- Jackson reflective data binding: field names ARE the wire format ---------
# NIP-47 and CLINK used to be listed here. They are not data-bound reflectively
# any more — OptimizedJsonMapper routes them at kotlinx serializers that write
# every field name as a string literal — so there is no rule to verify.
# --- Enums used as navigation-route arguments: the CLASS name is the lookup ---
# androidx.navigation resolves an enum route argument by fully-qualified class
# name (NavTypeConverter calls Class.forName on the serial name). Renaming the
# class throws while the nav graph is being built, so the app cannot get past
# login at all -- release-only, and total.
#
# These need the CLASS pinned, which the `-keepclassmembers enum *` rule below
# deliberately does not do: it keeps constant names and lets the class be
# renamed. Add a line here whenever an enum becomes a route argument.
class com.vitorpamplona.amethyst.ui.navigation.routes.DiscoverTab
class com.vitorpamplona.amethyst.ui.screen.loggedIn.bookmarkgroups.BookmarkType
class com.vitorpamplona.amethyst.ui.navigation.routes.GeocacheTab
# --- Enum constants persisted as strings -------------------------------------
# The preference stores write `enum.name` into DataStore and read it back with
# valueOf(). A renamed constant resets that setting for every existing user on
# the first launch after the update — silently, and only in a release build.
# The enum CLASS is still renamed; only the constant names are pinned.
enum com.vitorpamplona.amethyst.commons.tor.TorType INTERNAL
# Not a preference: these constant names ARE the NIP-47 wire strings. The
# response parser does NwcErrorCode.valueOf(json["code"]) on a string another
# wallet wrote, so a rename turns every typed error into a null code and the
# UI loses the reason a payment failed. Falls back quietly (try/catch), which
# is exactly why it would never be noticed.
enum com.vitorpamplona.quartz.nip47WalletConnect.rpc.NwcErrorCode RATE_LIMITED NOT_IMPLEMENTED INSUFFICIENT_BALANCE PAYMENT_FAILED QUOTA_EXCEEDED RESTRICTED UNAUTHORIZED INTERNAL UNSUPPORTED_ENCRYPTION BAD_REQUEST NOT_FOUND EXPIRED UNSUPPORTED_PAYMENT_INSTRUCTION UNSUPPORTED_NETWORK OTHER
enum com.vitorpamplona.amethyst.commons.model.ThemeType SYSTEM LIGHT DARK
enum com.vitorpamplona.amethyst.commons.model.BooleanType ALWAYS NEVER
enum com.vitorpamplona.amethyst.commons.model.ConnectivityType ALWAYS NEVER
enum com.vitorpamplona.amethyst.commons.model.FeatureSetType SIMPLIFIED
enum com.vitorpamplona.amethyst.commons.model.FontFamilyType SYSTEM
enum com.vitorpamplona.amethyst.commons.model.FontSizeType NORMAL
enum com.vitorpamplona.amethyst.commons.model.AccentColorType PURPLE
enum com.vitorpamplona.amethyst.commons.model.ProfileGalleryType CLASSIC
enum com.vitorpamplona.quartz.nip05DnsIdentifiers.namecoin.NamecoinBackend
enum com.vitorpamplona.quartz.nipACWebRtcCalls.tags.CallType
enum com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.concord.ChannelExpand
# Still listed after ScheduledPost moved to kotlinx: this one is also the on-disk
# format of the scheduled-post file, and a plain @Serializable enum is not obviously
# immune — kotlinx builds its descriptor from the entries, and it has not been proven
# here that those names are literals rather than Enum.name read at runtime. Keeping
# the constants costs nothing (the blanket enum rule already provides them) and the
# failure mode — every queued post's status unreadable — is not worth guessing at.
enum com.vitorpamplona.amethyst.commons.scheduledposts.ScheduledPostStatus PENDING PUBLISHING SENT FAILED CANCELLED