No effect system

verdict4s-core carries only cats-core, circe and Iron. No effect type, no HTTP client, deliberately no fs2. You can drive the protocol with whatever you already use — including nothing at all.

def mvnDeps = Seq(mvn"com.softinio::verdict4s-core::0.1.0")

This style — the library builds and reads the messages, you do the I/O — is sometimes called sans-IO.

The shape of it

import com.softinio.verdict4s.*
import com.softinio.verdict4s.algebra.*

val questions = Ask(
  Question.noul("Does this convey urgency?"),
  Question.choice[Dept]("Which team should handle this?")
)

// 1. render
val body: Either[Verdict4sError, String] =
  SystemOne.renderAsk(questions, "Help! My payouts are failing.")

// 2. send it however you like -- your code, your HTTP client
//    POST to SystemOne.evaluateUrl with SystemOne.headers(apiKey)

// 3. read the answers back, at exactly the right types
val answers = SystemOne.parseAsk(questions, responseBody)

SystemOne also gives you evaluateUrl, modelsUrl, headers, and parseError for turning a failed response into a typed error.

Why this layer exists

Three things fall out of keeping it effect-free:

Any effect system works. Twitter Future, Akka or Pekko HTTP, sttp, a plain blocking HttpClient — see other effect systems.

Any platform works. The same code runs on the JVM and on Scala.js.

Tests need nothing. Verdict4s' own codec tests have no network, no effect system and no mocks, because there is nothing there to mock.

The invariant that makes this true is narrow and easy to break: core must not gain a dependency on cats-effect, http4s or fs2. Notably fs2 is excluded even though it looks harmless, because fs2-core pulls in cats-effect-kernel.

Validation without sending anything

Because building a request is pure, you can check one in a unit test, or at startup, without a key or a network:

val result = Ask(Question.score("How frustrated?", levels))
  .request(state, Model.JevLatest)

result.isValid  // false if `levels` is not 2..10 entries