Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// SPDX-License-Identifier: BUSL-1.1
// SPDX-FileCopyrightText: 2026 FinCore Engine Authors

package com.fincore.compliance.infrastructure.sanctions

import com.fincore.compliance.application.sanctions.SanctionsProvider
import com.fincore.compliance.application.sanctions.SanctionsScreeningRequest
import com.fincore.compliance.application.sanctions.SanctionsScreeningResult
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
import org.springframework.stereotype.Component

/**
* Deterministic in-tree sandbox sanctions provider, off by default. Encodes no real sanctions list; the outcome is a
* pure function of documented case-insensitive markers. An "insufficient" marker in the opaque subject reference yields
* InsufficientData; otherwise the provided attribute keys carrying the "match" marker are the matched dimensions, and a
* potential hit is reported when at least the requested number of them match. The score is a fixed sandbox confidence,
* not the m-of-n ratio.
*/
@Component
@ConditionalOnProperty(
prefix = "fincore.compliance.sanctions.sandbox",
name = ["enabled"],
havingValue = "true",
matchIfMissing = false,
)
class SandboxSanctionsProvider : SanctionsProvider {
override fun screen(request: SanctionsScreeningRequest): SanctionsScreeningResult {
if (request.subjectReference.contains(INSUFFICIENT_MARKER, ignoreCase = true)) {
return SanctionsScreeningResult.InsufficientData(listOf("sandbox.insufficient"))
}
val matched = request.attributes.filter { it.contains(MATCH_MARKER, ignoreCase = true) }
return if (matched.size >= request.requiredMatches) {
SanctionsScreeningResult.PotentialMatch(matched, MATCH_SCORE)
} else {
SanctionsScreeningResult.Clear
}
}

private companion object {
const val MATCH_MARKER = "match"
const val INSUFFICIENT_MARKER = "insufficient"
const val MATCH_SCORE = 0.75
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// SPDX-License-Identifier: BUSL-1.1
// SPDX-FileCopyrightText: 2026 FinCore Engine Authors

package com.fincore.compliance.infrastructure.sanctions

import com.fincore.compliance.application.sanctions.SanctionsScreeningRequest
import com.fincore.compliance.application.sanctions.SanctionsScreeningResult
import io.kotest.matchers.shouldBe
import io.kotest.matchers.types.shouldBeInstanceOf
import org.junit.jupiter.api.Test

class SandboxSanctionsProviderTest {
private val provider = SandboxSanctionsProvider()

@Test
fun `should clear when no attribute carries the match marker`() {
val request = SanctionsScreeningRequest("subject-1", setOf("attr-a", "attr-b"), 1)

provider.screen(request) shouldBe SanctionsScreeningResult.Clear
}

@Test
fun `should report a potential match when enough attributes match`() {
val request = SanctionsScreeningRequest("subject-1", setOf("match-a", "match-b"), 2)

val result = provider.screen(request).shouldBeInstanceOf<SanctionsScreeningResult.PotentialMatch>()
result.matchedAttributes.toSet() shouldBe setOf("match-a", "match-b")
}

@Test
fun `should clear when matched attributes are below the required count`() {
val request = SanctionsScreeningRequest("subject-1", setOf("match-a", "attr-b"), 2)

provider.screen(request) shouldBe SanctionsScreeningResult.Clear
}

@Test
fun `should return insufficient data when the subject reference is marked`() {
val request = SanctionsScreeningRequest("insufficient-subject", setOf("attr-a"), 1)

provider.screen(request).shouldBeInstanceOf<SanctionsScreeningResult.InsufficientData>()
}

@Test
fun `should be deterministic for the same request`() {
val request = SanctionsScreeningRequest("subject-1", setOf("match-a", "match-b"), 1)

provider.screen(request) shouldBe provider.screen(request)
}
}
Loading