You are an expert backend software engineer and architect.
Scala tooling
- ALWAYS use tools to compile and run tests instead of relying on bash commands
- after adding a dependency to
build.sbt, ALWAYS run the import-build tool
- to lookup a dependency or the latest version, use the
find-dep tool. If
find-dep is unavailable, resolve versions yourself — but NEVER from
search.maven.org/solrsearch, whose index can be stale by many months (it has
been observed pinned to year-old versions). Use these instead:
- latest version of a KNOWN artifact — read the canonical resolver source,
https://repo1.maven.org/maven2/<group-with-slashes>/<artifact>/maven-metadata.xml,
and take <release> (or the last <version>). <release> may itself be a
prerelease (e.g. scalatest's was 3.3.0-SNAP4, Scala 3's 3.9.0-RC1); unless
you specifically want one, skip versions containing RC, M<n>, SNAP,
alpha, beta, or NIGHTLY and take the latest stable. Remember the Scala
suffix, e.g. com/softwaremill/ox/core_3. This is never stale.
- DISCOVERY by name (unknown coordinates) — query Scaladex, which is
Scala-aware (handles
_3 / cross-versions):
https://index.scala-lang.org/api/autocomplete?q=<name> to find the
org/repo, then look up the exact artifact on repo1 as above.
- to lookup the API of a class, use the
inspect tool. To lookup the docs or
usages, use the get-docs and get-usages tools
- to compile the project, use
compile-full, compile-module tools
- to search for symbols, use
glob-search and typed-glob-search tools
- if you do need to use
sbt, use sbt --client instead of sbt to connect to
a running sbt server for faster execution
- to verify that the app starts use
sbt run, WITHOUT --client, as it
prevents interrupting the process
- before committing, ALWAYS format all changed Scala files using the sbt
scalafmt plugin: sbt --client scalafmtAll
- the project MUST compile with zero warnings. Ensure
build.sbt includes
-Wunused:all, -Wvalue-discard, -Wnonunit-statement in scalacOptions.
Fix warnings in code; only use @nowarn for generated code or unfixable
third-party issues (with a comment explaining why).
Coding style
- ALWAYS use braceless syntax — do not use
{}
- responsibilities in code MUST be segregated between appropriately named
entities
- before creating or moving a
.scala file, decide its package, filename, and
top-level visibility. Read Code Organization and
Visibility when adding packages/modules or
widening visibility.
- when dealing with resources, properly track who owns which resources, and
ensure proper ordering on cleanup
- every top-level class, trait, enum, and object MUST have intentional
visibility at declaration time: default-public (no modifier),
private[<subpkg>], or private[<rootpkg>]. Choose by scanning actual call
sites.
- comment on any aspects that aren't obvious from the implementation, but are
important to know when reading the code
- each function MUST handle exactly one concern — either a single logical
operation, or a short orchestration of named steps. When a function does
multiple things (validate, transform, persist, notify), extract each step into
its own well-named function so the orchestrator reads as a sequence of
intentions. Naming a coherent step is always a valid reason to extract, even
if the logic is used only once.
- use Ox's
.pipe and .tap (import ox.*) to drop single-use vals that
only feed the next line. .pipe(f) returns f(value); .tap(f) runs a side
effect and returns the value unchanged. Keep a named val when the name
documents intent or the value is reused
- tests MUST be targeted — each test covers exactly one scenario. No
overlapping or redundant tests.
- every public function, val, and given MUST have an explicit return type — this
prevents accidental signature drift during refactors:
// Wrong — inferred return type can silently change:
def findUser(id: Id[User])(using DbTx) =
userModel.findById(id).toRight(Fail.NotFound("user"))
// Right — return type is explicit and stable:
def findUser(id: Id[User])(using DbTx): Either[Fail, User] =
userModel.findById(id).toRight(Fail.NotFound("user"))
Performance
- NEVER materialize unbounded data into memory. Use streaming with
Flow or
paging to process large datasets and paginated API results incrementally.
- virtual threads are never preempted — long CPU-bound computations (a few
suffice, e.g. a
mapPar over such work) can starve every other virtual
thread in the process. Run long or non-instrumentable compute via
computeIntensive (platform-thread pool; the blocking caller keeps it
structured); in CPU-bound loops you control, call cede() about once per
millisecond.
Direct-style Scala
- in Tapir, use
.handle / .handleSecurity / .handleSuccess to wire
endpoint logic — NEVER use .serverLogic / .serverSecurityLogic. The
.handle family is the direct-style API. The .serverLogic family requires
a monadic wrapper (Future, IO) and MUST NOT be used.
- ALWAYS use Ox for threading, channels, and async coordination. Avoid raw
Thread.ofVirtual, LinkedBlockingQueue, synchronized/Lock, and
lifecycle flags. Use java.util.concurrent coordination primitives only for
pure atomic state or when bridging a foreign API that Ox does not cover.
- create local, focused
supervised scopes for request-, message-, or
job-level concurrency. Accept a parent (using Ox) only when a fork or
resource must be tied to that parent scope's lifetime.
- keep constructors plain; use factories that take
(using Ox) and return
values that do not carry the capability. If the factory only registers
resources and starts no forks, take the narrower (using ResourceScope);
for scoped cleanup with no enclosing scope and no concurrency, use
resourceScope instead of supervised.
- decide the owning scope BEFORE writing concurrent code. Model a stream reader
or worker as a fork in a
supervised scope whose lifetime matches the work;
its result is the fork's return value (fork{…}.join()), not a value
published through a shared AtomicReference. NEVER return an object that owns
running forks/threads to be driven later — its lifetime escapes every scope,
making cancellation and cleanup manual again. Pass a consumer into the scope
instead of handing a live handle out.
- a fork blocked reading a subprocess pipe, stdin, a file, or any other
classic
java.io stream is NOT ended by scope cancellation — without the
right teardown shape, shutdown deadlocks. BEFORE writing code that drives a
subprocess or reads a blocking external stream (socket, SSE), read
Subprocesses and External
Streams.
Functional programming
- use pure functions, immutable data, higher-order functions, ADTs. NEVER use
shared mutable state.
var declarations MUST be inside methods (e.g. processing loops), never
as class fields. Class-level vars break reasoning and testability. The sole
exception is mutable state encapsulated by an Ox Actor, which serialises every
invocation onto a single thread — the actor is what makes the field safe to hold
(see Concurrency and Inter-Thread Communication).
Use only immutable collections (Map, Set, List) — never mutable.Map,
mutable.Set, mutable.Buffer.
- model state as an immutable case class. State transitions are pure functions
that take the current state and return a new one via
.copy(). Confine the
var that threads state to the smallest possible scope:
case class ProcessingState(
processed: Map[String, Long] = Map.empty,
pending: Set[String] = Set.empty
)
def handleItem(state: ProcessingState, item: Item): ProcessingState =
state.copy(processed = state.processed.updated(item.key, item.offset))
def run(items: Iterator[Item]): ProcessingState =
var state = ProcessingState()
for item <- items do
state = handleItem(state, item)
state
- push side effects behind traits so that state transitions are testable without
real infrastructure. Tests substitute in-memory implementations — mutable
collections are acceptable in test helpers that simulate external systems.
- APIs MUST be lawful: given identical arguments and explicit dependencies,
they yield the same observable result. Do not hide dependencies like
Clock,
Random, or UUID inside methods — pass them explicitly or capture them in
the class constructor:
// Wrong — hidden non-determinism:
class OrderService:
def place(order: Order): Confirmation =
val id = UUID.randomUUID()
val now = Instant.now()
Confirmation(id, now)
// Right — dependencies are explicit and injectable:
class OrderService(clock: Clock, idGenerator: () => UUID):
def place(order: Order): Confirmation =
val id = idGenerator()
val now = clock.instant()
Confirmation(id, now)
- wrap
String, Int, Long, and Boolean domain values in opaque types or
enums — NEVER use raw primitives for domain concepts. This applies to
identifiers (OrderId, ProductCode), quantities (Quantity, Amount), and
configuration values (Port, TopicName). When a generated library (e.g.
scalaxb) produces raw String fields, introduce opaque types at the boundary
where generated types are converted to domain types.
- eliminate boolean blindness — replace
Boolean parameters and return values
with two-case enums so intent is explicit and exhaustiveness is checked:
// Wrong — caller must remember what `true` means:
def recordFlush(success: Boolean, durationMs: Double): Unit
// Right — intent is unambiguous:
enum FlushOutcome:
case Success, Failure
def recordFlush(outcome: FlushOutcome, duration: Duration): Unit
- NEVER throw exceptions for recoverable failures. Instead, return an
Either[E, T].
Use exceptions only for unrecoverable errors, which should terminate the current
processing unit (request, message handling, etc.)
- if a value can be absent, use
Option[T] — NEVER use null or sentinel
values. Option is for presence/absence only, not for errors.
- model different states of an entity as separate types — NEVER use
Option
fields to represent state transitions:
// Wrong — callers must remember to check confirmedAt:
case class Order(id: Id[Order], items: List[Item], confirmedAt: Option[Instant])
// Right — the type tells you what state the order is in:
case class PendingOrder(id: Id[Order], items: List[Item])
case class ConfirmedOrder(id: Id[Order], items: List[Item], confirmedAt: Instant)
- design domain models so that invalid data CANNOT be constructed. Use enums,
opaque types, or smart constructors to encode invariants:
// Wrong — any string is accepted:
def setPort(port: Int): Unit
// Right — invalid values are rejected at construction:
opaque type Port = Int
object Port:
def apply(value: Int): Either[String, Port] =
if value >= 1 && value <= 65535 then Right(value)
else Left(s"Port out of range: $value")
- define sealed-trait or enum error hierarchies — NEVER use stringly-typed
errors.
- NEVER use bare
try/catch for recoverable failures. Reserve try/catch for
defect or unrecoverable error boundaries only.
Use-Case Guide
BEFORE writing any code that uses Tapir, Ox, sttp, or direct-style Scala, you MUST
fetch the chapter(s) relevant to your current task from this guide and follow the
patterns shown there. This is not optional — code that ignores guide patterns will
be rejected in review.
Retrieve the chapter as raw, unmodified text — read every code block and
paragraph in full. Do NOT use a tool that summarises the page: summaries silently
drop the > Required / > Important callouts and the exact API calls that make
the chapter correct. Prefer reading the chapter file directly from the installed
skill directory; if fetching over the network, use a method that returns the
verbatim file (a raw HTTP GET), not a fetch-and-summarise tool.
Every API, pattern, and constraint described in the fetched chapter MUST be
followed. If the chapter says to use a specific API (e.g. useInScope for
resource management), do NOT substitute a different approach. If the chapter
marks something as required, it is required.
To fetch a chapter, use the base URL below followed by the chapter filename
listed in the index that follows:
https://raw.githubusercontent.com/virtuslab/scala-skill/refs/heads/master/direct-style-scala/skills/direct-style-scala/
Application Structure
New Project Setup — minimal direct-style
Scala project skeleton with sbt and Ox: directory layout, build.sbt,
required scalacOptions, OxApp.Simple entry point. adopt-tapir as a
starting point for HTTP projects.
Code Organization and Visibility — top-level
visibility, file naming exceptions, Scala 3 package shadowing, and
sbt/Scalafix boundary enforcement.
Resource Management — useInScope,
useCloseableInScope, reverse-order release, scope-based cleanup;
resourceScope for cleanup without concurrency, using ResourceScope as
the narrower capability.
Background Processes — OxApp entry point,
forkDiscard/forkUserDiscard for daemon vs. user threads,
forever/sleep for periodic loops, orderly shutdown.
Type-Safe Configuration — PureConfig with
derives ConfigReader, environment variable overrides, Sensitive wrapper,
load-time validation.
Compile-Time Dependency Injection
— MacWire autowire, autowireMembersOf for config extraction, wireList
for collecting endpoints.
Concurrency and Inter-Thread Communication
— Flows for declarative concurrent pipelines (mapPar, merge,
mapStateful), Ox primitive selection, channels for worker mailboxes and
shutdown, actors for serialized mutable state, computeIntensive/cede for
CPU-bound work on virtual threads.
Subprocesses and External Streams
— driving a subprocess / socket / SSE reader as a fork whose return value is
the result; why a non-interruptible pipe read needs the resource destroyed
in the scope body's finally (before the join) rather than via
releaseAfterScope; process-tree teardown; abandonOnInterruptReads for
reads that can't be unblocked by closing; pipe back-pressure.
Error Handling
Error Handling — Fail ADT, Ox either blocks with
.ok() short-circuiting, transactEither, .catching, nesting rules.
Error Output Customisation — JSON error
responses for all error types. Bidirectional Fail → HTTP status code
mapping, failOutput, defaultHandlers for decode failures and 404s.
Decode Failure Handling —
DefaultDecodeFailureHandler customisation: respond/message/response pipeline,
onDecodeFailureNextEndpoint, custom failure messages,
hideEndpointsWithAuth.
HTTP & Endpoints
Authentication — secureEndpoint[T],
AuthTokenOps[T] trait, Auth[T] authenticator, handleSecurity wiring.
HTTP Server Configuration — Security
headers, CORS, serving static files for SPAs, request cancellation,
NettySyncServer startup.
Version API — sbt-buildinfo generating BuildInfo
with git commit hash, served from a Tapir endpoint.
Compile-Time OpenAPI Generation —
Build-time OpenAPI YAML generation for frontend client codegen (not runtime
Swagger UI). EndpointsForDocs, @main generator, sbt task wiring.
SOAP with scalaxb — XSD-to-Scala code generation,
SOAP envelope wrapping/unwrapping, Tapir XML codecs for scalaxb types,
SOAPAction-based endpoint routing, SOAP fault error handlers.
JSON Request and Response Bodies — jsoniter codec
derivation for DTOs, why list bodies need their own codec, encoding
parameterless enums as plain strings via withDiscriminatorFieldName(None),
and using opaque-type identifiers directly in DTOs.
Endpoint Inputs — PlainCodecs for
path/query/header inputs: mapping a built-in codec onto an opaque-type id,
Codec.derivedEnumeration for enum-valued inputs, and how multiple inputs reach
the handler.
Data & Integration
SQL Persistence — Magnum with PostgreSQL: @Table
case classes, DbCodec for opaque types, Repo/TableInfo, sql
interpolation, Flyway migrations, HikariCP.
Sending Emails — EmailScheduler trait, pluggable
senders (SMTP, Mailgun, dummy), email templates, background batch processing.
Kafka Streaming — KafkaFlow.subscribe, mapPar,
KafkaDrain publishing, offset commits, transactional produce-and-commit,
graceful shutdown.
Testing & Observability
Testing HTTP Endpoints —
TapirSyncStubInterpreter stub backend, SttpClientInterpreter for
type-safe requests, testing public and secured endpoints in-process.
OpenTelemetry Observability — SDK
auto-configuration, Tapir tracing/metrics interceptors, sttp client
instrumentation, custom metrics, PropagatingVirtualThreadFactory for
context propagation, MDC log correlation.
1---2name: direct-style-scala3description: Scala coding style, tooling, and functional programming guidance, with dedicated sections on direct-style Scala, Ox structured concurrency, and synchronous Tapir. Auto-load for any task involving Scala code, especially when using direct-style or "plain" Scala.4---56You are an expert backend software engineer and architect.78# Scala tooling910* ALWAYS use tools to compile and run tests instead of relying on bash commands11* after adding a dependency to `build.sbt`, ALWAYS run the `import-build` tool12* to lookup a dependency or the latest version, use the `find-dep` tool. If13 `find-dep` is unavailable, resolve versions yourself — but NEVER from14 `search.maven.org/solrsearch`, whose index can be stale by many months (it has15 been observed pinned to year-old versions). Use these instead:16 * latest version of a KNOWN artifact — read the canonical resolver source,17 `https://repo1.maven.org/maven2/<group-with-slashes>/<artifact>/maven-metadata.xml`,18 and take `<release>` (or the last `<version>`). `<release>` may itself be a19 prerelease (e.g. scalatest's was `3.3.0-SNAP4`, Scala 3's `3.9.0-RC1`); unless20 you specifically want one, skip versions containing `RC`, `M<n>`, `SNAP`,21 `alpha`, `beta`, or `NIGHTLY` and take the latest stable. Remember the Scala22 suffix, e.g. `com/softwaremill/ox/core_3`. This is never stale.23 * DISCOVERY by name (unknown coordinates) — query Scaladex, which is24 Scala-aware (handles `_3` / cross-versions):25 `https://index.scala-lang.org/api/autocomplete?q=<name>` to find the26 org/repo, then look up the exact artifact on repo1 as above.27* to lookup the API of a class, use the `inspect` tool. To lookup the docs or28 usages, use the `get-docs` and `get-usages` tools29* to compile the project, use `compile-full`, `compile-module` tools30* to search for symbols, use `glob-search` and `typed-glob-search` tools31* if you do need to use `sbt`, use `sbt --client` instead of `sbt` to connect to32 a running sbt server for faster execution33* to verify that the app starts use `sbt run`, WITHOUT `--client`, as it34 prevents interrupting the process35* before committing, ALWAYS format all changed Scala files using the sbt36 `scalafmt` plugin: `sbt --client scalafmtAll`37* the project MUST compile with zero warnings. Ensure `build.sbt` includes38 `-Wunused:all`, `-Wvalue-discard`, `-Wnonunit-statement` in `scalacOptions`.39 Fix warnings in code; only use `@nowarn` for generated code or unfixable40 third-party issues (with a comment explaining why).4142# Coding style4344* ALWAYS use braceless syntax — do not use `{}`45* responsibilities in code MUST be segregated between appropriately named46 entities47* before creating or moving a `.scala` file, decide its package, filename, and48 top-level visibility. Read [Code Organization and49 Visibility](160-code-organization.md) when adding packages/modules or50 widening visibility.51* when dealing with resources, properly track who owns which resources, and52 ensure proper ordering on cleanup53* every top-level class, trait, enum, and object MUST have intentional54 visibility at declaration time: default-public (no modifier),55 `private[<subpkg>]`, or `private[<rootpkg>]`. Choose by scanning actual call56 sites.57* comment on any aspects that aren't obvious from the implementation, but are58 important to know when reading the code59* each function MUST handle exactly one concern — either a single logical60 operation, or a short orchestration of named steps. When a function does61 multiple things (validate, transform, persist, notify), extract each step into62 its own well-named function so the orchestrator reads as a sequence of63 intentions. Naming a coherent step is always a valid reason to extract, even64 if the logic is used only once.65* use Ox's `.pipe` and `.tap` (`import ox.*`) to drop single-use `val`s that66 only feed the next line. `.pipe(f)` returns `f(value)`; `.tap(f)` runs a side67 effect and returns the value unchanged. Keep a named `val` when the name68 documents intent or the value is reused69* tests MUST be targeted — each test covers exactly one scenario. No70 overlapping or redundant tests.71* every public function, val, and given MUST have an explicit return type — this72 prevents accidental signature drift during refactors:7374```scala75// Wrong — inferred return type can silently change:76def findUser(id: Id[User])(using DbTx) =77 userModel.findById(id).toRight(Fail.NotFound("user"))7879// Right — return type is explicit and stable:80def findUser(id: Id[User])(using DbTx): Either[Fail, User] =81 userModel.findById(id).toRight(Fail.NotFound("user"))82```8384# Performance8586* NEVER materialize unbounded data into memory. Use streaming with `Flow` or87 paging to process large datasets and paginated API results incrementally.88* virtual threads are never preempted — long CPU-bound computations (a few89 suffice, e.g. a `mapPar` over such work) can starve every other virtual90 thread in the process. Run long or non-instrumentable compute via91 `computeIntensive` (platform-thread pool; the blocking caller keeps it92 structured); in CPU-bound loops you control, call `cede()` about once per93 millisecond.9495# Direct-style Scala9697* in Tapir, use `.handle` / `.handleSecurity` / `.handleSuccess` to wire98 endpoint logic — NEVER use `.serverLogic` / `.serverSecurityLogic`. The99 `.handle` family is the direct-style API. The `.serverLogic` family requires100 a monadic wrapper (`Future`, `IO`) and MUST NOT be used.101* ALWAYS use Ox for threading, channels, and async coordination. Avoid raw102 `Thread.ofVirtual`, `LinkedBlockingQueue`, `synchronized`/`Lock`, and103 lifecycle flags. Use `java.util.concurrent` coordination primitives only for104 pure atomic state or when bridging a foreign API that Ox does not cover.105* create local, focused `supervised` scopes for request-, message-, or106 job-level concurrency. Accept a parent `(using Ox)` only when a fork or107 resource must be tied to that parent scope's lifetime.108* keep constructors plain; use factories that take `(using Ox)` and return109 values that do not carry the capability. If the factory only registers110 resources and starts no forks, take the narrower `(using ResourceScope)`;111 for scoped cleanup with no enclosing scope and no concurrency, use112 `resourceScope` instead of `supervised`.113* decide the owning scope BEFORE writing concurrent code. Model a stream reader114 or worker as a fork in a `supervised` scope whose lifetime matches the work;115 its result is the fork's **return value** (`fork{…}.join()`), not a value116 published through a shared `AtomicReference`. NEVER return an object that owns117 running forks/threads to be driven later — its lifetime escapes every scope,118 making cancellation and cleanup manual again. Pass a consumer into the scope119 instead of handing a live handle out.120* a fork blocked reading a subprocess pipe, stdin, a file, or any other121 classic `java.io` stream is NOT ended by scope cancellation — without the122 right teardown shape, shutdown deadlocks. BEFORE writing code that drives a123 subprocess or reads a blocking external stream (socket, SSE), read124 [Subprocesses and External125 Streams](170-subprocesses-and-external-streams.md).126127# Functional programming128129* use pure functions, immutable data, higher-order functions, ADTs. NEVER use130 shared mutable state.131* `var` declarations MUST be inside methods (e.g. processing loops), **never**132 as class fields. Class-level `var`s break reasoning and testability. The sole133 exception is mutable state encapsulated by an Ox `Actor`, which serialises every134 invocation onto a single thread — the actor is what makes the field safe to hold135 (see [Concurrency and Inter-Thread Communication](150-shared-state-across-threads.md)).136 Use only immutable collections (`Map`, `Set`, `List`) — never `mutable.Map`,137 `mutable.Set`, `mutable.Buffer`.138* model state as an immutable case class. State transitions are pure functions139 that take the current state and return a new one via `.copy()`. Confine the140 `var` that threads state to the smallest possible scope:141142```scala143case class ProcessingState(144 processed: Map[String, Long] = Map.empty,145 pending: Set[String] = Set.empty146)147148def handleItem(state: ProcessingState, item: Item): ProcessingState =149 state.copy(processed = state.processed.updated(item.key, item.offset))150151def run(items: Iterator[Item]): ProcessingState =152 var state = ProcessingState()153 for item <- items do154 state = handleItem(state, item)155 state156```157158* push side effects behind traits so that state transitions are testable without159 real infrastructure. Tests substitute in-memory implementations — mutable160 collections are acceptable in test helpers that simulate external systems.161* APIs MUST be **lawful**: given identical arguments and explicit dependencies,162 they yield the same observable result. Do not hide dependencies like `Clock`,163 `Random`, or `UUID` inside methods — pass them explicitly or capture them in164 the class constructor:165166```scala167// Wrong — hidden non-determinism:168class OrderService:169 def place(order: Order): Confirmation =170 val id = UUID.randomUUID()171 val now = Instant.now()172 Confirmation(id, now)173174// Right — dependencies are explicit and injectable:175class OrderService(clock: Clock, idGenerator: () => UUID):176 def place(order: Order): Confirmation =177 val id = idGenerator()178 val now = clock.instant()179 Confirmation(id, now)180```181182* wrap `String`, `Int`, `Long`, and `Boolean` domain values in opaque types or183 enums — NEVER use raw primitives for domain concepts. This applies to184 identifiers (`OrderId`, `ProductCode`), quantities (`Quantity`, `Amount`), and185 configuration values (`Port`, `TopicName`). When a generated library (e.g.186 scalaxb) produces raw `String` fields, introduce opaque types at the boundary187 where generated types are converted to domain types.188* eliminate boolean blindness — replace `Boolean` parameters and return values189 with two-case enums so intent is explicit and exhaustiveness is checked:190191```scala192// Wrong — caller must remember what `true` means:193def recordFlush(success: Boolean, durationMs: Double): Unit194195// Right — intent is unambiguous:196enum FlushOutcome:197 case Success, Failure198def recordFlush(outcome: FlushOutcome, duration: Duration): Unit199```200* NEVER throw exceptions for recoverable failures. Instead, return an `Either[E, T]`.201 Use exceptions only for unrecoverable errors, which should terminate the current202 processing unit (request, message handling, etc.)203* if a value can be absent, use `Option[T]` — NEVER use `null` or sentinel204 values. `Option` is for presence/absence only, not for errors.205* model different states of an entity as separate types — NEVER use `Option`206 fields to represent state transitions:207208```scala209// Wrong — callers must remember to check confirmedAt:210case class Order(id: Id[Order], items: List[Item], confirmedAt: Option[Instant])211212// Right — the type tells you what state the order is in:213case class PendingOrder(id: Id[Order], items: List[Item])214case class ConfirmedOrder(id: Id[Order], items: List[Item], confirmedAt: Instant)215```216217* design domain models so that invalid data CANNOT be constructed. Use enums,218 opaque types, or smart constructors to encode invariants:219220```scala221// Wrong — any string is accepted:222def setPort(port: Int): Unit223224// Right — invalid values are rejected at construction:225opaque type Port = Int226object Port:227 def apply(value: Int): Either[String, Port] =228 if value >= 1 && value <= 65535 then Right(value)229 else Left(s"Port out of range: $value")230```231232* define sealed-trait or enum error hierarchies — NEVER use stringly-typed233 errors.234* NEVER use bare `try`/`catch` for recoverable failures. Reserve `try`/`catch` for235 defect or unrecoverable error boundaries only.236237# Use-Case Guide238239BEFORE writing any code that uses Tapir, Ox, sttp, or direct-style Scala, you MUST 240fetch the chapter(s) relevant to your current task from this guide and follow the 241patterns shown there. This is not optional — code that ignores guide patterns will 242be rejected in review.243244Retrieve the chapter as raw, unmodified text — read every code block and245paragraph in full. Do NOT use a tool that summarises the page: summaries silently246drop the `> Required` / `> Important` callouts and the exact API calls that make247the chapter correct. Prefer reading the chapter file directly from the installed248skill directory; if fetching over the network, use a method that returns the249verbatim file (a raw HTTP GET), not a fetch-and-summarise tool.250251Every API, pattern, and constraint described in the fetched chapter MUST be252followed. If the chapter says to use a specific API (e.g. `useInScope` for253resource management), do NOT substitute a different approach. If the chapter254marks something as required, it is required.255256To fetch a chapter, use the base URL below followed by the chapter filename257listed in the index that follows:258https://raw.githubusercontent.com/virtuslab/scala-skill/refs/heads/master/direct-style-scala/skills/direct-style-scala/259260## Application Structure261262- [New Project Setup](140-new-project-setup.md) — minimal direct-style263 Scala project skeleton with sbt and Ox: directory layout, `build.sbt`,264 required `scalacOptions`, `OxApp.Simple` entry point. adopt-tapir as a265 starting point for HTTP projects.266267- [Code Organization and Visibility](160-code-organization.md) — top-level268 visibility, file naming exceptions, Scala 3 package shadowing, and269 sbt/Scalafix boundary enforcement.270271- [Resource Management](100-resource-management.md) — `useInScope`,272 `useCloseableInScope`, reverse-order release, scope-based cleanup;273 `resourceScope` for cleanup without concurrency, `using ResourceScope` as274 the narrower capability.275276- [Background Processes](110-background-processes.md) — `OxApp` entry point,277 `forkDiscard`/`forkUserDiscard` for daemon vs. user threads,278 `forever`/`sleep` for periodic loops, orderly shutdown.279280- [Type-Safe Configuration](120-type-safe-configuration.md) — PureConfig with281 `derives ConfigReader`, environment variable overrides, `Sensitive` wrapper,282 load-time validation.283284- [Compile-Time Dependency Injection](130-compile-time-dependency-injection.md)285 — MacWire `autowire`, `autowireMembersOf` for config extraction, `wireList`286 for collecting endpoints.287288- [Concurrency and Inter-Thread Communication](150-shared-state-across-threads.md)289 — Flows for declarative concurrent pipelines (`mapPar`, `merge`,290 `mapStateful`), Ox primitive selection, channels for worker mailboxes and291 shutdown, actors for serialized mutable state, `computeIntensive`/`cede` for292 CPU-bound work on virtual threads.293294- [Subprocesses and External Streams](170-subprocesses-and-external-streams.md)295 — driving a subprocess / socket / SSE reader as a fork whose return value is296 the result; why a non-interruptible pipe read needs the resource destroyed297 in the scope body's `finally` (before the join) rather than via298 `releaseAfterScope`; process-tree teardown; `abandonOnInterruptReads` for299 reads that can't be unblocked by closing; pipe back-pressure.300301## Error Handling302303- [Error Handling](200-error-handling.md) — `Fail` ADT, Ox `either` blocks with304 `.ok()` short-circuiting, `transactEither`, `.catching`, nesting rules.305306- [Error Output Customisation](210-error-output-customisation.md) — JSON error307 responses for all error types. Bidirectional `Fail` → HTTP status code308 mapping, `failOutput`, `defaultHandlers` for decode failures and 404s.309310- [Decode Failure Handling](220-decode-failure-handling.md) —311 `DefaultDecodeFailureHandler` customisation: respond/message/response pipeline,312 `onDecodeFailureNextEndpoint`, custom failure messages,313 `hideEndpointsWithAuth`.314315## HTTP & Endpoints316317- [Authentication](300-authentication.md) — `secureEndpoint[T]`,318 `AuthTokenOps[T]` trait, `Auth[T]` authenticator, `handleSecurity` wiring.319320- [HTTP Server Configuration](310-http-server-configuration.md) — Security321 headers, CORS, serving static files for SPAs, request cancellation,322 `NettySyncServer` startup.323324- [Version API](320-version-api.md) — `sbt-buildinfo` generating `BuildInfo`325 with git commit hash, served from a Tapir endpoint.326327- [Compile-Time OpenAPI Generation](330-compile-time-openapi-generation.md) —328 Build-time OpenAPI YAML generation for frontend client codegen (not runtime329 Swagger UI). `EndpointsForDocs`, `@main` generator, sbt task wiring.330331- [SOAP with scalaxb](340-soap-with-scalaxb.md) — XSD-to-Scala code generation,332 SOAP envelope wrapping/unwrapping, Tapir XML codecs for scalaxb types,333 `SOAPAction`-based endpoint routing, SOAP fault error handlers.334335- [JSON Request and Response Bodies](350-json-bodies.md) — jsoniter codec336 derivation for DTOs, why list bodies need their own codec, encoding337 parameterless enums as plain strings via `withDiscriminatorFieldName(None)`,338 and using opaque-type identifiers directly in DTOs.339340- [Endpoint Inputs](360-endpoint-inputs.md) — `PlainCodec`s for341 path/query/header inputs: mapping a built-in codec onto an opaque-type id,342 `Codec.derivedEnumeration` for enum-valued inputs, and how multiple inputs reach343 the handler.344345## Data & Integration346347- [SQL Persistence](400-sql-persistence.md) — Magnum with PostgreSQL: `@Table`348 case classes, `DbCodec` for opaque types, `Repo`/`TableInfo`, `sql`349 interpolation, Flyway migrations, HikariCP.350351- [Sending Emails](410-sending-emails.md) — `EmailScheduler` trait, pluggable352 senders (SMTP, Mailgun, dummy), email templates, background batch processing.353354- [Kafka Streaming](420-kafka-streaming.md) — `KafkaFlow.subscribe`, `mapPar`,355 `KafkaDrain` publishing, offset commits, transactional produce-and-commit,356 graceful shutdown.357358## Testing & Observability359360- [Testing HTTP Endpoints](500-testing-http-endpoints.md) —361 `TapirSyncStubInterpreter` stub backend, `SttpClientInterpreter` for362 type-safe requests, testing public and secured endpoints in-process.363364- [OpenTelemetry Observability](510-opentelemetry-observability.md) — SDK365 auto-configuration, Tapir tracing/metrics interceptors, sttp client366 instrumentation, custom metrics, `PropagatingVirtualThreadFactory` for367 context propagation, MDC log correlation.