diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index e58ee35c577df17c0ef1399553b0a2c4504ebb35..4cc239f93dca70175de051edbb1e6c5364011575 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -2,27 +2,28 @@
 compose-multiplatform = "1.7.1"
 agp = "8.2.2"
 java = "17"
-kotlin = "2.0.21"
-ksp = "2.0.21-1.0.28"
+kotlin = "2.1.0"
+ksp = "2.1.0-1.0.29"
 
-ktor = "3.0.2"
-kotlinx-serialization = "1.7.3"
+kotlinx-serialization = "1.8.0-RC"
+ktor = "3.0.3"
 cache4k = "0.13.0"
 statelyConcurrentCollections = "2.1.0"
 slf4j = "2.0.16"
 kotlinx-io = "0.6.0"
 
 ktorfit = "2.2.0"
-coroutines = "1.9.0"
+coroutines = "1.10.1"
 datetime = "0.6.1"
 sandwich = "2.0.10"
 arrow = "1.2.4"
 # Main Koin version - for core, android deps
-koin = "4.1.0-Beta1"
-koinTest = "4.1.0-Beta1"
+koin = "4.0.1"
+koinTest = "4.0.1"
 gradleBuildConfigPlugin = "5.5.1"
+akkurate = "0.11.0"
 # Koin version for Compose multiplatform
-koinComposeMultiplatform = "4.1.0-Beta1"
+koinComposeMultiplatform = "4.0.1"
 mosaic = "0.13.0"
 molecule = "2.0.0"
 adaptive = "1.0.1"
@@ -116,6 +117,7 @@ arrow-core = { module = "io.arrow-kt:arrow-core", version.ref = "arrow" }
 arrow-fxCoroutines = { module = "io.arrow-kt:arrow-fx-coroutines", version.ref = "arrow" }
 arrow-optics = { module = "io.arrow-kt:arrow-optics", version.ref = "arrow" }
 arrow-opticsKspPlugin = { module = "io.arrow-kt:arrow-optics-ksp-plugin", version.ref = "arrow" }
+akkurate = { module = "dev.nesk.akkurate:akkurate-core", version.ref = "akkurate" }
 
 mosaic = { module = "com.jakewharton.mosaic:mosaic-runtime", version.ref = "mosaic" }
 koin-compose = { module = "io.insert-koin:koin-compose", version.ref = "koinComposeMultiplatform" }
diff --git a/temerity/build.gradle.kts b/temerity/build.gradle.kts
index 47ae2a134891e484b0d4396aad45149db501a0dc..62d36fb7861d2aed3a52e41b86aa2b1853b9b0ef 100644
--- a/temerity/build.gradle.kts
+++ b/temerity/build.gradle.kts
@@ -85,7 +85,7 @@ kotlin {
                 implementation(libs.koin.core)
                 implementation(libs.ktorfit.lib)
                 implementation(libs.kotlinx.coroutines.core)
-                implementation(libs.kermit)
+                api(libs.kermit)
                 // TODO: Re-add cache4k when updated for Kotlin 2.0.21
                 implementation(libs.statelyConcurrentCollections)
                 implementation(libs.kmpIo)
diff --git a/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/TemClientConfig.kt b/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/TemClientConfig.kt
index d3caf43b6b3599adffeae0e23684371df94ff89e..491f006cd23c7ba5e4e3b89a6dc90309ea65ef9b 100644
--- a/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/TemClientConfig.kt
+++ b/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/TemClientConfig.kt
@@ -33,11 +33,11 @@ import kotlin.time.Duration
  * @property serviceToken Specifies the authorization token to use for API calls
  * @property optDebugEnabled Specifies whether debug logging should be enabled
  *
- * @property expectSuccess Specifies whether to expect successful responses by default
- * @property useWebTimeout Specifies whether to use a timeout  other than default for web requests
- * @property webTimeout Specifies the timeout [Duration] to use when making web requests
- * @property cacheTimeout Specifies the timeout [Duration] to use when storing cache entries. Affects keep-alive time for cached instance data like user role type fields.
- * @property threadCount Specifies the number of threads to use for each client instance. Defaults to 2 at a minimum
+ * @property optExpectSuccess Specifies whether to expect successful responses by default
+ * @property optUseWebTimeout Specifies whether to use a timeout  other than default for web requests
+ * @property optWebTimeoutDuration Specifies the timeout [Duration] to use when making web requests
+ * @property optCacheTimeoutDuration Specifies the timeout [Duration] to use when storing cache entries. Affects keep-alive time for cached instance data like user role type fields.
+ * @property optThreadCount Specifies the number of threads to use for each client instance. Defaults to 2 at a minimum
  */
 @TemDsl
 public class TemClientConfig {
@@ -46,37 +46,37 @@ public class TemClientConfig {
   public var serviceToken: String? = null
   public var optLoggingEnabled: Boolean = true
   public var optDebugEnabled: Boolean = false
-  public var supportKtxNotebook: Boolean = false
+  public var optSupportKtxNotebook: Boolean = false
 
-  public var expectSuccess: Boolean = true
-  public var useWebTimeout: Boolean = false
+  public var optExpectSuccess: Boolean = true
+  public var optUseWebTimeout: Boolean = false
 
   @Suppress("MemberVisibilityCanBePrivate")
-  public var webTimeout: Duration? = null
+  public var optWebTimeoutDuration: Duration? = null
 
   @Suppress("MemberVisibilityCanBePrivate")
   // Default cache timeout is set to 15 minutes if this is left unspecified; @see [edu.ucsc.its.temerity.core.Temerity] init block
-  public var cacheTimeout: Duration? = null
+  public var optCacheTimeoutDuration: Duration? = null
 
-  public var threadCount: Int? = null
+  public var optThreadCount: Int? = null
 
   /**
    * Configures the HttpClient with the provided block.
    * @param () -> HttpClient The block to configure the HttpClient.
    */
-  internal var httpClientConfigBlock: (HttpClientConfig<*>.() -> Unit)? = null
-  internal var httpClientBuilder: (() -> HttpClient)? = null
-  internal var httpClientLoggingBlock: (LoggingConfig.() -> Unit)? = null
+  internal var optHttpClientConfigBlock: (HttpClientConfig<*>.() -> Unit)? = null
+  internal var optHttpClientBuilder: (() -> HttpClient)? = null
+  internal var optHttpClientLoggingBlock: (LoggingConfig.() -> Unit)? = null
 
   public fun logging(block: LoggingConfig.() -> Unit) {
-    httpClientLoggingBlock = block
+    optHttpClientLoggingBlock = block
   }
 
   /**
    * Set custom HttpClient configuration for the default HttpClient.
    */
   public fun httpClient(block: HttpClientConfig<*>.() -> Unit) {
-    httpClientConfigBlock = block
+    optHttpClientConfigBlock = block
   }
 
   /**
@@ -87,13 +87,12 @@ public class TemClientConfig {
     engineFactory: HttpClientEngineFactory<T>,
     block: HttpClientConfig<T>.() -> Unit = {},
   ) {
-    httpClientBuilder = {
+    optHttpClientBuilder = {
       HttpClient(engineFactory, block)
     }
   }
 
   public companion object {
-
     internal fun withToken(serviceToken: String): TemClientConfig = TemClientConfig().apply {
       this.serviceToken = serviceToken
     }
diff --git a/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/core/Temerity.kt b/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/core/Temerity.kt
index 44a02ddbe90957cb7ec6027bdab48c11b81c56bd..ab73b7c140a8d20f60ad72aac32e60db4ae0759e 100644
--- a/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/core/Temerity.kt
+++ b/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/core/Temerity.kt
@@ -74,7 +74,6 @@ import kotlinx.datetime.LocalDate
 import kotlinx.datetime.LocalTime
 import kotlinx.datetime.minus
 import kotlinx.serialization.SerializationException
-import kotlinx.serialization.encodeToString
 import kotlinx.serialization.json.Json
 import org.koin.core.Koin
 import org.koin.core.KoinApplication
@@ -185,6 +184,7 @@ public class Temerity internal constructor(
   internal val koinContext = createKoinApp(config)
   override fun getKoin(): Koin = koinContext.koin
 
+  private var libraryLogger: KermitLogger
   private var platformApi: PlatformApi
   private var cachedUserRoleList: ConcurrentMutableMap<Int, String>
   private var libraryCoroutineDispatcher: CoroutineDispatcher
@@ -203,7 +203,7 @@ public class Temerity internal constructor(
     }
 
     libraryCoroutineDispatcher = get<CoroutineDispatcher>(named("libraryCoroutineDispatcher")) {
-      parametersOf(availableThreads(config.threadCount), "Temerity Library Dispatcher")
+      parametersOf(availableThreads(config.optThreadCount), "Temerity Library Dispatcher")
     }
     libraryCoroutineScope = get<CoroutineScope>(named("libraryCoroutineScope")) {
       parametersOf(libraryCoroutineDispatcher)
@@ -213,6 +213,10 @@ public class Temerity internal constructor(
     webRequestDispatcher = get<CoroutineDispatcher>(named("childDispatcher")) { parametersOf(libraryCoroutineDispatcher, 2, "TemerityWebRequestCoroutineDispatcher") }
     fileProcessDispatcher = get<CoroutineDispatcher>(named("childDispatcher")) { parametersOf(libraryCoroutineDispatcher, 1, "TemerityFileProcessCoroutineDispatcher") }
 
+    libraryLogger = get<KermitLogger>(named("libraryLogger")) {
+      parametersOf(BuildConfig.PACKAGE_NAME, config)
+    }
+
     platformApi = get<PlatformApi>(named("ktorfitApi")) {
       parametersOf(config, webRequestDispatcher)
     }
@@ -620,7 +624,7 @@ internal fun buildHttpClient(
     }
 
     if (config.optDebugEnabled) {
-      when (config.httpClientLoggingBlock) {
+      when (config.optHttpClientLoggingBlock) {
         null -> {
           install(Logging) {
             this.logger = object : KtorLogger {
@@ -635,18 +639,18 @@ internal fun buildHttpClient(
           }
         }
         else -> {
-          config.httpClientLoggingBlock?.let {
+          config.optHttpClientLoggingBlock?.let {
             Logging(it)
           }
         }
       }
     }
 
-    expectSuccess = config.expectSuccess
+    expectSuccess = config.optExpectSuccess
 
-    if (config.useWebTimeout) {
+    if (config.optUseWebTimeout) {
       val defaultWebTimeout = DEFAULT_WEB_TIMEOUT.toLong(MILLISECONDS)
-      val configuredWebTimeout = config.webTimeout?.toLong(MILLISECONDS)
+      val configuredWebTimeout = config.optWebTimeoutDuration?.toLong(MILLISECONDS)
       install(HttpTimeout) {
         connectTimeoutMillis = configuredWebTimeout ?: defaultWebTimeout
         requestTimeoutMillis = configuredWebTimeout ?: defaultWebTimeout
@@ -654,9 +658,9 @@ internal fun buildHttpClient(
       }
     }
 
-    config.httpClientConfigBlock?.invoke(this)
+    config.optHttpClientConfigBlock?.invoke(this)
   }
-  return config.httpClientBuilder?.invoke()?.config(defaultConfig) ?: HttpClient(httpClientEngine, defaultConfig)
+  return config.optHttpClientBuilder?.invoke()?.config(defaultConfig) ?: HttpClient(httpClientEngine, defaultConfig)
 }
 
 /**
diff --git a/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/di/LibModule.kt b/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/di/LibModule.kt
index 76891c922dce04a93a95ce3ef1b2a17e629a85f4..d6fbe81f556afa013b7acc10362d538977c9b6a3 100644
--- a/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/di/LibModule.kt
+++ b/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/di/LibModule.kt
@@ -22,8 +22,8 @@ import de.jensklingenberg.ktorfit.ktorfit
 import edu.ucsc.its.temerity.TemClientConfig
 import edu.ucsc.its.temerity.api.PlatformApi
 import edu.ucsc.its.temerity.api.createPlatformApi
+import edu.ucsc.its.temerity.core.Temerity.Companion.createLogger
 import edu.ucsc.its.temerity.core.buildHttpClient
-import edu.ucsc.its.temerity.core.createCommonLogger
 import edu.ucsc.its.temerity.extensions.coroutines.createLibraryScope
 import io.ktor.client.HttpClient
 import io.ktor.client.engine.HttpClientEngine
@@ -41,8 +41,8 @@ internal object LibModule {
    * It includes the platform module and provides factories for [HttpClient] and [PlatformApi].
    */
   internal fun libModule() = module {
-    single<KermitLogger>(named("libraryLogger")) { (tag: String?, config: TemClientConfig?) ->
-      createCommonLogger(tag = tag, config = config)
+    single<KermitLogger>(named("libraryLogger")) { (tag: String, config: TemClientConfig) ->
+      createLogger(tag, config)
     }
     single<CoroutineDispatcher>(named("libraryCoroutineDispatcher")) { (threadCount: Int, dispatcherName: String) ->
       Dispatchers.IO.limitedParallelism(threadCount, dispatcherName)
diff --git a/temerity/src/jvmTest/kotlin/edu/ucsc/its/temerity/test/DevUtilityTests.kt b/temerity/src/jvmTest/kotlin/edu/ucsc/its/temerity/test/DevUtilityTests.kt
index be3c3b3850f6f14c1ea5ed6342c44ee1b90cb981..19a62a3a5572b022f7f77feddacdf38692e2b5a7 100644
--- a/temerity/src/jvmTest/kotlin/edu/ucsc/its/temerity/test/DevUtilityTests.kt
+++ b/temerity/src/jvmTest/kotlin/edu/ucsc/its/temerity/test/DevUtilityTests.kt
@@ -20,7 +20,9 @@ package edu.ucsc.its.temerity.test
 import edu.ucsc.its.temerity.core.Temerity
 import edu.ucsc.its.temerity.core.Temerity.Companion.createLogger
 import io.kotest.core.spec.style.FunSpec
+import io.kotest.engine.runBlocking
 import org.koin.test.KoinTest
+import kotlin.time.Duration.Companion.hours
 import kotlin.time.Duration.Companion.minutes
 
 class DevUtilityTests :
@@ -41,17 +43,21 @@ class DevUtilityTests :
         configureDevEnvironment(dotenv)
       }
     }
-
     timeout = 5.minutes.inWholeMilliseconds
 
-    test("Delete all Canvas Test Student Users") {
-      val testStudentList = testTemerity.getUsers().filter {
-        it.firstName.startsWith("test", ignoreCase = true) &&
-          it.lastName.startsWith("student", ignoreCase = true) &&
-          it.emailAddress.isEmpty()
-      }
-      testStudentList.forEach {
-        testTemerity.deleteUser(it.userId)
+    test("Delete all Canvas Test Student Users").config(blockingTest = true, timeout = 12.hours) {
+      runBlocking {
+        var deletedUsers = 0
+        val testStudentList = testTemerity.getUsers().filter {
+          it.firstName.startsWith("test", ignoreCase = true) &&
+            it.lastName.startsWith("student", ignoreCase = true) &&
+            it.emailAddress.isEmpty()
+        }
+        testStudentList.forEach {
+          testTemerity.deleteUser(it.userId)
+          deletedUsers++
+        }
+        kermit.d("Deleted $deletedUsers test student users.")
       }
     }
   }
diff --git a/temerity/src/jvmTest/kotlin/edu/ucsc/its/temerity/test/ProdUtilityTests.kt b/temerity/src/jvmTest/kotlin/edu/ucsc/its/temerity/test/ProdUtilityTests.kt
index af9e10d6fe64095548c9e886d25a856d2a858e42..6e43ac6f5bd8bb39733c6c4f378454d9876b6c16 100644
--- a/temerity/src/jvmTest/kotlin/edu/ucsc/its/temerity/test/ProdUtilityTests.kt
+++ b/temerity/src/jvmTest/kotlin/edu/ucsc/its/temerity/test/ProdUtilityTests.kt
@@ -18,16 +18,24 @@
 package edu.ucsc.its.temerity.test
 
 import edu.ucsc.its.temerity.core.Temerity
+import edu.ucsc.its.temerity.core.Temerity.Companion.createLogger
+import io.kotest.common.ExperimentalKotest
 import io.kotest.core.spec.style.FunSpec
+import io.kotest.engine.runBlocking
 import org.koin.test.KoinTest
+import kotlin.time.Duration.Companion.hours
 import kotlin.time.Duration.Companion.minutes
 
 class ProdUtilityTests :
   FunSpec(),
   KoinTest {
 
+  private val kermit = createLogger("ProdUtilityTests")
+
   init {
     coroutineDebugProbes = true
+    @OptIn(ExperimentalKotest::class)
+    blockingTest = true
 
     val dotenv = dotenvVaultJvm()
 
@@ -41,14 +49,19 @@ class ProdUtilityTests :
 
     timeout = 5.minutes.inWholeMilliseconds
 
-    test("Delete all Canvas Test Student Users") {
-      val testStudentList = testTemerity.getUsers().filter {
-        it.firstName.startsWith("test", ignoreCase = true) &&
-          it.lastName.startsWith("student", ignoreCase = true) &&
-          it.emailAddress.isEmpty()
-      }
-      testStudentList.forEach {
-        testTemerity.deleteUser(it.userId)
+    test("Delete all Canvas Test Student Users").config(blockingTest = true, timeout = 12.hours) {
+      runBlocking {
+        var deletedUsers = 0
+        val testStudentList = testTemerity.getUsers().filter {
+          it.firstName.startsWith("test", ignoreCase = true) &&
+            it.lastName.startsWith("student", ignoreCase = true) &&
+            it.emailAddress.isEmpty()
+        }
+        testStudentList.forEach {
+          testTemerity.deleteUser(it.userId)
+          deletedUsers++
+        }
+        kermit.d("Deleted $deletedUsers test student users.")
       }
     }
   }