refactor: delete the unused AuthDecisionResolver

AuthDecisionResolver has never had a production caller. Searching every commit
that touched amethyst/src/main for the symbol outside the object's own file
returns nothing, and `git log -S` against AuthCoordinator.kt is likewise empty:
it arrived unused and stayed that way, kept alive only by its own test.

The live equivalent is the per-account block in AuthCoordinator, which covers
every branch it modelled — ALLOW/DENY/ASK, and the full UserAuthChoice mapping
including the setDecision writes behind "always allow" and "never allow".

Two things made it worse than merely dead. It folded every logged-in account
into a single verdict, whereas the coordinator decides per account because one
socket is shared and an answer given for @a must not reveal @b. And its
"no verdicts -> authenticate" branch encoded the old any-account-allows fold
that was deliberately removed to fix the over-AUTH bug; there is no random-key
fallback any more. Left in place it reads as a template for policy the codebase
has since rejected.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rado2dnqpbCuCUyCd3trQz
This commit is contained in:
Claude
2026-08-17 16:24:24 +00:00
parent 91a3cd9fff
commit 4a1204ae57
2 changed files with 0 additions and 185 deletions
@@ -1,68 +0,0 @@
/*
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.service.relayClient.authCommand.model
import com.vitorpamplona.amethyst.commons.relayauth.RelayAuthDecision
import com.vitorpamplona.amethyst.commons.relayauth.RelayAuthVerdict
/**
* Combines every logged-in account's [RelayAuthVerdict] into a single decision for whether to
* authenticate with a relay — and, when the user is asked, what to remember. Pulled out of
* [AuthCoordinator] so the policy is unit-testable without the relay client, signers, or Compose.
*/
object AuthDecisionResolver {
/**
* @param shouldAuth whether to sign the NIP-42 auth challenge.
* @param remember a per-relay override to persist ([RelayAuthDecision.ALLOW]/[RelayAuthDecision.DENY]),
* or null to leave the relay's stored decision untouched.
*/
data class Outcome(
val shouldAuth: Boolean,
val remember: RelayAuthDecision? = null,
)
/**
* Resolves the combined verdict:
* - **No verdicts** (no ledgers watching) => auto-authenticate; the caller has no policy to apply.
* - **Any [RelayAuthVerdict.ALLOW]** => authenticate without asking.
* - **Any [RelayAuthVerdict.ASK]** (and none allow) => call [prompt] and act on the user's choice,
* remembering ALLOW/DENY for Always-allow / Block.
* - **Otherwise** (all DENY) => do not authenticate.
*
* [prompt] is only invoked in the ASK case, so the no-op paths never build a dialog.
*/
suspend fun resolve(
verdicts: List<RelayAuthVerdict>,
prompt: suspend () -> UserAuthChoice,
): Outcome =
when {
verdicts.isEmpty() -> Outcome(shouldAuth = true)
verdicts.any { it == RelayAuthVerdict.ALLOW } -> Outcome(shouldAuth = true)
verdicts.any { it == RelayAuthVerdict.ASK } ->
when (prompt()) {
UserAuthChoice.ALLOW_ONCE -> Outcome(shouldAuth = true)
UserAuthChoice.ALWAYS_ALLOW -> Outcome(shouldAuth = true, remember = RelayAuthDecision.ALLOW)
UserAuthChoice.BLOCK -> Outcome(shouldAuth = false, remember = RelayAuthDecision.DENY)
UserAuthChoice.DISMISS -> Outcome(shouldAuth = false)
}
else -> Outcome(shouldAuth = false)
}
}
@@ -1,117 +0,0 @@
/*
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.service.relayClient.authCommand.model
import com.vitorpamplona.amethyst.commons.relayauth.RelayAuthDecision
import com.vitorpamplona.amethyst.commons.relayauth.RelayAuthVerdict
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Test
class AuthDecisionResolverTest {
/** A prompt that must never be called; fails the test if the resolver asks the user. */
private val neverPrompt: suspend () -> UserAuthChoice = { error("prompt() should not be called") }
@Test
fun noVerdictsAutoAuthenticatesWithoutPrompting() =
runTest {
val outcome = AuthDecisionResolver.resolve(emptyList(), neverPrompt)
assertTrue(outcome.shouldAuth)
assertNull(outcome.remember)
}
@Test
fun anyAllowAuthenticatesWithoutPrompting() =
runTest {
val outcome =
AuthDecisionResolver.resolve(
listOf(RelayAuthVerdict.DENY, RelayAuthVerdict.ALLOW, RelayAuthVerdict.ASK),
neverPrompt,
)
assertTrue(outcome.shouldAuth)
assertNull(outcome.remember)
}
@Test
fun allDenyDoesNotAuthenticateAndDoesNotPrompt() =
runTest {
val outcome =
AuthDecisionResolver.resolve(
listOf(RelayAuthVerdict.DENY, RelayAuthVerdict.DENY),
neverPrompt,
)
assertFalse(outcome.shouldAuth)
assertNull(outcome.remember)
}
@Test
fun askAllowOnceAuthenticatesButRemembersNothing() =
runTest {
val outcome =
AuthDecisionResolver.resolve(listOf(RelayAuthVerdict.ASK)) { UserAuthChoice.ALLOW_ONCE }
assertTrue(outcome.shouldAuth)
assertNull(outcome.remember)
}
@Test
fun askAlwaysAllowAuthenticatesAndRemembersAllow() =
runTest {
val outcome =
AuthDecisionResolver.resolve(listOf(RelayAuthVerdict.ASK)) { UserAuthChoice.ALWAYS_ALLOW }
assertTrue(outcome.shouldAuth)
assertEquals(RelayAuthDecision.ALLOW, outcome.remember)
}
@Test
fun askBlockDoesNotAuthenticateAndRemembersDeny() =
runTest {
val outcome =
AuthDecisionResolver.resolve(listOf(RelayAuthVerdict.ASK)) { UserAuthChoice.BLOCK }
assertFalse(outcome.shouldAuth)
assertEquals(RelayAuthDecision.DENY, outcome.remember)
}
@Test
fun askDismissDoesNotAuthenticateAndRemembersNothing() =
runTest {
val outcome =
AuthDecisionResolver.resolve(listOf(RelayAuthVerdict.ASK)) { UserAuthChoice.DISMISS }
assertFalse(outcome.shouldAuth)
assertNull(outcome.remember)
}
@Test
fun askIsOnlyReachedWhenNoAccountAllows() =
runTest {
// ALLOW present alongside ASK must short-circuit to auth without prompting.
var prompted = false
val outcome =
AuthDecisionResolver.resolve(listOf(RelayAuthVerdict.ASK, RelayAuthVerdict.ALLOW)) {
prompted = true
UserAuthChoice.BLOCK
}
assertTrue(outcome.shouldAuth)
assertFalse(prompted)
}
}