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
23 changes: 20 additions & 3 deletions .github/workflows/publish-central.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ jobs:
name: publish the existing candidate
needs: [provenance]
runs-on: ubuntu-latest
timeout-minutes: 15
timeout-minutes: 35
environment:
name: central-validation
url: https://central.sonatype.com/publishing/deployments
Expand All @@ -99,6 +99,7 @@ jobs:
import time
import urllib.error
import urllib.request
import xml.etree.ElementTree as ET

version = os.environ['VERSION']
deployment = os.environ['DEPLOYMENT_ID']
Expand Down Expand Up @@ -127,22 +128,38 @@ jobs:
assert status['deploymentName'] == 'io.github.chomingi:langfuse-otel-java:' + version, 'Unexpected deployment name'
expected = {'pkg:maven/io.github.chomingi/' + artifact + '@' + version for artifact in
('langfuse-otel-java', 'langfuse-otel-core', 'langfuse-otel-spring-boot-starter')}
assert expected.issubset(set(status.get('purls', []))), 'Missing expected Maven coordinates'
state = status['deploymentState']
print('Central state:', state, 'coordinates:', status.get('purls', []), flush=True)
if state == 'VALIDATED':
coordinates = {purl.split('?', 1)[0].split('#', 1)[0] for purl in status.get('purls', [])}
assert expected.issubset(coordinates), 'Missing expected Maven coordinates'
post('/deployment/' + deployment)
elif state not in ('PUBLISHING', 'PUBLISHED'):
raise SystemExit('Refusing to publish state ' + state)

deadline = time.monotonic() + 600
deadline = time.monotonic() + 1800
while state != 'PUBLISHED':
if time.monotonic() >= deadline:
raise SystemExit('Publication is still pending; inspect this deployment before retrying.')
time.sleep(15)
status = post('/status?id=' + deployment)
state = status['deploymentState']
print('Central state:', state, flush=True)
if state not in ('VALIDATED', 'PUBLISHING', 'PUBLISHED'):
raise SystemExit('Unexpected publication state ' + state)

# Verify already-published coordinates from the public POMs. Keep the PURL check before
# publication, and never send Central credentials to the public repository.
ns = {'m': 'http://maven.apache.org/POM/4.0.0'}
for artifact in ('langfuse-otel-java', 'langfuse-otel-core', 'langfuse-otel-spring-boot-starter'):
url = ('https://repo.maven.apache.org/maven2/io/github/chomingi/' + artifact + '/' +
version + '/' + artifact + '-' + version + '.pom')
with client.open(urllib.request.Request(url), timeout=30) as response:
pom = ET.fromstring(response.read())
group = pom.findtext('m:groupId', namespaces=ns) or pom.findtext('m:parent/m:groupId', namespaces=ns)
pom_version = pom.findtext('m:version', namespaces=ns) or pom.findtext('m:parent/m:version', namespaces=ns)
assert group == 'io.github.chomingi' and pom_version == version, 'Unexpected public POM identity'
assert pom.findtext('m:artifactId', namespaces=ns) == artifact, 'Unexpected public artifact'
with open(os.environ['GITHUB_STEP_SUMMARY'], 'a') as summary:
summary.write('Maven Central deployment `' + deployment + '` is **PUBLISHED**.\n')
summary.write('Verify public consumer resolution before publishing the GitHub draft.\n')
Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

## [Unreleased]

### Fixed

- Verify published Central coordinates through public POMs when resuming publication; retain
validation-time coordinate checks before publishing and allow up to 30 minutes for synchronization.

## [0.2.2] - 2026-09-25

### Added
Expand Down
6 changes: 6 additions & 0 deletions OBSERVATION-VALIDATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,3 +186,9 @@ The release retains that update and aligns the observation consumer and SDK test
The September 23–24 evidence above remains evidence for 1.62.0; it must not be relabeled as
1.66.0 performance evidence. Release CI reruns contracts, consumer tests and Langfuse read-back
with 1.66.0. The optional sustained-load workflow records its actual runtime separately.

The exact 0.2.2 release commit subsequently passed the hosted Java 17/Linux two-hour profile with
OpenTelemetry 1.66.0: 21,600,000 measured observations, no loss in NORMAL windows, all intentional
fault/overload losses accounted for, and no retained sampled observations. The public core JAR's
SHA-256 matches the tested JAR. See [RELEASE-0.2.2.md](RELEASE-0.2.2.md) for release CI, read-back,
public consumer checks, runtime conditions and the preserved measurement JSON.
79 changes: 79 additions & 0 deletions RELEASE-0.2.2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# 0.2.2 release audit

Release commit: `bef979f3f0238daf500da6998a07a1bd3f3b08c8`.
GitHub-verified signed tag: `v0.2.2`, tag object `ce5b47912215fe94cb5e5743590df23ee512d410`.
OpenTelemetry: `1.66.0`. Existing fluent APIs and starter defaults remain supported.

## Release gates

| Gate | Evidence |
|---|---|
| PR CI | [36120540570](https://github.com/ChoMinGi/langfuse-otel-java/actions/runs/36120540570) |
| Exact main commit CI | [36120934747](https://github.com/ChoMinGi/langfuse-otel-java/actions/runs/36120934747) |
| Signed-tag release gates and Central validation | [36125603878](https://github.com/ChoMinGi/langfuse-otel-java/actions/runs/36125603878) |
| Hosted sustained-load profile | [36120945616](https://github.com/ChoMinGi/langfuse-otel-java/actions/runs/36120945616) |

All four runs passed. Checks include Java 11/17/21, supported Spring AI/LangChain4j combinations,
package/Javadoc/coverage/API compatibility, consumers, operational contracts, static analysis,
licenses and the vulnerability gate (zero High/Critical findings at release scan time).
The independent Langfuse 4.41.0 Docker read-back gate passed on both main and the signed tag;
the optional shared-project export smoke was skipped and is not counted as read-back evidence.

## Exact-commit Langfuse read-back

Four synthetic traces each contained CHAIN root, GENERATION and TOOL observations. Capture-on/off,
hierarchy, metadata search, shared fields and usage were checked. Every observation carried the
full release commit as its version and `0.2.2` as its release. Main CI trace IDs:

- Explicit API, capture on: `ff45f65b2f84285c301971c154f7868b`
- Explicit API, capture off: `922cbc6a1a2e9d95cfc1fe7037b55a37`
- Raw helpers, capture on: `512f4091223f940b17ccf882259d95b0`
- Raw helpers, capture off: `5322055fe5fec1ca73bf3f14959a80b8`

## Release artifact load validation

Hosted Ubuntu/Linux amd64, Java `17.0.20.1+1`, four available processors, 128–512 MiB JVM heap,
32 producer threads, two owned SDK batch workers, fixed 1 KiB input/output and 3,000 offered ops/s.
Ten-minute warmup followed by `7,200.03646041` measured seconds across 24 windows.
The full run took `7,889.848130994` monotonic seconds; maximum wall/monotonic divergence was 1 ms.

| Receiver | Observations | Failed export spans | Queue drops | Window p99 recording latency |
|---|---:|---:|---:|---:|
| NORMAL, 60 minutes | 10,800,000 | 0 | 0 | 15.791–17.663 µs |
| 503, 30 minutes | 5,400,000 | 115,200 | 5,273,536 | 15.247–16.319 µs |
| SLOW 200 ms, 30 minutes | 5,400,000 | 0 | 876,064 | 13.095–17.647 µs |

All 21,600,000 observations were accounted for: 15,335,200 accepted including recovery/drain,
115,200 failed exports and 6,149,600 bounded-queue drops. Queue loss is intentional under the
injected faults/overload; this is not durable or exactly-once delivery. Post-GC live heap ranged
11.23–11.51 MiB, idle JVM thread count stayed at 53, and all 768 sampled ended observations were
collectible. Percentiles cover producer recording calls, excluding pacing, flush and networking.
This is one controlled receiver configuration, not a maximum-throughput, Langfuse capacity or
universal leak-free guarantee. It must not be compared directly with the earlier macOS/1.62.0 run.

[Raw measurements](experiments/core-operations/evidence/2026-09-25-release/load-soak.json) and
[validated summary](experiments/core-operations/evidence/2026-09-25-release/load-soak-summary.json)
are preserved in the repository.

## Maven Central verification

Deployment: `dd04b703-820b-4368-8cf9-252f0893506d`.
The upload included the signed parent POM, both module artifacts/sources/Javadocs and signed
CycloneDX SBOM. Public coordinates were resolved on 2026-09-25 UTC into a new temporary Maven
repository without installing the local reactor. The published artifacts passed Spring AI consumer
(2 tests), LangChain4j consumer (2 tests) and explicit observation consumer (5 tests).

The published core JAR SHA-256 exactly matches the JAR used in the hosted two-hour run:

```text
0ecd199be1066fbb0f9e80befdeb2ee6d40933e7e997f3a5bbfa253632f96c4a
```

The initial publication status poll exceeded ten minutes. Resuming exposed an overly strict
assumption that validation-time PURLs would be unchanged after publication. The publication workflow
now retains the prepublication PURL check, verifies already-published coordinates through public
POMs, and allows thirty minutes for synchronization. No artifact was rebuilt or re-uploaded.

See the [GitHub release](https://github.com/ChoMinGi/langfuse-otel-java/releases/tag/v0.2.2),
[core artifact](https://central.sonatype.com/artifact/io.github.chomingi/langfuse-otel-core/0.2.2)
and [starter artifact](https://central.sonatype.com/artifact/io.github.chomingi/langfuse-otel-spring-boot-starter/0.2.2).
5 changes: 3 additions & 2 deletions RELEASING.md
Original file line number Diff line number Diff line change
Expand Up @@ -232,8 +232,9 @@ Alternatively, a maintainer can dispatch **Publish validated Central candidate**
rebuild artifacts or upload another bundle. Before credentials are exposed it verifies the signed
tag, main ancestry, successful release run containing that deployment ID, and a successful sustained-load
run for the exact release commit. It then checks the Central deployment name and Maven coordinates,
publishes only a validated candidate, and waits for `PUBLISHED`. A retry can resume polling an
already-publishing/published candidate. Public consumer resolution and GitHub draft publication remain
publishes only a validated candidate, and waits up to 30 minutes for `PUBLISHED`. A retry can resume polling an
already-publishing/published candidate. Published coordinates are checked independently through public POMs;
Central authentication headers are never sent to the public repository. Public consumer resolution and GitHub draft publication remain
separate final steps.

```bash
Expand Down
3 changes: 2 additions & 1 deletion ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ release gates in [RELEASING.md](RELEASING.md).

Phase-0 evidence: [CORE-PREFLIGHT.md](CORE-PREFLIGHT.md). API contract: [OBSERVATION-API.md](OBSERVATION-API.md).
Implementation and operational evidence: [OBSERVATION-VALIDATION.md](OBSERVATION-VALIDATION.md).
Release publication follows the gated process in [RELEASING.md](RELEASING.md).
Published artifacts are available from Maven Central. See the [release audit](RELEASE-0.2.2.md).
Development continues on `0.2.3-SNAPSHOT`, with API compatibility checked against `0.2.2`.

## Deferred — Spring Boot 4 adapter line

Expand Down
2 changes: 1 addition & 1 deletion consumer-tests/core-observation-consumer/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.release>11</maven.compiler.release>
<langfuse-otel.version>0.2.2</langfuse-otel.version>
<langfuse-otel.version>0.2.3-SNAPSHOT</langfuse-otel.version>
<junit.version>5.9.3</junit.version>
</properties>

Expand Down
2 changes: 1 addition & 1 deletion consumer-tests/core-prompt-consumer/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.release>11</maven.compiler.release>
<langfuse-otel.version>0.2.2</langfuse-otel.version>
<langfuse-otel.version>0.2.3-SNAPSHOT</langfuse-otel.version>
<junit.version>5.9.3</junit.version>
</properties>

Expand Down
2 changes: 1 addition & 1 deletion consumer-tests/langchain4j-spring-boot-consumer/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
<java.version>17</java.version>
<netty.version>4.1.138.Final</netty.version>
<tomcat.version>10.1.59</tomcat.version>
<langfuse-otel.version>0.2.2</langfuse-otel.version>
<langfuse-otel.version>0.2.3-SNAPSHOT</langfuse-otel.version>
<langchain4j.version>1.0.0</langchain4j.version>
<otel.proto.version>1.3.2-alpha</otel.proto.version>
</properties>
Expand Down
2 changes: 1 addition & 1 deletion consumer-tests/spring-boot-consumer/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
<java.version>17</java.version>
<netty.version>4.1.138.Final</netty.version>
<tomcat.version>10.1.59</tomcat.version>
<langfuse-otel.version>0.2.2</langfuse-otel.version>
<langfuse-otel.version>0.2.3-SNAPSHOT</langfuse-otel.version>
<spring-ai.version>1.0.9</spring-ai.version>
<otel.proto.version>1.3.2-alpha</otel.proto.version>
</properties>
Expand Down
2 changes: 1 addition & 1 deletion examples/langchain4j-example/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
<netty.version>4.1.138.Final</netty.version>
<tomcat.version>10.1.59</tomcat.version>
<jackson-bom.version>2.21.5</jackson-bom.version>
<langfuse-otel.version>0.2.2</langfuse-otel.version>
<langfuse-otel.version>0.2.3-SNAPSHOT</langfuse-otel.version>
</properties>

<dependencies>
Expand Down
2 changes: 1 addition & 1 deletion examples/spring-ai-example/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
<netty.version>4.1.138.Final</netty.version>
<tomcat.version>10.1.59</tomcat.version>
<jackson-bom.version>2.21.5</jackson-bom.version>
<langfuse-otel.version>0.2.2</langfuse-otel.version>
<langfuse-otel.version>0.2.3-SNAPSHOT</langfuse-otel.version>
</properties>

<dependencies>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
{
"file": "experiments/core-operations/target/operations/load-soak.json",
"clockChecksPassed": true,
"groups": [
{
"recording": "body",
"receiver": "NORMAL",
"concurrency": 32,
"samples": 12,
"operations": 10800000,
"measuredSeconds": 3600.0195664479998,
"opsPerSecond": 2999.983694715288,
"p99MicrosRange": [
15.791,
17.663
],
"producerBytesPerOp": 2152.0022755555556,
"postGcHeapBytesRange": [
11780160,
12066360
],
"threadCountRange": [
53,
53
],
"queueDrops": 0,
"failedExportSpans": 0
},
{
"recording": "body",
"receiver": "UNAVAILABLE",
"concurrency": 32,
"samples": 6,
"operations": 5400000,
"measuredSeconds": 1800.008558947,
"opsPerSecond": 2999.9857351561623,
"p99MicrosRange": [
15.247,
16.319
],
"producerBytesPerOp": 2152.0484533333333,
"postGcHeapBytesRange": [
12034248,
12062168
],
"threadCountRange": [
53,
53
],
"queueDrops": 5273536,
"failedExportSpans": 115200
},
{
"recording": "body",
"receiver": "SLOW",
"concurrency": 32,
"samples": 6,
"operations": 5400000,
"measuredSeconds": 1800.008335015,
"opsPerSecond": 2999.986108372659,
"p99MicrosRange": [
13.095,
17.647
],
"producerBytesPerOp": 2152.0022755555556,
"postGcHeapBytesRange": [
12028656,
12064240
],
"threadCountRange": [
53,
53
],
"queueDrops": 876064,
"failedExportSpans": 0
}
],
"measurementSecondsRequested": 7200,
"warmupSecondsRequested": 600,
"twoHourProfileCompleted": true,
"note": "p99 range is across windows, not a merged percentile. Allocation is producer-thread only."
}
Loading
Loading