ADR-0005 — Future-based public API, resolving the PLAN / STYLE conflict

Context

The repository carries two documents that disagree about the shape of the public API, and CLAUDE.md names one of them the single source of truth:

Question PLAN.md SCALA_CODE_STYLE.md
Concurrency Future, sttp Backend[Future] Direct style, Ox supervised scopes
Error channel Future failing with CodebergException Either[CodebergError, A]
Pagination Page[A], listAll: Future[Vector[A]] ox.flow.Flow[A]
JSON upickle jsoniter-scala
Package prefix codeberg4s.* com.worxbend.codeberg4s.*

This is not a style disagreement that can be split; the two produce different libraries.

Decision

PLAN.md wins on API shape. SCALA_CODE_STYLE.md wins on everything else. This was confirmed with the repository owner before any production code was written.

Concretely:

The dual rail

PLAN.md §3.2 asks for two error contracts, and both are kept, because they serve genuinely different callers:

client.repos.get(owner, name):         Future[Repository]                        // fails with CodebergException
client.repos.attempt.get(owner, name): Future[Either[CodebergError, Repository]] // never fails

CodebergException carries the full CodebergError ADT, so the convenience rail loses no information — recover { case e: CodebergException => e.error } recovers the typed value.

The rails are mechanical projections of one implementation in modules/core. Duplicating logic across them is a review-blocking defect.

Consequences

Good: idiomatic for the Future-using majority; no effect-system dependency; the style guide still governs the parts that make the code readable and safe.

Bad: Future is eager, which makes retry and pagination subtle — a Future already running cannot be re-run. Mitigated exactly as PLAN.md §9 proposes: all such logic lives in core over Exec[F] and is tested with F = Either; Future appears only at the outermost projection, where each attempt is a fresh thunk (Exec.suspend).

Bad: the style guide's examples no longer match the code in two places. Its Ox examples describe a dependency this build does not have at all. Its jsoniter examples now name the right library — that part was settled by ADR-0003 — but they derive a codec per DTO with JsonCodecMaker, and this build derives none; modules/codec hand-writes a single codec for a document model instead, for the reasons ADR-0003 gives. SCALA_CODE_STYLE.md is left unedited — it is upstream-derived — and this ADR is the pointer that explains the divergence.