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
13 changes: 13 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,19 @@ jobs:
uses: gradle/actions/setup-gradle@v6
- name: bootJar
run: ./gradlew bootJar --console=plain
# The SSE `ready` frame reports Implementation-Version to clients as `platformVersion`, and it is read from
# the jar manifest at runtime. Nothing in the test suite can see it: the tests run from exploded classes, where
# there is no manifest and the field is null by design. So dropping the manifest attribute would ship
# platformVersion=null to every consumer with a fully green build. Assert it on the artifact instead.
- name: jar manifest version matches the build
run: |
ver=$(./gradlew -q properties --no-configuration-cache | awk -F': ' '/^version: /{print $2}')
jar="build/libs/protean-${ver}.jar"
test -f "$jar" || { echo "::error::expected $jar, found: $(ls build/libs)"; exit 1; }
got=$(unzip -p "$jar" META-INF/MANIFEST.MF | tr -d '\r' | awk -F': ' '/^Implementation-Version: /{print $2}')
test "$got" = "$ver" \
|| { echo "::error::manifest says '${got:-<absent>}', build says '$ver' — platformVersion would lie to consumers"; exit 1; }
echo "platformVersion will report: $got"

# Worker/container isolation tests self-skip when no bootJar is present (they
# need the fat -boot.jar), so the `test` job never exercises them. This job
Expand Down
11 changes: 7 additions & 4 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@
# dispatch uploads first and creates NO tag, so a Drop lets the SAME version be retried. The tag + release/<version>
# marker are created by a human AFTER the VALIDATED deployment is confirmed (see the release runbook).
#
# Versioning (decision "b"): the source stays at 0.0.1-SNAPSHOT permanently; the release coordinate is never
# committed. It comes from the `version` input and is injected via -Pversion, which build.gradle reads through
# providers.gradleProperty('version'). So dispatching version=0.0.1 publishes org.htcom:protean:0.0.1.
# Versioning (decision "b"): the release coordinate is never committed. It comes from the `version` input and is
# injected via -Pversion, which build.gradle reads through providers.gradleProperty('version'). So dispatching
# version=0.1.0 publishes org.htcom:protean:0.1.0. The committed default is the NEXT version as a SNAPSHOT
# (0.1.0-SNAPSHOT while 0.1.0 is being worked towards), bumped right after each release — it used to hold the
# released version permanently, which made a development build announce itself as older than the release it
# already surpassed once the SSE `ready` frame started reporting it as `platformVersion`.
#
# Signing: in-memory (vanniktech signAllPublications → Bouncy Castle). No gpg binary or keyring is installed on the
# runner; the armored private key is supplied as a string. The key, its passphrase, and the Central Portal user
Expand Down Expand Up @@ -79,7 +82,7 @@ jobs:
ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.ORG_GRADLE_PROJECT_mavenCentralPassword }}
# The container isolation track tells consumers to run ghcr.io/<owner>/protean-worker:<version>, so a release
# that only ships the library leaves that track with no image to pull: ci.yml pushes the image on main, but at
# the development version (0.0.1-SNAPSHOT), never at the release coordinate. Same -Pversion as the upload, and
# the development SNAPSHOT, never at the release coordinate. Same -Pversion as the upload, and
# build.gradle puts that version in jib's `to.image`, so this pushes exactly one tag — there is no floating
# `latest` for a release to fight a later snapshot over. Runs AFTER the Central upload deliberately — the upload is
# the gated, expensive half, and a GHCR tag pushed for a deployment that is later Dropped is deletable, while
Expand Down
8 changes: 8 additions & 0 deletions CHANGELOG.ko.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,14 @@

### 변경

- 커밋되는 개발 버전이 `0.1.0-SNAPSHOT` 이 됐다(종전에는 릴리스된 좌표 그대로인
`0.0.1-SNAPSHOT`). **GitHub Packages 의 개발 스냅샷도 이 좌표로 옮겨 간다** —
`org.htcom:protean:0.0.1-SNAPSHOT` 에 고정된 빌드는 더 이상 갱신을 받지 못하므로
`0.1.0-SNAPSHOT` 으로 옮겨야 한다. 릴리스 좌표는 영향이 없고 여전히 `-Pversion` 으로
주입하며 커밋하지 않는다. 바꾼 이유는 새 `ready` 프레임이다 — 이 값을 클라이언트에
`platformVersion` 으로 내려보내는데, `0.0.1-SNAPSHOT` 은 `0.0.1` 보다 **앞서므로**
모든 개발 빌드가 이미 넘어선 릴리스보다 자기가 낮다고 말하고 있었다.

- `debug.evaluate`·`debug.redefine` 이 이제 **`destructiveHint: true`** 로 광고한다(이전 `false`).
스펙상 이 힌트의 기본값이 `true` 라서 `false` 는 침묵이 아니라 **"아무 말도 않는 툴보다 안전하다"는
주장**이었다. 사실이 아니다 — `evaluate` 는 임의의 메서드·생성자 호출을 해석하고
Expand Down
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,15 @@ delegate to them, read the migration note at the end of this section.

### Changed

- The committed development version is now `0.1.0-SNAPSHOT` (it was `0.0.1-SNAPSHOT`,
left at the released coordinate). **Development snapshots on GitHub Packages move
with it** — a build pinned to `org.htcom:protean:0.0.1-SNAPSHOT` stops receiving
updates and must move to `0.1.0-SNAPSHOT`. Released coordinates are unaffected and
are still injected with `-Pversion`, never committed. The reason for the change is
the new `ready` frame: it reports this value to clients as `platformVersion`, and
`0.0.1-SNAPSHOT` sorts *before* `0.0.1`, so every development build was announcing
itself as older than a release it had already surpassed.

- `debug.evaluate` and `debug.redefine` now advertise **`destructiveHint: true`**
(was `false`). The spec's default for that hint is `true`, so `false` was not
silence — it claimed these were safer than a tool that says nothing. They are
Expand Down
12 changes: 9 additions & 3 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,14 @@ plugins {
group = 'org.htcom'
// Development default is a SNAPSHOT; override with -Pversion=<release> for a Central Portal upload. The Portal's
// validate→Publish (VALIDATED) flow requires a non-SNAPSHOT version — a SNAPSHOT is instead PUT to the maven-snapshots
// repo (a different endpoint/permission), so Level 2 and the eventual go-live pass -Pversion=0.0.1.
version = providers.gradleProperty('version').getOrElse('0.0.1-SNAPSHOT')
// repo (a different endpoint/permission), so a go-live passes -Pversion=<release>.
//
// The default names the version being WORKED TOWARDS, not the one last released, and it is bumped right after each
// release. This used to hold the released version permanently, which was harmless while the version was only a
// publishing coordinate — but the SSE `ready` frame reports it to clients as `platformVersion`, and `0.0.1-SNAPSHOT`
// sorts BEFORE `0.0.1`, so a build carrying post-0.0.1 features announced itself as older than the release that
// lacks them. A version nobody reads can be a placeholder; one on the wire cannot.
version = providers.gradleProperty('version').getOrElse('0.1.0-SNAPSHOT')

java {
toolchain {
Expand Down Expand Up @@ -207,7 +213,7 @@ dependencies {
publishing {
publications {
library(MavenPublication) {
// group=org.htcom, artifactId=protean (settings.gradle rootProject.name), version=0.0.1-SNAPSHOT.
// group=org.htcom, artifactId=protean (settings.gradle rootProject.name), version=the value set above.
// components.java = plain jar (for consumption) + sources + javadoc + generated POM.
// implementation dependencies are written to the POM as runtime scope, api as compile, so consumers resolve them transitively.
// Sidecar worker artifact (C28, process track): the Shadow plugin already contributes the flat shaded
Expand Down
2 changes: 1 addition & 1 deletion docs/guide/04-rest-api.ko.md
Original file line number Diff line number Diff line change
Expand Up @@ -415,7 +415,7 @@ data: {"platform":"protean","platformVersion":"0.1.0","tracesEnabled":true,
여기에는 **연결 시점에 한 번 알아야 하고 이후 스트림으로는 알 수 없는** 값만 싣는다 — 주기적으로 바뀌는 값은 `metrics`·`modules`·`summary` 프레임의 몫이다:

- `platform` — 구현체 식별자. 언제나 `"protean"`. 이 계약을 여러 구현이 공유하므로, 버전 숫자만 받은 콘솔이 그것이 누구의 버전인지 추측하지 않도록 함께 싣는다. **표시·진단용이며 동작을 분기하는 데 쓰지 않는다.**
- `platformVersion` — 이 라이브러리의 버전. jar manifest 의 `Implementation-Version` 에서 읽는다. manifest 가 없는 레이아웃(exploded classes — 테스트·IDE 실행)에서는 **`null`** 이다. 그 상황에서는 버전을 정말로 모르며, 임의의 대체 문자열은 "지금 어느 버전과 이야기하고 있나" 에 대한 지어낸 답이 된다. 필드 자체는 항상 있으므로 클라이언트가 "없음" 과 "모름" 을 구별할 일이 없다.
- `platformVersion` — 이 라이브러리의 버전. jar manifest 의 `Implementation-Version` 에서 읽는다. manifest 가 없는 레이아웃(exploded classes — 이 라이브러리 자신의 테스트, 그리고 클래스 디렉터리로 잡힌 IDE 실행)에서는 **`null`** 이다. 그 상황에서는 버전을 정말로 모르며, 임의의 대체 문자열은 "지금 어느 버전과 이야기하고 있나" 에 대한 지어낸 답이 된다. 실제로는 보기 드물다 — 소비자 앱은 protean 을 jar 로 해석하고 `examples/quickstart` 데모도 그러므로, **콘솔이 실제로 붙는 경로는 전부 진짜 버전을 내려보낸다.** 필드 자체는 항상 있으므로 클라이언트가 "없음" 과 "모름" 을 구별할 일이 없다.
- `tracesEnabled` / `metricsEnabled` — `protean.trace.enabled` 와 `protean.trace.metrics.enabled`. **"기록이 꺼져 있다"** 와 **"켜져 있는데 아직 아무 일도 없었다"** 를 가른다. 빈 스트림만으로는 이 둘이 구별되지 않는다.
- `buffered` — 이 프레임 **다음에 오는 `trace` 프레임이 싣는 행 수**이며, 링이 보유한 행 수가 아니다. `protean.trace.capacity` 가 재생 상한 200 을 넘으면 둘이 달라진다. 숫자만 알리고 행은 주지 않는 것은 둘 다 안 하느니만 못하다.
- `tickMs` — 푸시 주기(`1000`). 클라이언트의 침묵 감지 워치독이 가정 대신 계약을 기준으로 자기 값을 정할 수 있게 한다.
Expand Down
2 changes: 1 addition & 1 deletion docs/guide/04-rest-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -415,7 +415,7 @@ data: {"platform":"protean","platformVersion":"0.1.0","tracesEnabled":true,
It carries only what a client must know **once, at connect time** and cannot derive from the stream afterwards — anything that changes on a schedule belongs to the `metrics`/`modules`/`summary` frames instead:

- `platform` — implementation identifier, always `"protean"`. Present so a console watching several implementations of this contract is not left reading a bare version number and guessing whose it is. **Display and diagnostics only — do not branch behaviour on it.**
- `platformVersion` — this library's version, read from the jar manifest (`Implementation-Version`). **`null`** when running from a layout that has no manifest (exploded classes: tests, IDE runs) — the version is genuinely unknown there, and a placeholder would be a fabricated answer. The field is always present, so a client never has to distinguish "absent" from "unknown".
- `platformVersion` — this library's version, read from the jar manifest (`Implementation-Version`). **`null`** when running from a layout that has no manifest (exploded classes: the library's own test suite, and IDE runs configured against class directories) — the version is genuinely unknown there, and a placeholder would be a fabricated answer. In practice it is rarely seen: a consumer application resolves protean as a jar, and so does the `examples/quickstart` demo, so every path a console actually connects to reports a real version. The field is always present, so a client never has to distinguish "absent" from "unknown".
- `tracesEnabled` / `metricsEnabled` — `protean.trace.enabled` and `protean.trace.metrics.enabled`. They separate **"recording is off"** from **"recording is on but nothing has happened yet"**, which an empty stream cannot.
- `buffered` — how many rows the `trace` frame that follows this one carries, **not** how many the ring holds. The two differ whenever `protean.trace.capacity` exceeds the 200-row replay cap; announcing a count while withholding the rows would be worse than announcing neither.
- `tickMs` — the push period (`1000`), so a client's silence watchdog has a contract to size itself against instead of assuming one.
Expand Down
2 changes: 1 addition & 1 deletion docs/guide/11-operations.ko.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ CI 는 일부러 다르게 하며 그 차이는 고칠 버그가 아니다: CI
- 발행 publication 은 `components.java`(plain jar + Shadow 플러그인이 기여하는 **worker** shaded jar — `shadowed` bundling 속성으로 구분되어 일반 소비자는 여전히 plain jar 를 받음) + sources jar + javadoc jar + 생성 POM.
- **worker 컨테이너 이미지**는 Jib 로 별도 발행한다: `GITHUB_OWNER`/`GITHUB_ACTOR`/`GITHUB_TOKEN` 이 설정되면 `./gradlew jib` 가 `ghcr.io/<owner>/protean-worker:<ver>` 로 push 하고(없으면 `jibDockerBuild`/`jibBuildTar` 로 로컬 빌드), CI 는 라이브러리 발행과 동일 gate 로 `main` push 시에만 실행한다.
- JDBC 드라이버(`mysql-connector-j`, `postgresql`)는 protean 자기 bootJar·테스트엔 필요하지만 소비자에게 transitive 로 강제하지 않으려고 발행 POM 에서 `optional=true` 로 표시된다. 소비자가 worker DB 프로비저닝을 쓰면 자기 드라이버를 명시 추가해야 한다.
- **릴리스는 Maven Central**(Sonatype Central Portal)로 간다 — `com.vanniktech.maven.publish.base` 플러그인. 릴리스 좌표는 커밋하지 않는다: 소스 버전은 `0.0.1-SNAPSHOT` 으로 두고 릴리스는 `-Pversion=<release>` 로 주입한다. `publishToMavenCentral` 은 먼저 `verifyReleaseArtifact` 게이트(아티팩트 집합 + 실제 소비 테스트)를 돌린 뒤 업로드하며, 배포는 **VALIDATED** 에서 멈추고 실공개는 Portal 에서 사람이 따로 Publish 한다. 서명은 in-memory 이고 property-gated(`signingInMemoryKey*`)라 키가 없는 빌드는 다른 곳에 unsigned 로 발행된다. 업로드는 수동 `Release` 워크플로(`workflow_dispatch`)가 돌리며 tag push 로는 발동하지 않는다.
- **릴리스는 Maven Central**(Sonatype Central Portal)로 간다 — `com.vanniktech.maven.publish.base` 플러그인. 릴리스 좌표는 커밋하지 않는다: 릴리스는 `-Pversion=<release>` 로 주입한다. 대신 커밋되는 것은 **다음** 버전의 SNAPSHOT(0.1.0 을 향해 작업 중이면 `0.1.0-SNAPSHOT`)이고, **릴리스 직후 bump 한다**. SSE `ready` 프레임이 이 값을 `platformVersion` 으로 클라이언트에 내려보내기 때문이다 — 방금 릴리스한 버전을 기본값으로 두면 모든 개발 빌드가 이미 넘어선 릴리스보다 자기가 **낮다고** 말하게 된다(`0.0.1-SNAPSHOT` 은 `0.0.1` 보다 앞선다). `publishToMavenCentral` 은 먼저 `verifyReleaseArtifact` 게이트(아티팩트 집합 + 실제 소비 테스트)를 돌린 뒤 업로드하며, 배포는 **VALIDATED** 에서 멈추고 실공개는 Portal 에서 사람이 따로 Publish 한다. 서명은 in-memory 이고 property-gated(`signingInMemoryKey*`)라 키가 없는 빌드는 다른 곳에 unsigned 로 발행된다. 업로드는 수동 `Release` 워크플로(`workflow_dispatch`)가 돌리며 tag push 로는 발동하지 않는다.
- **GitHub Packages** 는 개발 스냅샷을 담는다(CI 가 `main` push 마다 발행). URL·인증을 `build.gradle` 에 하드코딩하지 않고 `gradle.properties`/환경변수로 외부화한다. `githubOwner`/`githubRepo`/`githubActor`/`githubToken`(또는 `GITHUB_OWNER`/`GITHUB_REPO`/`GITHUB_ACTOR`/`GITHUB_TOKEN` env)이 **모두 주어졌을 때만** 원격 repository 가 등록되고(없으면 mavenLocal 만), 그때 `publishLibraryPublicationToGitHubPackagesRepository` 태스크로 발행한다. 자격 템플릿은 `gradle.properties.example` 참고.

## 관련 문서
Expand Down
2 changes: 1 addition & 1 deletion docs/guide/11-operations.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ CI does this differently on purpose, and that difference is not a bug to fix: it
- The publication comprises `components.java` (plain jar + the **worker** shaded jar the Shadow plugin contributes, distinguished by the `shadowed` bundling attribute so default consumers still resolve the plain jar) + sources jar + javadoc jar + the generated POM.
- The **worker container image** publishes separately via Jib: `./gradlew jib` pushes `ghcr.io/<owner>/protean-worker:<ver>` when `GITHUB_OWNER`/`GITHUB_ACTOR`/`GITHUB_TOKEN` are set (otherwise `jibDockerBuild`/`jibBuildTar` build it locally). CI runs this on pushes to `main` only, gated exactly like the library publish.
- The JDBC drivers (`mysql-connector-j`, `postgresql`) are needed for protean's own bootJar/tests but are marked `optional=true` in the published POM so as not to force them transitively on consumers. A consumer using worker DB provisioning must add its own driver explicitly.
- **Releases go to Maven Central** (Sonatype Central Portal) via the `com.vanniktech.maven.publish.base` plugin. The release coordinate is never committed — the source version stays `0.0.1-SNAPSHOT` and the release is injected with `-Pversion=<release>`. `publishToMavenCentral` first runs the `verifyReleaseArtifact` gate (artifact set + a real consume test), then uploads; the deployment stops at **VALIDATED** and going live is a separate manual Publish on the Portal. Signing is in-memory and property-gated (`signingInMemoryKey*`), so a build without the key still publishes unsigned elsewhere. The upload is driven by the manual `Release` workflow (`workflow_dispatch`), never by a tag push.
- **Releases go to Maven Central** (Sonatype Central Portal) via the `com.vanniktech.maven.publish.base` plugin. The release coordinate is never committed — the release is injected with `-Pversion=<release>`. What *is* committed is the next version as a SNAPSHOT (`0.1.0-SNAPSHOT` while 0.1.0 is being worked towards), **bumped right after each release**: the SSE `ready` frame reports this value to clients as `platformVersion`, and a default left at the version just released would make every development build announce itself as older than a release it already surpassed (`0.0.1-SNAPSHOT` sorts before `0.0.1`). `publishToMavenCentral` first runs the `verifyReleaseArtifact` gate (artifact set + a real consume test), then uploads; the deployment stops at **VALIDATED** and going live is a separate manual Publish on the Portal. Signing is in-memory and property-gated (`signingInMemoryKey*`), so a build without the key still publishes unsigned elsewhere. The upload is driven by the manual `Release` workflow (`workflow_dispatch`), never by a tag push.
- **GitHub Packages** carries the development snapshots (CI publishes on pushes to `main`). The URL/credentials are not hardcoded in `build.gradle` but externalized via `gradle.properties`/environment variables. The remote repository is registered **only when all of** `githubOwner`/`githubRepo`/`githubActor`/`githubToken` (or the `GITHUB_OWNER`/`GITHUB_REPO`/`GITHUB_ACTOR`/`GITHUB_TOKEN` env vars) are provided (otherwise mavenLocal only), and it is then published via the `publishLibraryPublicationToGitHubPackagesRepository` task. For the credential template see `gradle.properties.example`.

## Related documents
Expand Down
2 changes: 1 addition & 1 deletion verification/consumer/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ repositories {

// The version under test is injected by the root release gate (-PproteanVersion=<version>); defaults to the dev
// SNAPSHOT so this build is runnable on its own after a plain publishToMavenLocal.
def proteanVersion = (findProperty('proteanVersion') ?: '0.0.1-SNAPSHOT').toString()
def proteanVersion = (findProperty('proteanVersion') ?: '0.1.0-SNAPSHOT').toString()

dependencies {
implementation "org.htcom:protean:${proteanVersion}"
Expand Down
Loading