Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
c8eb1ae
build: record the measured duplication baseline after the jsoniter re…
w0rxbend Aug 9, 2026
d5e8601
perf(core): parse a Link header once and in linear time
w0rxbend Aug 9, 2026
7da767a
perf(core): build a redacted URI once per call with a single builder
w0rxbend Aug 9, 2026
2063476
test(codec): pin the timestamp shapes Forgejo actually sends
w0rxbend Aug 9, 2026
42013d2
perf(codec): parse a Forgejo timestamp without DateTimeFormatter
w0rxbend Aug 9, 2026
196598c
perf(codec): decode arrays with a builder and stop at first failure
w0rxbend Aug 9, 2026
587ee07
perf(domain): validate a colour and split a path without a regex
w0rxbend Aug 9, 2026
8bc1f40
fix(core): report RetriesExhausted only when the policy actually gave up
w0rxbend Aug 9, 2026
007bcaf
fix(domain)!: reject dot segments in every single-segment identifier
w0rxbend Aug 9, 2026
59acbde
fix(domain)!: reject a base URI carrying credentials, a query or a fr…
w0rxbend Aug 9, 2026
43a984b
perf: stop allocating in the no-op telemetry sink and the attempt rail
w0rxbend Aug 9, 2026
b6b4f44
style(codec): rewrap the ArrayElements scaladoc to the configured width
w0rxbend Aug 9, 2026
4e0d4d6
refactor(domain): give the shared path-segment rule one home
w0rxbend Aug 9, 2026
d5ec0c5
build: bank the duplication the last two commits removed
w0rxbend Aug 9, 2026
f2b20be
test(codec): add a repeatable allocation harness for the decode path
w0rxbend Aug 9, 2026
616b054
fix(codec): settle what a duplicate JSON key means
w0rxbend Aug 9, 2026
6bd0c64
perf(codec)!: read fields from the parser's vector instead of a map
w0rxbend Aug 9, 2026
8d325b4
perf(codec)!: read a whole JSON number as a Long
w0rxbend Aug 9, 2026
1abc05e
perf(core)!: carry a response body as bytes instead of a string
w0rxbend Aug 9, 2026
216654b
test(codec): measure a page decode from bytes and via a string
w0rxbend Aug 9, 2026
e53a0e8
refactor(domain)!: close issue and pull response constructors
w0rxbend Aug 9, 2026
fe059be
refactor(domain)!: close user and organization response constructors
w0rxbend Aug 9, 2026
18d13e8
refactor(domain)!: close repository response constructors
w0rxbend Aug 9, 2026
063de49
refactor(domain)!: close the remaining response constructors
w0rxbend Aug 9, 2026
99b0b02
docs(releasing): record that response models may grow a field
w0rxbend Aug 9, 2026
d500424
fix(domain)!: close the upload commands and validate their media type
w0rxbend Aug 9, 2026
93f7f5c
feat(core)!: bound the size of a response body
w0rxbend Aug 9, 2026
d18462d
fix(client)!: signal a truncated page walk instead of returning a sho…
w0rxbend Aug 9, 2026
79e339c
refactor(core)!: remove the pagination walker PageWalk replaced
w0rxbend Aug 9, 2026
e229097
fix(core)!: compare byte-carrying values by their bytes
w0rxbend Aug 9, 2026
e236d50
fix(domain)!: compare uploads by their bytes, not by array identity
w0rxbend Aug 9, 2026
c89f7b6
fix(client): keep a failing telemetry sink from failing the call
w0rxbend Aug 9, 2026
f96bfa5
fix(core)!: keep a credential out of a decode-failure snippet
w0rxbend Aug 9, 2026
27e2311
fix(transport)!: shut down the HTTP client the library created
w0rxbend Aug 9, 2026
8807ad4
docs(examples): build the shared backend the way that closes
w0rxbend Aug 9, 2026
af20230
fix(client): fail a retry that was waiting when the client closes
w0rxbend Aug 9, 2026
6d3edd1
fix(transport): apply the configured credential after caller headers
w0rxbend Aug 9, 2026
c824b05
test(domain): cover the not-my-type branch of the upload equality
w0rxbend Aug 9, 2026
0651de0
build!: target Java 25 and say so where consumers look
w0rxbend Aug 9, 2026
8ccd3bd
build: wire MIMA so 0.1.0 can be a binary-compatibility baseline
w0rxbend Aug 9, 2026
a91b3eb
build: verify the Mill distribution before executing it
w0rxbend Aug 9, 2026
d164ed6
ci: pin every action to a commit SHA
w0rxbend Aug 9, 2026
c68141b
ci: keep the action pins updated with dependabot
w0rxbend Aug 9, 2026
af549b3
build(deps): drop the unused jsoniter-scala-macros dependency
w0rxbend Aug 9, 2026
408fef4
build(deps): bump jsoniter-scala to 2.40.1
w0rxbend Aug 9, 2026
153ebb1
build(deps): bump scoverage to 2.5.2
w0rxbend Aug 9, 2026
e440ee9
build(deps): bump munit to 1.3.5
w0rxbend Aug 9, 2026
c6c5a74
style: bump scalafmt to 3.11.5
w0rxbend Aug 9, 2026
f9a3044
docs: correct the 0.1.0 release note
w0rxbend Aug 9, 2026
91d8c22
docs: bring the roadmap's distance-to-0.1.0 table up to date
w0rxbend Aug 9, 2026
d8084ad
docs: correct the accessor path for the ZIP download group
w0rxbend Aug 9, 2026
e4df7bc
docs(versions): describe the JSON boundary as jsoniter, not upickle
w0rxbend Aug 9, 2026
aa3437f
docs(adr): put upickle back into ADR-0003's own history
w0rxbend Aug 9, 2026
29f61c4
ci: upload the coverage report that actually exists
w0rxbend Aug 9, 2026
1271678
build: enforce the JSON boundary against the library in use
w0rxbend Aug 9, 2026
f58e17b
docs: track PLAN.md so its two dozen citations resolve
w0rxbend Aug 9, 2026
080cbcc
docs: reassess readiness against what the repo now contains
w0rxbend Aug 9, 2026
48b3c81
ci: give the runner the tools its scripts actually need
w0rxbend Aug 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 18 additions & 4 deletions .forgejo/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,16 @@ name: CI
# and `pull_request`, so neither had ever run. They are defined once, in the
# workflow that actually has a schedule, rather than maintained in two places
# and firing in neither.
#
# The two actions below are pinned to commit SHAs for the same reason the
# GitHub workflows are: a `@v4` tag can be repointed at other code by whoever
# controls the action's repository, and a SHA cannot. Read those SHAs from
# **code.forgejo.org**, not github.com — a Forgejo runner resolves a bare
# `actions/<name>` against its DEFAULT_ACTIONS_URL, which is code.forgejo.org
# on Codeberg. The comment after each SHA is the release tag that carried it
# there. An instance configured to pull actions from somewhere else needs the
# SHAs re-resolved against that forge; the tags alone would silently work and
# silently mean something different.

on:
push:
Expand All @@ -29,15 +39,19 @@ jobs:
verify:
runs-on: docker
container:
image: docker.io/eclipse-temurin:21-jdk
# Must not be older than the `-java-output-version:25` in build.mill.
# Mill provisions its own JDK from .mill-jvm-version, so the image's JDK
# is what scala-cli and any non-Mill step run on; an older image would
# let those two disagree without failing anything.
image: docker.io/eclipse-temurin:25-jdk
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0

- name: Cache Coursier
uses: actions/cache@v4
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
with:
path: .cache/coursier
key: coursier-${{ hashFiles('build.mill', '.mill-version') }}
key: coursier-${{ hashFiles('build.mill', '.mill-version', '.mill-jvm-version') }}
restore-keys: coursier-

# verify.sh runs scripts/coverage-gate.sc through scala-cli, so the image
Expand Down
67 changes: 54 additions & 13 deletions .github/actions/scala-toolchain/action.yml
Original file line number Diff line number Diff line change
@@ -1,22 +1,40 @@
name: Scala toolchain
description: >
JDK 21 (Temurin), a workspace-local Coursier cache and scala-cli — everything
./verify.sh needs and nothing it does not.
JDK 25 (Temurin), a workspace-local Coursier cache, scala-cli and the Coursier
CLI — everything ./verify.sh and scripts/site.sh need, and nothing they do not.

# Every workflow in this repository sets its toolchain up through this one
# action. Four copies of "install a JDK, warm the cache, fetch scala-cli" is
# four places for CI to drift away from itself, and the first symptom of that
# drift is a release built on a different JDK from the one the tests ran on.
#
# scala-cli is not optional decoration: verify.sh runs scripts/coverage-gate.sc
# on every invocation and scripts/crap.sc under --with-slow, and both are
# scala-cli scripts. A runner without scala-cli fails at the coverage step.
# It is also the single place where the release job's toolchain comes from, so
# the two `uses:` below are pinned to commit SHAs rather than to `@v5` / `@v6`.
# A major-version tag is a mutable pointer the action's maintainers — or anyone
# who compromises them — can repoint at other code, and this action runs inside
# the job that holds the artifact signing key. The trailing comment names the
# release each SHA belongs to, so the line stays readable, and
# .github/dependabot.yml lists this directory explicitly so the two pins are
# proposed for update alongside the workflow-level ones.
#
# Neither tool is optional decoration. verify.sh runs scripts/coverage-gate.sc on
# every invocation and scripts/crap.sc under --with-slow, and both are scala-cli
# scripts, so a runner without scala-cli fails at the coverage step. scripts/site.sh
# resolves its mdoc and scaladoc classpaths with `cs fetch`, so a runner without the
# Coursier CLI fails the site build before it compiles a single page.

inputs:
java-version:
description: JDK feature release to install. Change it in one place, here.
description: >
JDK feature release to install. Change it in one place, here. Keep it in
step with `.mill-jvm-version`: that file decides which JDK Mill compiles
with, and `-java-output-version:25` in build.mill fails on anything older
than 25. This input governs what the *rest* of the job sees — scala-cli
scripts, `javap`, anything invoked outside Mill — so letting the two drift
apart means CI silently checks a different toolchain from the one that
produces the jars.
required: false
default: "21"
default: "25"
cache-prefix:
description: >
Cache key prefix. Give a job its own prefix when its Coursier footprint
Expand All @@ -38,19 +56,20 @@ runs:
echo "MILL_JVM_OPTS=-Xmx3g" >> "$GITHUB_ENV"

- name: Set up JDK ${{ inputs.java-version }} (Temurin)
uses: actions/setup-java@v5
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
with:
distribution: temurin
java-version: ${{ inputs.java-version }}

- name: Cache Coursier
uses: actions/cache@v6
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: .cache/coursier
# build.mill names every dependency; .mill-version names Mill's own.
# Nothing else changes what Coursier downloads, so nothing else belongs
# in the key.
key: ${{ inputs.cache-prefix }}-${{ runner.os }}-${{ hashFiles('build.mill', '.mill-version') }}
# build.mill names every dependency; .mill-version names Mill's own;
# .mill-jvm-version names the JDK Coursier provisions and unpacks into
# this same cache. Nothing else changes what Coursier downloads, so
# nothing else belongs in the key.
key: ${{ inputs.cache-prefix }}-${{ runner.os }}-${{ hashFiles('build.mill', '.mill-version', '.mill-jvm-version') }}
restore-keys: |
${{ inputs.cache-prefix }}-${{ runner.os }}-

Expand All @@ -76,3 +95,25 @@ runs:
rm -f "$dir/scala-cli.gz"
echo "$dir" >> "$GITHUB_PATH"
"$dir/scala-cli" version --cli-version

# scripts/site.sh calls `cs fetch` to resolve the mdoc and scaladoc
# classpaths, so the site build needs the Coursier CLI as a binary on PATH —
# having a Coursier *cache* is not the same thing. Pinned by version and
# checksum for the reason the block above gives.
- name: Install the Coursier CLI
shell: bash
env:
COURSIER_CLI_VERSION: "2.1.24"
COURSIER_CLI_SHA256: "d2c0572a17fb6146ea65349b59dd216b38beff60ae22bce6e549867c6ed2eda6"
run: |
set -euo pipefail
dir="$RUNNER_TEMP/coursier"
mkdir -p "$dir"
url="https://github.com/coursier/coursier/releases/download/v${COURSIER_CLI_VERSION}/cs-x86_64-pc-linux.gz"
curl -fsSL -o "$dir/cs.gz" "$url"
echo "${COURSIER_CLI_SHA256} $dir/cs.gz" | sha256sum --check --strict
gunzip -c "$dir/cs.gz" > "$dir/cs"
chmod +x "$dir/cs"
rm -f "$dir/cs.gz"
echo "$dir" >> "$GITHUB_PATH"
"$dir/cs" version
81 changes: 81 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# Keeps the commit-SHA action pins from rotting.
#
# Every `uses:` in .github/ names a 40-character commit SHA rather than a tag,
# so nobody can repoint the code a workflow runs (see the header of
# .github/workflows/release.yml for why that matters in the job holding the
# signing key). The cost of that safety is that a pin is frozen: a fix released
# upstream never arrives on its own, and an unattended pin drifts from
# "deliberate" to "nobody looked at this in two years", which is its own kind of
# stale. Dependabot closes that gap — it opens a pull request when a pinned
# action publishes a new release, rewriting both the SHA and the trailing
# version comment, and a human reviews and merges it like any other change.
#
# SCOPE — this file covers GitHub Actions only.
#
# Two things it deliberately does not cover:
#
# * Scala and Mill dependencies. Dependabot has no Mill support, and
# build.mill is where those versions live. `mill mill.scalalib.Dependency/
# showUpdates` reports them today, and docs/READINESS.md still names
# Renovate as the eventual automation for them — Renovate also runs on
# Codeberg, which Dependabot does not.
#
# * .forgejo/workflows/ci.yml. Dependabot reads .github/workflows and the
# directories listed below; it does not see the Forgejo workflow, and it
# should not touch it anyway. Those SHAs come from code.forgejo.org, and
# Dependabot would resolve versions against github.com, where the same tag
# is different code. That file is updated by hand; its own header says so.

version: 2

updates:
# `directory: "/"` means ".github/workflows" for this ecosystem — the four
# workflow files. It does not reach into .github/actions/, which is why the
# composite action gets its own entry below.
- package-ecosystem: github-actions
directory: "/"
schedule:
interval: weekly
# Monday morning, so an update is waiting at the start of the week
# rather than landing on a Friday afternoon.
day: monday
time: "07:00"
timezone: Europe/Warsaw
# One pull request for all of them. Six separate pull requests to move six
# `actions/*` pins forward is six reviews of the same decision, and the
# reliable outcome of that is that none of them get reviewed.
groups:
github-actions:
patterns:
- "*"
open-pull-requests-limit: 5
# Produces subjects like "ci(deps): bump actions/checkout from 7.0.1 to
# 7.0.2", which is the Conventional Commits form CLAUDE.md requires.
commit-message:
prefix: ci
include: scope
labels:
- dependencies
- github-actions

# The composite action every workflow builds its toolchain through. Its two
# pins are the ones the release job runs with, so they matter at least as
# much as the workflow-level ones.
- package-ecosystem: github-actions
directory: "/.github/actions/scala-toolchain"
schedule:
interval: weekly
day: monday
time: "07:00"
timezone: Europe/Warsaw
groups:
github-actions:
patterns:
- "*"
open-pull-requests-limit: 5
commit-message:
prefix: ci
include: scope
labels:
- dependencies
- github-actions
25 changes: 19 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ jobs:
# longer has hung, not slowed down.
timeout-minutes: 45
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
# verify.sh's scalafmt step asks Git which Scala sources are tracked
# (.scalafmt.conf sets project.git = true). A shallow checkout is
Expand All @@ -49,13 +49,26 @@ jobs:
- name: Verify
run: ./verify.sh

# Coverage HTML is the one artifact worth keeping from a pull request:
# the gate reports a percentage, and the report says which lines.
# The coverage report is the one artifact worth keeping from a pull
# request: the gate prints a percentage, the report says which lines.
#
# It is XML, not HTML. verify.sh's coverage step runs
# `mill <module>.scoverage.xmlReport` and nothing else, because XML is
# what scripts/coverage-gate.sc and scripts/crap.sc read. Mill can also
# render `scoverage.htmlReport`, but generating it here would mean a
# `mill` invocation in this workflow, which the header above rules out,
# and adding it to verify.sh would charge every local run for a page
# nothing reads. To read a downloaded report as a web page, run
# `./mill modules.__.scoverage.htmlReport` locally against the same
# commit.
#
# The path is the report directory itself rather than the whole
# `scoverage/` tree, which is mostly Mill's own task cache.
- name: Upload coverage reports
if: always()
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: scoverage-${{ github.run_id }}
path: out/modules/*/scoverage/
name: scoverage-xml-${{ github.run_id }}
path: out/modules/*/scoverage/xmlReport.dest/scoverage.xml
if-no-files-found: ignore
retention-days: 14
6 changes: 3 additions & 3 deletions .github/workflows/nightly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ jobs:
# produced for this repository it will be hours, not minutes.
timeout-minutes: 360
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- uses: ./.github/actions/scala-toolchain
with:
Expand Down Expand Up @@ -65,7 +65,7 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

# The pinned spec is a snapshot of a moving target. This does not fail
# the build when Codeberg deploys a new Forgejo — it reports, so that a
Expand Down Expand Up @@ -113,7 +113,7 @@ jobs:

- name: Keep the live spec for comparison
if: always()
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: live-swagger-${{ github.run_id }}
path: ${{ runner.temp }}/live.json
Expand Down
15 changes: 14 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,19 @@ name: Release
# The full procedure — how to set the version, how the key and credentials are
# produced, how to verify the release landed, what to do about a bad one — is
# in RELEASING.md. This file is that document made executable.
#
# Every third-party action below is pinned to a full commit SHA, with the
# release it belongs to in a trailing comment so the line stays readable. A tag
# like `v7` is a mutable pointer: whoever controls the action's repository can
# move it to different code at any time, and this job would fetch and run that
# code with MILL_PGP_SECRET_BASE64 — the signing key — in its environment.
# A commit SHA cannot be moved. `.github/dependabot.yml` opens a pull request
# when a pinned action publishes a new release, so the pins are reviewed and
# moved forward deliberately instead of quietly ageing.
#
# `./.github/actions/scala-toolchain` is exempt because it is not fetched from
# anywhere: a `uses:` beginning with `./` runs the copy in this repository at
# the commit being built, so it is already pinned by the checkout above.

on:
push:
Expand All @@ -34,7 +47,7 @@ jobs:
# artifact reaches Central.
name: maven-central
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- uses: ./.github/actions/scala-toolchain

Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/site.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ jobs:
group: site-build-${{ github.ref }}
cancel-in-progress: true
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- uses: ./.github/actions/scala-toolchain
with:
Expand Down Expand Up @@ -64,7 +64,7 @@ jobs:
fi

- name: Package for Pages
uses: actions/upload-pages-artifact@v5
uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0
with:
path: out/site/html

Expand All @@ -90,4 +90,4 @@ jobs:
url: ${{ steps.deployment.outputs.page_url }}
steps:
- id: deployment
uses: actions/deploy-pages@v5
uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0
8 changes: 6 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -265,9 +265,13 @@ docs/ai/
# AI-generated plan / scratch documents
#
# These are agent working notes, not project docs. If you ever want to commit
# one deliberately, use: git add -f PLAN.md
# one deliberately, use: git add -f <name>.md
#
# PLAN.md is the exception and is deliberately absent from the list below. It
# is tracked, because two dozen tracked files cite it by section number and a
# citation to an ignored file is a dead reference for everyone who clones this
# repository. Its own header says what it is and where it has been superseded.
# ---------------------------------------------------------------------------
PLAN.md
PLANS.md
PLANNING.md
IMPLEMENTATION_PLAN.md
Expand Down
Loading