Follow-up to #326. The pure-helper accumulation/cap rules (item 1) and the approval-screen double-submit guard (item 4) now have test coverage on branch `nip55-batch-accumulation-tests`. The two remaining items from #326 are deferred here because they are `Nip55Activity`-bound and need a test harness that does not exist in the repo.
What's left to cover
- `decisionLocked` re-entry guards (`Nip55Activity`: `handleApprove`, `handleReject`, `setupContent`, `calculateRiskAndSetupContent`, `handleIntent`): a second tap or a late-arriving intent after a decision is committed must be ignored — no second signing coroutine, no set mutation.
- displayed-snapshot binding (`handleApprove`): a request that races in after render must not be signed; handlers act on `displayed`, never live `pending`.
Why deferred
`Nip55Activity` pulls its handler/storage/permission-store/biometric from `application as KeepMobileApp` and drives real native FROST signing plus a biometric prompt — none of which runs cleanly headless. Covering items 2–3 needs one of:
- a full instrumented Activity harness (`createAndroidComposeRule()` + faked `KeepMobileApp` deps + biometric bypass), or
- a refactor extracting the guard logic out of the lifecycle methods into pure helpers (like `batchAccumulationDecision`) that plain JUnit can cover.
Either is a larger, security-sensitive change kept out of the focused test-only PR (#326 items 1 & 4). The consent-integrity invariant these guards protect is "the signed set == the displayed set the user approved."
The #352 Compose harness (`createComposeRule()`) is Composable-only and cannot reach the Activity, so it does not unblock this.
Follow-up to #326. The pure-helper accumulation/cap rules (item 1) and the approval-screen double-submit guard (item 4) now have test coverage on branch `nip55-batch-accumulation-tests`. The two remaining items from #326 are deferred here because they are `Nip55Activity`-bound and need a test harness that does not exist in the repo.
What's left to cover
Why deferred
`Nip55Activity` pulls its handler/storage/permission-store/biometric from `application as KeepMobileApp` and drives real native FROST signing plus a biometric prompt — none of which runs cleanly headless. Covering items 2–3 needs one of:
Either is a larger, security-sensitive change kept out of the focused test-only PR (#326 items 1 & 4). The consent-integrity invariant these guards protect is "the signed set == the displayed set the user approved."
The #352 Compose harness (`createComposeRule()`) is Composable-only and cannot reach the Activity, so it does not unblock this.