codeberg4s roadmap
The single source of truth for what is done and what is next. Every commit that
completes a line ticks its box here. Endpoint-level coverage is tracked
separately, in docs/API_INVENTORY.md.
Gate names match PLAN.md §7.
Phase 0 — Bootstrap · Gate G0
- ✓ Mill 1.1.7 pinned in
.mill-version; bootstrap./millcommitted (ADR-0006) - ✓ Seven modules under
object modules: the five published ones —domain,core,codec,transport,client— plusit(integration suites) andexamples(compiled by the build so a stale example breaks it) - ✓
scalacOptionswith warnings fatal - ✓ Scalafmt + Scalafix configs,
mill modules.__.reformatgreen - ✓ scoverage wired via
mill-contrib-scoverage - ✓ ADRs 0001–0006, constitution mapping
- ✓ Scalafix wired via
mill-scalafix(Mill 1.x has no built-infix; ADR-0006) - ✓
verify.shwith the ordered gate and an architecture-boundary check - ✓ CI pipeline —
.forgejo/workflows/ci.ymlruns theverifyjob on push and pull request, and.github/workflows/carries the same gate plusnightly.yml, which holds the slow analysis and the spec-drift detector behind a realschedule:trigger. Every action is pinned to a commit SHA rather than a tag, and Dependabot keeps those pins moving.
Phase 1 — Recon and foundation · Gate G-R, Gate G1
- ✓ Swagger spec vendored and checksummed (
spec/swagger.v1.json,docs/SPEC_PROVENANCE.md) - ✓
docs/API_INVENTORY.md— 506 operations listed, in-scope marked - ✓
docs/HAZARDS.md— all six hazards verified against live probes; two of PLAN.md's assumptions turned out to be wrong - ✓ 54 golden fixtures captured (
modules/codec/test/resources/golden/) - ✓
domain: error ADT,CallContext, opaque identifiers,Auth, config, paging value types - ✓
core:Exec[F], ports,RetryEngine,LinkHeader,Pages,ApiPipeline,StatusMapping,Redaction - ✓ Vertical slice:
GET /versionandGET /repos/{owner}/{repo}through every layer, both rails - ✓ Full error paths on the slice: 404, decode failure, transport failure, retry-then-succeed
Phase 2 — Cross-cutting hardening · Gate G2
- ✓ Track A — retry engine, pagination driver,
Page/PageWalk,Telemetryport wired through bothCodebergClientfactories - ✓ Track B — error ADT, Forgejo error-body parsing against captured samples,
redaction guarantees,
CodebergExceptionbridging - ✓ Walking every page is reachable from the public API as
com.worxbend.codeberg4s.paging.PageWalk(all/fold/foreach). One helper taking the listing operation as an argument, rather than alistAllon each of the thirty-eight API classes — the termination rule is the subtle part of pagination and belongs in one place. - ☐ Property suites (
*Props.scala,Propertytag) for codec laws, pagination invariants, retry bounds. Partially done:modules/domainhasPropertyBase(pinned ScalaCheck seed,Propertytag),IdentifierProps(13 properties over the opaque identifiers andBaseUri),SecretProps(9 properties asserting no rendering path emits a credential),CodebergErrorPropsandPageProps. Of the three areasPLAN.md§6.3 names, two are covered: codec round-trip and totality laws byJsonPropsandApiErrorBodyCodecProps, retry bounds byRetryEngineProps. The pagination driver is the one still open —PaginationPropswas deleted along withcore.Pagination, and the walker that replaced it,paging.PageWalk, has example-based tests only.
Phase 3 — Endpoint waves · Gate G3 per wave
Priority order is value-weighted, per PLAN.md §7. Shared models are owned by
the first wave that needs them; later waves consume rather than redefine
(docs/LEDGER.md).
- ✓ Wave 1 — users (8 operations): current user, lookup by name, search, repositories, followers, following, keys
- ✓ Wave 2 — repos (11 operations): read, search, branches, branch, tags,
releases, release, topics, commits, forks, contents (the union response —
see
docs/HAZARDS.md) - ✓ Wave 3 — issues (10 operations): list/read/create/edit, comments, labels, milestones
- ✓ Wave 4 — pulls (8 operations): list/read/create/edit, merge, reviews, commits, files
- ✓ Wave 5 — orgs (10 operations): organisation read and list, repos, members, public members, teams, team read, team members, team repos, a user's orgs
- ✓ Wave 6 — notifications (7 operations): list, mark all read, unread count, thread read, thread mark-read, per-repository list and mark-read
- ✓ Wave 7 — misc (6 operations): markdown render (both forms), the three
settings/*endpoints, signing key - ✓
GET /repos/issues/search— returns a bare array, not the{ok, data}envelope the other search endpoints use - ✓ The long tail — all 378 remaining in-scope operations, in three rounds: repository Actions, git data and publishing; issues, hooks, access control and administration; user account, user social, organizations and miscellaneous. The in-scope surface is now 439 of 439.
That is 439 of 439 in-scope operations, 100 % (docs/API_INVENTORY.md §0).
Gate G3-final is met. The 67 operations not implemented are the ones PLAN.md
§0 puts out of scope for v1 — admin, activitypub and package.
Per-wave definition of done: models from golden fixtures · codec round-trips ·
both rails · Scaladoc stating the error contract · inventory checkbox flipped ·
coverage thresholds hold. (CRAP and CPD thresholds are part of this definition
on paper only until those tools are proven — docs/CONSTITUTION_MAPPING.md.)
Phase 4 — Release engineering · Gate G4
- ☐ Stryker4s wired, ≥ 80 % mutation score on
domain+core+codec—scripts/mutate.shexists and the runner is proven: the 1.1.1 command runner generates mutants from these sources, scalameta parsed every one of the 196 production files those modules held at the time under the Scala 3 dialect, the instrumented output recompiles under-Werror, and the break threshold demonstrably fails the run. Those modules hold 503 production files today, so even the parse half of that proof predates the current sources. No score exists for this repository — that needs a run with the real test command, roughly 1400mill testinvocations. The ≥ 80 % figure is still a target and is still unproven.docs/CONSTITUTION_MAPPING.mdhas the detail. - ✓ PMD CPD wired, fails above 40 duplicated tokens in production sources —
wired, verified, and green. PMD 7.26.0's scalameta Scala module
tokenises Scala 3 here without a lexical error, and
scripts/cpd.sh --reportfinds 363 duplication groups at 40+ tokens. Ticked because the tool runs and the gate holds, not because the code is duplication-free:verify.shcompares that count againstCPD_BASELINE_GROUPS, so it fails on an increase and tells you to bank a decrease.CPD_MIN_TOKENSis still 40 and every group is still reported. Paying the debt down isdocs/LEDGER.md§"Helpers awaiting promotion". - ✓
scripts/crap.sc, fails on any method with CRAP > 30 — implemented over the scoverage XML, and run against a fresh report: 2,217 methods measured, worst 28.0, limit 30. Note the complexity input is a documented proxy (branch="true"statement count), not a control-flow analysis; the script's header lists the three directions it is known to be wrong in. Read it as a ranking, not a certified metric. - ✓
modules/it— Testcontainers-Forgejo suite (ForgejoContainerSuite) plus the live-Codeberg smoke suite behindCODEBERG_IT=1(CodebergLiveSmokeSuite), both taggedIntegrationand excluded fromverify.sh - ✓
scripts/coverage-gate.sc— reads scoverage's ownstatement-rateandbranch-rate, floors at 90/85 fordomain+core+codecand 80/80 fortransport+client, and treats a missing report as a failure rather than a skip. Called byverify.sh, and asserted against a freshly generated report:domain100.00 % statement / 100.00 % branch,core96.59 % / 92.48 %,codec95.20 % / 91.47 %. - ☐ README with compiling examples,
CHANGELOG.md—CHANGELOG.mdwritten and corrected against the current sources; README rewritten and every Scala block compiled against those sources under the project's flags. Unticked because that check is manual:PLAN.md§"Phase 4" asks for mdoc so the build enforces it. - ☐ Maven Central publishing config, MIMA baseline from 0.1.0 — publishing is
configured:
build.millpublishes five artifacts (codeberg4s-domain,-core,-codec,-transport,-client) undercom.worxbendat0.1.0-SNAPSHOT, MIT,EarlySemVer, withmodules.itdeliberately excluded. MIMA is now wired throughcom.github.lolgab::mill-mima::0.2.2, mixed intoCodeberg4sPublishModuleso one declaration covers all five artifacts. What remains is the publication itself:binaryCompatibleWithisSeq.emptybecause there is nothing on Central to compare against, somimaReportBinaryIssuesreports nothing until 0.1.0 is released.RELEASING.md§ "Binary compatibility is checked by MIMA" carries the measured detail of what MIMA does and does not see through the response models'private[codeberg4s]constructors. - ✓ Nightly spec-drift detector — it lives in
.github/workflows/nightly.ymlbehind a realschedule:trigger (03:00 UTC) plusworkflow_dispatch, alongside theverify.sh --nightlyjob. It was previously declared in.forgejo/workflows/ci.ymlgated on ascheduleevent that workflow never emitted, so it had never run. It compares the sha256 of the liveswagger.v1.jsonagainst the pinned copy and, on a mismatch, emits a warning and a truncated diff in the job summary. It still does not open an issue, asPLAN.md§7 asks — that is the piece left.
Distance to 0.1.0
PLAN.md §10 defines done. Measured against it, honestly:
| Definition-of-done clause | State |
|---|---|
| All in-scope endpoints on both rails with documented error contracts | 439 / 439, 100 % (docs/API_INVENTORY.md §0) — every one on both rails with a Scaladoc error contract |
verify.sh green |
Yes, both modes. Default run: format, lint, zero-warning compile, 3,658 unit tests, boundary check, coverage. --with-slow adds duplication and CRAP and also passes |
Coverage per §6.1 (≥ 90 % line / ≥ 85 % branch on domain+core+codec) |
Enforced and asserted against a fresh report by scripts/coverage-gate.sc: domain 100.00 % / 100.00 %, core 96.59 % / 92.48 %, codec 95.20 % / 91.47 % |
| Mutation ≥ 80 % | Runner proven, no score produced — the figure is unproven — see Phase 4 |
| Zero CRAP > 30 | Yes: 2,217 methods measured, worst 28.0. Its complexity input is a documented proxy, so read it as a ranking |
| CPD clean | No — 363 duplication groups at 40 tokens. The gate passes because it fails on an increase over that recorded number, not because the duplication is gone. A real finding about the code; docs/LEDGER.md names most of them |
| Acceptance features + Gherkin mutation clean | Dormant by decision — docs/CONSTITUTION_MAPPING.md |
| Published to Maven Central | Configured and MIMA wired, but nothing published, so there is still no baseline to compare against |
| README quickstart works against live codeberg.org | Samples are checked against the source signatures by hand. CodebergLiveSmokeSuite exercises the same calls against codeberg.org under CODEBERG_IT=1, but nobody has run the README itself |
| Mapping doc, ADRs, inventory, provenance current | Yes, as of this revision |
The endpoint surface is done. What is left is evidence, not code. Two clauses are genuinely unmet — the mutation score does not exist, and the codebase carries 363 duplication groups that the gate records rather than forgives — and two more are met only by hand: the README's examples are checked by a person rather than by mdoc, and nothing has been published, so MIMA has nothing to compare 0.1.1 against until the 0.1.0 tag is on Central.
Out of scope for 0.1.0
Per PLAN.md §0: OAuth2 token acquisition flows (pre-obtained tokens only),
ActivityPub federation, admin endpoints, attachment streaming above 50 MB, and
Scala.js / Native cross-builds. The Gherkin acceptance pipeline is dormant —
see docs/CONSTITUTION_MAPPING.md.
The 50 MB line is now enforced rather than merely written down:
CodebergConfig.maxDownloadBodyBytes defaults to 50 MiB and a body past it is
TransportCause.ResponseTooLarge, non-retryable. Textual responses have their
own, smaller bound at maxResponseBodyBytes.