Privacy-Period-Tracker/app/proguard-rules.pro

42 lines
2.0 KiB
Prolog
Raw Permalink Normal View History

feat: guard §45's logging rules, and stop the leak that needed no log call closes #38 checkNoHealthLogging fails the build on any logging call in a module that can see a cycle date. It runs in `./gradlew check`. WHY IT IS A GUARD AND NOT A GREP Both traps were already live in this repository. PeriodApplication passes android.util.Log.WARN to WorkManager as a CONSTANT, which is not a log call. ReminderWorker's KDoc says "a Log.d in a worker is the kind that survives", explaining why there isn't one — a naive grep fails the build on the clearest possible explanation, and the obvious fix is to delete the explanation. So it matches a call shape, and strips comments first. Proved both directions per GUARDS.md §1: an injected Log.d in CycleRepository produced exactly one failure; a comment containing Log.d( and println( stayed green. It also failed its own first run by walking domain/*/bin/, a gitignored IDE output holding stale copies of test files — a guard that fails on untracked build output is one somebody switches off. THE LEAK IT WAS NOT LOOKING FOR Prediction's init block interpolated dates into its require messages: require(!windowStart.isAfter(windowEnd)) { "window start $windowStart is..." } Five predicted dates across three messages, inside an IllegalArgumentException — the one string a crash reporter collects without anybody choosing to log it. §45 forbids exactly this and no logging statement was involved. The same applies to every data class, since toString() renders every field into any string that touches it. PeriodRecord, SpottingRecord, CycleRecord, Prediction and NotYetObservation now override it: ids and cycle lengths survive, dates do not. NoDatesInDiagnosticsTest pins seven cases and was itself proved to fail. R8 -assumenosideeffects strips android.util.Log from release, covering what a source guard cannot reach: a dependency logging on our behalf, and a module added without being listed in the guard. VERIFIED ON A RELEASE BUILD, NOT REASONED ABOUT assembleRelease signed with the debug keystore, installed, driven from onboarding to a forecast and then logging a period: zero ISO dates in logcat, zero health words, and the only mentions of the package are the system's own. A screenshot confirms it reached a real forecast, because "no logs" is trivially true of an app that did nothing. 201 tests pass. All three guards green. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18 22:01:33 -05:00
# Release builds are minified.
chore: adopt the project template and add the Kotlin/Compose skeleton Period was a bare directory holding one 2,527-line specification, with no git repository, no tracker and no documentation convention. This is the adoption from Projects/Template/START-HERE-New-Project.md, plus a project that compiles so the hooks and future guards have something real to run against. Documents. scaffold.sh created 19 paths, 0 skipped. The specification moved to docs/planning/PRODUCT_PLAN.md unchanged in substance, with a status header; the capitalised Docs/ is gone. Every scaffolded document was filled in for Period. docs/OPERATIONS.md deleted — an offline app is not a deployed service. DOC_TRUST_MAP.md written last, describing what is actually here, including what this project deliberately does not have. Code. Four Gradle modules. domain/cycle and domain/prediction are kotlin("jvm") and cannot see the Android SDK, so the engine is testable without an emulator — 17 tests pass, 12 of them the acceptance cases from PRODUCT_PLAN.md §51. BaselinePredictionEngine is a robust-median prototype and explicitly not the product; it exists so Batch 02's replacement can be shown to be better rather than merely different. Versions verified against their official sources today rather than inherited from the specification's own numbers, which that document asks for: Kotlin 2.4.10, AGP 9.3.1, Gradle 9.7.0, Compose BOM 2026.08.00, Room 2.8.4, Hilt 2.60.1. AGP 9 ships Kotlin built in, so org.jetbrains.kotlin.android is no longer applied. compileSdk is 37 because current AndroidX requires it; targetSdk stays 36, Play's floor from 2026-08-31, and the difference is deliberate. Six scripts taken into scripts/; the rest declined and named in docs/TOOLS.md. Three hooks in .githooks/, with pre-commit adapted to Gradle. closes #1 closes #2
2026-08-18 02:16:47 -05:00
#
feat: guard §45's logging rules, and stop the leak that needed no log call closes #38 checkNoHealthLogging fails the build on any logging call in a module that can see a cycle date. It runs in `./gradlew check`. WHY IT IS A GUARD AND NOT A GREP Both traps were already live in this repository. PeriodApplication passes android.util.Log.WARN to WorkManager as a CONSTANT, which is not a log call. ReminderWorker's KDoc says "a Log.d in a worker is the kind that survives", explaining why there isn't one — a naive grep fails the build on the clearest possible explanation, and the obvious fix is to delete the explanation. So it matches a call shape, and strips comments first. Proved both directions per GUARDS.md §1: an injected Log.d in CycleRepository produced exactly one failure; a comment containing Log.d( and println( stayed green. It also failed its own first run by walking domain/*/bin/, a gitignored IDE output holding stale copies of test files — a guard that fails on untracked build output is one somebody switches off. THE LEAK IT WAS NOT LOOKING FOR Prediction's init block interpolated dates into its require messages: require(!windowStart.isAfter(windowEnd)) { "window start $windowStart is..." } Five predicted dates across three messages, inside an IllegalArgumentException — the one string a crash reporter collects without anybody choosing to log it. §45 forbids exactly this and no logging statement was involved. The same applies to every data class, since toString() renders every field into any string that touches it. PeriodRecord, SpottingRecord, CycleRecord, Prediction and NotYetObservation now override it: ids and cycle lengths survive, dates do not. NoDatesInDiagnosticsTest pins seven cases and was itself proved to fail. R8 -assumenosideeffects strips android.util.Log from release, covering what a source guard cannot reach: a dependency logging on our behalf, and a module added without being listed in the guard. VERIFIED ON A RELEASE BUILD, NOT REASONED ABOUT assembleRelease signed with the debug keystore, installed, driven from onboarding to a forecast and then logging a period: zero ISO dates in logcat, zero health words, and the only mentions of the package are the system's own. A screenshot confirms it reached a real forecast, because "no logs" is trivially true of an app that did nothing. 201 tests pass. All three guards green. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18 22:01:33 -05:00
# When a rule is added here it must say what breaks without it a proguard file
chore: adopt the project template and add the Kotlin/Compose skeleton Period was a bare directory holding one 2,527-line specification, with no git repository, no tracker and no documentation convention. This is the adoption from Projects/Template/START-HERE-New-Project.md, plus a project that compiles so the hooks and future guards have something real to run against. Documents. scaffold.sh created 19 paths, 0 skipped. The specification moved to docs/planning/PRODUCT_PLAN.md unchanged in substance, with a status header; the capitalised Docs/ is gone. Every scaffolded document was filled in for Period. docs/OPERATIONS.md deleted — an offline app is not a deployed service. DOC_TRUST_MAP.md written last, describing what is actually here, including what this project deliberately does not have. Code. Four Gradle modules. domain/cycle and domain/prediction are kotlin("jvm") and cannot see the Android SDK, so the engine is testable without an emulator — 17 tests pass, 12 of them the acceptance cases from PRODUCT_PLAN.md §51. BaselinePredictionEngine is a robust-median prototype and explicitly not the product; it exists so Batch 02's replacement can be shown to be better rather than merely different. Versions verified against their official sources today rather than inherited from the specification's own numbers, which that document asks for: Kotlin 2.4.10, AGP 9.3.1, Gradle 9.7.0, Compose BOM 2026.08.00, Room 2.8.4, Hilt 2.60.1. AGP 9 ships Kotlin built in, so org.jetbrains.kotlin.android is no longer applied. compileSdk is 37 because current AndroidX requires it; targetSdk stays 36, Play's floor from 2026-08-31, and the difference is deliberate. Six scripts taken into scripts/; the rest declined and named in docs/TOOLS.md. Three hooks in .githooks/, with pre-commit adapted to Gradle. closes #1 closes #2
2026-08-18 02:16:47 -05:00
# of unexplained -keep lines is a file nobody can ever safely shrink again.
feat: guard §45's logging rules, and stop the leak that needed no log call closes #38 checkNoHealthLogging fails the build on any logging call in a module that can see a cycle date. It runs in `./gradlew check`. WHY IT IS A GUARD AND NOT A GREP Both traps were already live in this repository. PeriodApplication passes android.util.Log.WARN to WorkManager as a CONSTANT, which is not a log call. ReminderWorker's KDoc says "a Log.d in a worker is the kind that survives", explaining why there isn't one — a naive grep fails the build on the clearest possible explanation, and the obvious fix is to delete the explanation. So it matches a call shape, and strips comments first. Proved both directions per GUARDS.md §1: an injected Log.d in CycleRepository produced exactly one failure; a comment containing Log.d( and println( stayed green. It also failed its own first run by walking domain/*/bin/, a gitignored IDE output holding stale copies of test files — a guard that fails on untracked build output is one somebody switches off. THE LEAK IT WAS NOT LOOKING FOR Prediction's init block interpolated dates into its require messages: require(!windowStart.isAfter(windowEnd)) { "window start $windowStart is..." } Five predicted dates across three messages, inside an IllegalArgumentException — the one string a crash reporter collects without anybody choosing to log it. §45 forbids exactly this and no logging statement was involved. The same applies to every data class, since toString() renders every field into any string that touches it. PeriodRecord, SpottingRecord, CycleRecord, Prediction and NotYetObservation now override it: ids and cycle lengths survive, dates do not. NoDatesInDiagnosticsTest pins seven cases and was itself proved to fail. R8 -assumenosideeffects strips android.util.Log from release, covering what a source guard cannot reach: a dependency logging on our behalf, and a module added without being listed in the guard. VERIFIED ON A RELEASE BUILD, NOT REASONED ABOUT assembleRelease signed with the debug keystore, installed, driven from onboarding to a forecast and then logging a period: zero ISO dates in logcat, zero health words, and the only mentions of the package are the system's own. A screenshot confirms it reached a real forecast, because "no logs" is trivially true of an app that did nothing. 201 tests pass. All three guards green. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18 22:01:33 -05:00
# ---------------------------------------------------------------------------
# PRODUCT_PLAN.md §45: "Disable verbose logging in release builds."
# ---------------------------------------------------------------------------
#
# `checkNoHealthLogging` in the root build.gradle.kts already fails the build on
# any logging call in a module that can see a cycle date, and that is the real
# protection because it stops the line being written at all.
#
# This is the second layer, and it exists for the two cases a source guard
# cannot reach:
#
# - **A dependency logging on our behalf.** Room, WorkManager and the AndroidX
# libraries log, and a stack trace or a query they print can carry a value
# that came from a cycle record. The guard cannot see inside a library; R8
# can remove the call sites.
# - **A future module.** The guard reads a list of directories. Somebody adding
# a module and forgetting to list it gets no warning, because absence of a
# finding looks exactly like a clean result.
#
# `assumenosideeffects` lets R8 delete these calls entirely, arguments and all,
# rather than leaving a stripped string constant in the dex. It is safe here
# only because nothing in this app uses a Log return value `Log.d` returns an
# int nobody reads. If that ever stops being true, R8 will assume it is zero.
#
# Warning removed deliberately: `-dontwarn` is not used, because a warning here
# would mean the class shape changed and this rule stopped matching, which is
# exactly what we would want to hear about.
-assumenosideeffects class android.util.Log {
public static int v(...);
public static int d(...);
public static int i(...);
public static int w(...);
public static int e(...);
public static int wtf(...);
public static int println(...);
}