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
33 changes: 33 additions & 0 deletions .github/scripts/verify-release-readiness.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#!/usr/bin/env node

const fs = require('node:fs');

const REPORT = 'docs/performance/results/order-scale-language-summary.json';
const expectedVersion = process.env.RELEASE_VERSION;

if (!expectedVersion) {
throw new Error('RELEASE_VERSION is required');
}

const summary = JSON.parse(fs.readFileSync(REPORT, 'utf8'));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Provide the release-readiness report before reading it

In the RC workflow this script is run immediately after prepare-rc-release; I checked the repository and there is no docs/performance/results/order-scale-language-summary.json or earlier workflow step that generates it, so readFileSync throws ENOENT on every RC run before the build or publish can proceed. Add/generate the report in the workflow, or make this check consume an artifact that is actually present.

Useful? React with 👍 / 👎.

const candidate = summary.candidate || {};
const expectedCoordinate = `blue.language:blue-language-java:${expectedVersion}`;
const failures = [];

if (candidate.coordinate !== expectedCoordinate) {
failures.push(`candidate coordinate must be ${expectedCoordinate}`);
}
if (candidate.releaseReady !== true) {
failures.push('candidate.releaseReady must be true');
}
if (candidate.verdict !== 'YES') {
failures.push('candidate.verdict must be YES');
}

if (failures.length > 0) {
console.error(`Release authorization failed in ${REPORT}:`);
failures.forEach((failure) => console.error(`- ${failure}`));
process.exit(1);
}

console.log(`Release authorization confirmed for ${expectedCoordinate}`);
114 changes: 106 additions & 8 deletions .github/workflows/release-rc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,30 @@ on:
- next
paths:
- '.cz.toml'
- '.github/**'
- 'CHANGELOG.md'
- 'LICENSE*'
- 'README*'
- 'build.gradle'
- 'settings.gradle.kts'
- 'settings.gradle*'
- 'docs/**'
- 'gradle.properties'
- 'gradle/wrapper/**'
- 'gradlew'
- 'gradlew.bat'
- 'src/**'
- 'tools/**'

permissions:
contents: read

env:
CI: true
GITHUB_TOKEN: ${{ secrets.WORKFLOW_PAT }}
BLUE_RELEASE_CHANNEL: rc

concurrency:
group: release-rc-${{ github.ref }}
cancel-in-progress: false

jobs:
ReleaseRC:
Expand All @@ -27,7 +40,7 @@ jobs:
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.WORKFLOW_PAT }}
persist-credentials: false

- name: Fetch master and tags
run: git fetch origin master:refs/remotes/origin/master --tags
Expand Down Expand Up @@ -56,14 +69,100 @@ jobs:
id: version
run: node .github/scripts/prepare-rc-release.js

- name: Verify release authorization
env:
RELEASE_VERSION: ${{ steps.version.outputs.version }}
run: node .github/scripts/verify-release-readiness.js

- name: Commit and tag RC version
run: |
git add .cz.toml
git commit -m "chore: release ${{ steps.version.outputs.version }}"
git commit --allow-empty -m "chore: release ${{ steps.version.outputs.version }}"
git tag -a "v${{ steps.version.outputs.version }}" -m "Release ${{ steps.version.outputs.version }}"

- name: Execute Gradle build
run: ./gradlew clean build
run: >-
./gradlew clean build identityDifferentialTest
patchSequenceDifferentialTest memoryIntegrationTest cacheLifecycleTest
jmhClasses sourceReleaseArchive

- name: Verify binary API compatibility
run: |
baseline_root="$(mktemp -d)"
master_dir="${baseline_root}/master"
previous_rc_dir="${baseline_root}/previous-rc"
current_version='${{ steps.version.outputs.version }}'
rc_series="${current_version%-rc.*}"
previous_rc_tag=''
while IFS= read -r candidate_tag; do
if [[ "$candidate_tag" != "v${current_version}" ]]; then
previous_rc_tag="$candidate_tag"
break
fi
done < <(git tag --list "v${rc_series}-rc.*" --sort=-version:refname)

git worktree add --detach "$master_dir" origin/master
if [[ -n "$previous_rc_tag" ]]; then
git worktree add --detach "$previous_rc_dir" "$previous_rc_tag"
fi
cleanup() {
git worktree remove --force "$master_dir" || true
if [[ -n "$previous_rc_tag" ]]; then
git worktree remove --force "$previous_rc_dir" || true
fi
}
trap cleanup EXIT

BLUE_RELEASE_CHANNEL=stable "$master_dir/gradlew" -p "$master_dir" jar --no-daemon
if [[ -n "$previous_rc_tag" ]]; then
BLUE_RELEASE_CHANNEL=rc "$previous_rc_dir/gradlew" -p "$previous_rc_dir" jar --no-daemon
fi
mapfile -t master_jars < <(find "$master_dir/build/libs" -maxdepth 1 -type f \
-name 'blue-language-java-*.jar' \
! -name '*-sources.jar' ! -name '*-javadoc.jar' ! -name '*-jmh.jar')
mapfile -t candidate_jars < <(find build/libs -maxdepth 1 -type f \
-name 'blue-language-java-*.jar' \
! -name '*-sources.jar' ! -name '*-javadoc.jar' ! -name '*-jmh.jar')
test "${#master_jars[@]}" -eq 1
test "${#candidate_jars[@]}" -eq 1
tools/check-binary-api.sh \
"${master_jars[0]}" \
"${candidate_jars[0]}" \
build/reports/binary-api/master-to-candidate.txt
if [[ -n "$previous_rc_tag" ]]; then
mapfile -t previous_rc_jars < <(find "$previous_rc_dir/build/libs" -maxdepth 1 -type f \
-name 'blue-language-java-*.jar' \
! -name '*-sources.jar' ! -name '*-javadoc.jar' ! -name '*-jmh.jar')
test "${#previous_rc_jars[@]}" -eq 1
tools/check-binary-api.sh \
"${previous_rc_jars[0]}" \
"${candidate_jars[0]}" \
build/reports/binary-api/previous-rc-to-candidate.txt
printf 'Previous RC baseline: %s\n' "$previous_rc_tag" \
> build/reports/binary-api/previous-rc-baseline.txt
else
printf 'Previous RC baseline: none; master is the only baseline\n' \
> build/reports/binary-api/previous-rc-baseline.txt
fi

# Publish the unique version reservation before any remote artifact upload.
# A failed release can then advance to a new RC instead of reusing a
# coordinate that a previous attempt may already have uploaded.
- name: Push release commit and tag
env:
GH_TOKEN: ${{ secrets.WORKFLOW_PAT }}
run: |
gh auth setup-git
git push origin HEAD:next --follow-tags

- name: Verify immutable release tag provenance
run: |
release_commit="$(git rev-parse HEAD)"
local_tag_commit="$(git rev-parse 'v${{ steps.version.outputs.version }}^{}')"
remote_tag_commit="$(git ls-remote origin 'refs/tags/v${{ steps.version.outputs.version }}^{}' | awk '{print $1}')"
test -n "$remote_tag_commit"
test "$release_commit" = "$local_tag_commit"
test "$release_commit" = "$remote_tag_commit"

- name: Execute Gradle publish
run: ./gradlew publish
Expand All @@ -78,9 +177,6 @@ jobs:
JRELEASER_GPG_SECRET_KEY: ${{ secrets.GPG_SECRET_KEY }}
run: ./gradlew jreleaserFullRelease

- name: Push release commit and tag
run: git push origin HEAD:next --follow-tags

- name: Archive artifacts
uses: actions/upload-artifact@v4
if: always()
Expand All @@ -89,4 +185,6 @@ jobs:
path: |
build/libs
build/publications
build/release
build/reports/binary-api
build/jreleaser
16 changes: 14 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,25 @@ name: Release
on:
workflow_dispatch:

permissions:
contents: read

concurrency:
group: release-stable
cancel-in-progress: false

jobs:
Release:
runs-on: ubuntu-latest
env:
CI: true
BLUE_RELEASE_CHANNEL: stable

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Prepare a stable version before forcing the stable channel

When this manual release workflow runs from the current checkout, .cz.toml still contains 3.1.0-rc.15, but every Gradle command inherits BLUE_RELEASE_CHANNEL=stable; the new build.gradle guard rejects any version that is not plain x.y.z, so the first build step fails before publish or JReleaser can run. Add a stable-version preparation step, pass -PreleaseVersion, or avoid setting the stable channel until the version has been promoted.

Useful? React with 👍 / 👎.

steps:
- name: Check out
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.WORKFLOW_PAT }}
persist-credentials: false

- name: Check if branch is master
run: |
Expand All @@ -38,7 +46,10 @@ jobs:
uses: gradle/gradle-build-action@v2

- name: Execute Gradle build
run: ./gradlew clean build
run: >-
./gradlew clean build identityDifferentialTest
patchSequenceDifferentialTest memoryIntegrationTest cacheLifecycleTest
jmhClasses sourceReleaseArchive

- name: Execute Gradle publish
run: ./gradlew publish
Expand All @@ -61,4 +72,5 @@ jobs:
path: |
build/libs
build/publications
build/release
build/jreleaser
40 changes: 40 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,43 @@
## Unreleased (next 3.1.0 release candidate)

### Feat

- add immutable `FrozenJsonPatch` APIs for direct frozen patch-value handoff
- add configurable per-runtime cache policy, cache statistics, and idempotent runtime close
- add explicit low-memory, high-throughput, and disabled cache policy profiles
- add production-path processing metrics snapshots and conservative patch-impact classification

### Performance

- stream supported strict frozen canonical inputs directly into BlueId digests while retaining
the generic JCS fallback and compatibility oracle
- reuse resolved metadata for dependency-proven basic scalar typed-leaf replacements
- bound reloadable derived snapshots, aliases, recent-processing state, shared verified-reference
acceleration, and structural interning while preserving authoritative and active pinned evidence
- make conservative per-runtime cache bounds the default profile and keep the previous larger
bounds behind an explicit high-throughput profile
- replace hot implicit decimal/index regex compilation with exact ASCII scans

### Compatibility

- retain all existing mutable patch APIs and Java 8 bytecode targeting
- preserve full-resolution fallbacks for schema, fixed-value type, reference, collection,
contracts-changing, custom-merger, and unknown-capability cases
- add reproducible source-release archives and JVM descriptor compatibility reporting
- honor `SOURCE_DATE_EPOCH` for reproducible build metadata timestamps

### Fix

- keep nested transient planning scopes from closing parent reference state
- serialize shared processor-registry and type-resolution updates against processing and reject
lock upgrades instead of deadlocking
- defensively own mutable raw map, list, and array payloads while preserving Jackson/JCS enum
and array behavior
- drain work admitted through `Blue` runtime APIs during close and reject new or re-entrant
cache-sensitive work
- isolate retained conformance views from refreshed cache generations
- bound transient trusted reference retention and report its real eviction/rejection counters

## v2.0.0 (2026-05-13)

### Feat
Expand Down
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,18 @@ blue.cacheResolvedSnapshot(first);
Cache hits improve performance but do not change document identity or processor
gas accounting.

Cache bounds are selected per `Blue` runtime:

```java
Blue serviceRuntime = Blue.withCachePolicy(BlueCachePolicy.lowMemoryDefaults());
Blue batchRuntime = Blue.withCachePolicy(BlueCachePolicy.highThroughputDefaults());
Blue noReloadableCaches = Blue.withCachePolicy(BlueCachePolicy.disabled());
```

`boundedDefaults()` is the conservative production default. `disabled()` turns
off reloadable acceleration caches while preserving snapshots explicitly pinned
with `cacheResolvedSnapshot(...)`.

## Dictionary-Aware Export

A dictionary is a named collection of known Blue type definitions. When you send
Expand Down
Loading
Loading