Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
41 changes: 37 additions & 4 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ name: Release
# - the packaged Helm chart as a release asset
# - the GitHub Pages Helm repo (docs/charts/) — the `pages` job repackages + reindexes + commits to
# main automatically (deploy/helm/publish.sh remains for a manual/bootstrap refresh).
# - the GitHub Pages Maven repo (docs/maven/) — the same `pages` job publishes basquin-core there
# and commits it, so a Maven build can depend on com.basquin:basquin-core with no credentials.
on:
push:
tags: ['v*']
Expand Down Expand Up @@ -165,13 +167,44 @@ jobs:
helm package deploy/helm/basquin-operator --destination docs/charts \
--version "${{ steps.v.outputs.tag }}" --app-version "${{ steps.v.outputs.tag }}"
helm repo index docs/charts --url https://ianp94.github.io/basquin/charts --merge docs/charts/index.yaml
- name: Commit the updated Helm repo to main
# DD-043: publish basquin-core into docs/maven, which Pages serves at
# https://ianp94.github.io/basquin/maven/ — the Maven-repo counterpart of docs/charts.
# A Maven repo needs no index step (unlike `helm repo index`): the standard
# group/artifact/version layout plus maven-metadata.xml, which Gradle writes, is the whole
# contract. Publishing is additive, so prior versions stay resolvable.
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '17'
# This PR made `version` in basquin-core/build.gradle load-bearing. It was decorative before —
# nothing in the build read it, and the workflow stamps images/CLI/chart straight from the tag —
# so a release-prep commit that bumps the tag but forgets this field is a very plausible miss.
# The failure would be silent: Gradle would republish the OLD coordinate, `git diff --cached`
# would still see changed checksums and commit, and nobody would notice until a consumer 404s
# on the new version. Assert instead of overriding with -Pversion: the repo's convention is that
# the release commit bumps the file, and a guard enforces that convention where an override
# would hide its violation.
- name: Assert basquin-core's version matches the release tag
run: |
set -euo pipefail
CORE="$(./gradlew -q :basquin-core:properties --no-daemon | awk -F': ' '/^version:/{print $2}')"
TAG='${{ steps.v.outputs.tag }}'
echo "basquin-core version=$CORE release tag=$TAG"
if [ "$CORE" != "$TAG" ]; then
echo "::error::basquin-core/build.gradle has version '$CORE' but this release is '$TAG'." \
"Bump it in the release commit, or the Pages Maven repo will advertise the wrong" \
"coordinate for this release."
exit 1
fi
- name: Publish basquin-core into the Pages Maven repo
run: ./gradlew :basquin-core:publishAllPublicationsToPagesRepository --no-daemon
- name: Commit the updated Helm + Maven repos to main
run: |
git config user.name "basquin-bot[bot]"
git config user.email "307641014+basquin-bot[bot]@users.noreply.github.com"
git add docs/charts
git add docs/charts docs/maven
if git diff --cached --quiet; then
echo "no chart changes to publish"; exit 0
echo "no chart or maven changes to publish"; exit 0
fi
git commit -m "chore(release): publish chart ${{ steps.v.outputs.tag }} to the Pages Helm repo [skip ci]"
git commit -m "chore(release): publish chart + basquin-core ${{ steps.v.outputs.tag }} to the Pages repos [skip ci]"
git push origin main
57 changes: 57 additions & 0 deletions basquin-core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
// ResultStore per reset.
plugins {
id 'java'
id 'maven-publish'
}

group = 'com.basquin'
Expand All @@ -22,3 +23,59 @@ repositories { mavenCentral() }
jar {
archiveBaseName = 'basquin-core'
}

// Sources travel with the artifact: a consumer debugging an invariant threshold or the DD-040 id
// scheme needs to read this code, and it is small enough that shipping it costs nothing.
java {
withSourcesJar()
}

// DD-043: basquin-core is the one module consumed from OUTSIDE this Gradle build — by a Maven-built
// Quarkus extension (PR-2). Two publication targets, one configuration:
//
// * publishToMavenLocal -> ~/.m2/repository, for developing the extension against an
// unreleased core. Spike S4's addendum proved an artifact
// present only in the local repo resolves through the injected
// dependency, so this is a verified path, not an assumed one.
// * publishAllPublicationsToPagesRepository
// -> docs/maven/, which GitHub Pages serves at
// https://ianp94.github.io/basquin/maven/ (Pages source is
// main//docs). Consumers add that one <repository> and need no
// credentials — unlike GitHub Packages, which requires a token
// even for public artifacts. Same mechanism as the Helm repo
// already published from docs/charts/.
//
// Maven has no native git-dependency form, so a static repo committed to this repository and served
// over HTTPS is the closest equivalent to depending on the source directly.
publishing {
publications {
core(MavenPublication) {
from components.java
pom {
name = 'basquin-core'
description = 'Framework-neutral measurement core for Basquin: availability-invariant ' +
'evaluation and the per-request result store. No dependencies, by design.'
url = 'https://github.com/ianp94/basquin'
licenses {
license {
name = 'The Apache License, Version 2.0'
url = 'https://www.apache.org/licenses/LICENSE-2.0.txt'
}
}
scm {
url = 'https://github.com/ianp94/basquin'
connection = 'scm:git:https://github.com/ianp94/basquin.git'
}
}
}
}
repositories {
maven {
// A plain directory in the repo. `helm repo index` has an equivalent for charts; Maven
// needs no index step, only the standard group/artifact/version layout, which Gradle
// writes here along with maven-metadata.xml.
name = 'pages'
url = rootProject.layout.projectDirectory.dir('docs/maven')
}
}
}
88 changes: 88 additions & 0 deletions bench-results/dd043-publish-2026-07-25/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# DD-043 — `basquin-core` Maven publication: resolution evidence

**Why this exists.** Spec §4.1 claims the Pages-served Maven layout is resolvable by a real Maven
build. An earlier draft asserted that with quoted build output and **nothing committed behind it** —
which the approver correctly rejected: this project's binding invariant is that claims trace to
committed evidence, and the S4 addendum in the same section already follows that pattern by citing
`bench-results/dd043-spikes-2026-07-24/`. A quoted `BUILD SUCCESS` in prose is worse than a dangling
pointer, because there is no pointer to notice is broken.

This directory is the evidence.

## What was tested, and why over HTTP rather than `file://`

The layout was generated exactly as the release workflow generates it:

```
./gradlew :basquin-core:publishAllPublicationsToPagesRepository
```

then served over **local HTTP** (`python3 -m http.server 8099 --bind 127.0.0.1`, document root
`docs/maven/`) and resolved by Maven from a **clean local repository**, so nothing could be satisfied
from cache.

The first version of this test used a `file://` URL. That was weaker and the difference is not
cosmetic: GitHub Pages serves over HTTPS with its own content types and no directory listing, and
`file://` exercises none of that transport behaviour. HTTP is the same transport *class* as Pages, so
it tests what the claim is actually about.

| File | What it is |
|---|---|
| `consumer-pom.xml` | the throwaway consumer: declares `com.basquin:basquin-core:0.3.0` against the served repo |
| `mvn-resolve.log` | full `mvn -B dependency:resolve` output, clean `-Dmaven.repo.local` |
| `http-server-requests.log` | the server's request log — what Maven actually asked for |

Run with **Apache Maven 3.6.3** on **OpenJDK 17.0.19** (the host toolchain). Recorded because a
resolution result without its resolver version is not reproducible — resolver behaviour is exactly the
kind of thing that differs across Maven versions.

## Result

```
[INFO] BUILD SUCCESS
[INFO] com.basquin:basquin-core:jar:0.3.0:compile
```

**Every request Maven made:**

```
GET /com/basquin/basquin-core/0.3.0/basquin-core-0.3.0.pom
GET /com/basquin/basquin-core/0.3.0/basquin-core-0.3.0.pom.sha1
GET /com/basquin/basquin-core/0.3.0/basquin-core-0.3.0.jar
GET /com/basquin/basquin-core/0.3.0/basquin-core-0.3.0.jar.sha1
```

Two things that request log settles, which reasoning alone had only argued:

1. **Checksum sidecars are fetched** (`.pom.sha1`, `.jar.sha1`) — they are part of the retrieval flow,
so the checksums Gradle writes are reachable and correctly named. The log shows *retrieval only*: it
does **not** show they are validated, and Maven's default `checksumPolicy` is `warn`, so a mismatched
checksum would log a warning rather than fail the build. Do not read this as checksum enforcement.
2. **The Gradle Module Metadata file is never requested.** `basquin-core-0.3.0.module` is published
alongside the POM (Gradle's default), and the concern was whether it could cause a variant mismatch
for a Maven consumer. It cannot: Maven does not ask for it. That was previously an argument from
documented behaviour; here it is an observation.

## What this does and does not establish

**Establishes:** the generated layout — standard `group/artifact/version` plus `maven-metadata.xml` and
checksums — is resolvable by Maven over HTTP from a cold cache, and Maven ignores the `.module` file.

**Does not establish:** that a real GitHub Pages deploy serves it correctly. That cannot be tested
before the first `v*` tag publishes into `docs/maven/`, because Pages serves what is committed. The
supporting argument is precedent rather than evidence: `docs/charts/` is published by the same `pages`
job and `https://ianp94.github.io/basquin/charts/index.yaml` serves chart 0.3.0 over HTTPS today, and
`docs/.nojekyll` disables Jekyll processing repo-wide. **The next release tag is the real test.**

## Reproduce

```bash
./gradlew :basquin-core:publishAllPublicationsToPagesRepository
( cd docs/maven && python3 -m http.server 8099 --bind 127.0.0.1 & )
W=$(mktemp -d); cp bench-results/dd043-publish-2026-07-25/consumer-pom.xml "$W/pom.xml"
( cd "$W" && mvn -B dependency:resolve -Dmaven.repo.local="$W/.m2" )
```

Note the generated artifacts under `docs/maven/` are **not** committed — the release job owns
publishing, so `docs/maven/` holds only `.gitattributes` until the next tag. Running the command above
regenerates them locally.
24 changes: 24 additions & 0 deletions bench-results/dd043-publish-2026-07-25/consumer-pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<groupId>com.basquin.evidence</groupId>
<artifactId>publish-resolution-probe</artifactId>
<version>1.0</version>
<packaging>jar</packaging>
<properties><maven.compiler.release>17</maven.compiler.release></properties>
<!-- Points at a LOCAL HTTP server serving docs/maven verbatim, standing in for
https://ianp94.github.io/basquin/maven/ — same transport class, so this exercises what
file:// could not. -->
<repositories>
<repository>
<id>basquin-pages-probe</id>
<url>http://127.0.0.1:8099</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>com.basquin</groupId>
<artifactId>basquin-core</artifactId>
<version>0.3.0</version>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
127.0.0.1 - - [25/Jul/2026 11:44:50] "GET /com/basquin/basquin-core/0.3.0/basquin-core-0.3.0.pom HTTP/1.1" 200 -
127.0.0.1 - - [25/Jul/2026 11:44:50] "GET /com/basquin/basquin-core/0.3.0/basquin-core-0.3.0.pom.sha1 HTTP/1.1" 200 -
127.0.0.1 - - [25/Jul/2026 11:44:50] "GET /com/basquin/basquin-core/0.3.0/basquin-core-0.3.0.jar HTTP/1.1" 200 -
127.0.0.1 - - [25/Jul/2026 11:44:50] "GET /com/basquin/basquin-core/0.3.0/basquin-core-0.3.0.jar.sha1 HTTP/1.1" 200 -
Loading
Loading