import com.correx.core.approvals.ApprovalOutcome import com.correx.core.approvals.ApprovalStatus import com.correx.core.approvals.GrantScope import com.correx.core.approvals.Tier import com.correx.core.approvals.domain.DefaultApprovalEngine import com.correx.core.approvals.model.ApprovalContext import com.correx.core.approvals.model.ApprovalGrant import com.correx.core.approvals.model.ApprovalScopeIdentity import com.correx.core.events.types.GrantId import com.correx.core.events.types.SessionId import com.correx.core.sessions.ApprovalMode import com.correx.testing.fixtures.request import kotlinx.datetime.Instant import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertNull import org.junit.jupiter.api.Test class GrantSemanticsTest { private val engine = DefaultApprovalEngine() @Test fun `grant permits exact tier - auto_approved`() { val req = request("r1", Tier.T2).copy(toolName = "shell") val grant = ApprovalGrant( id = GrantId("g1"), scope = GrantScope.SESSION(toolName = "shell"), permittedTiers = setOf(Tier.T2), reason = "test", timestamp = Instant.parse("2026-01-01T00:00:00Z") ) val ctx = ApprovalContext( ApprovalScopeIdentity(SessionId("s1"), null, null), ApprovalMode.DENY ) val now = Instant.parse("2026-01-01T00:00:01Z") val decision = engine.evaluate(req, ctx, listOf(grant), now) assertEquals(ApprovalOutcome.AUTO_APPROVED, decision.outcome) assertEquals(ApprovalStatus.COMPLETED, decision.state) } @Test fun `grant does not permit other tiers`() { val req = request("r1", Tier.T3) val grant = ApprovalGrant( id = GrantId("g1"), scope = GrantScope.SESSION(), permittedTiers = setOf(Tier.T2), reason = "test", timestamp = Instant.parse("2026-01-01T00:00:00Z") ) val ctx = ApprovalContext( ApprovalScopeIdentity(SessionId("s1"), null, null), ApprovalMode.DENY ) val now = Instant.parse("2026-01-01T00:00:01Z") val decision = engine.evaluate(req, ctx, listOf(grant), now) assertEquals(ApprovalStatus.PENDING, decision.state) assertNull(decision.outcome) } @Test fun `multiple grants - any match is enough`() { val req = request("r1", Tier.T4).copy(toolName = "write_file") val grants = listOf( ApprovalGrant( GrantId("g1"), GrantScope.SESSION(toolName = "shell"), setOf(Tier.T3), "", Instant.parse("2026-01-01T00:00:00Z") ), ApprovalGrant( GrantId("g2"), GrantScope.SESSION(toolName = "write_file"), setOf(Tier.T4), "", Instant.parse("2026-01-01T00:00:00Z") ) ) val ctx = ApprovalContext( ApprovalScopeIdentity(SessionId("s1"), null, null), ApprovalMode.DENY ) val now = Instant.parse("2026-01-01T00:00:01Z") val decision = engine.evaluate(req, ctx, grants, now) assertEquals(ApprovalOutcome.AUTO_APPROVED, decision.outcome) } }