Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
a116e2b
docs(skill): add the API naming contract (camelCase wire names, snake…
Oct 30, 2026
afcbb31
Refresh Rust dependencies and preserve legacy cron ranges
owjs3901 Oct 30, 2026
4d5c238
Improve empty core schema assertion diagnostics
owjs3901 Oct 30, 2026
913eac9
Improve empty streaming and wire assertions
owjs3901 Oct 30, 2026
efc35ee
Improve empty validation emission assertions
owjs3901 Oct 30, 2026
d38ee93
Improve empty route and router assertions
owjs3901 Oct 30, 2026
05925fb
Improve empty macro schema assertion diagnostics
owjs3901 Oct 30, 2026
2fc9b60
Improve empty circular and file lookup assertions
owjs3901 Oct 30, 2026
aea7a75
Improve empty field attribute assertions
owjs3901 Oct 30, 2026
516d8f1
Improve empty schema input and type assertions
owjs3901 Oct 30, 2026
ed808b0
Refresh landing dependencies and theme snapshot
owjs3901 Oct 30, 2026
e2ded6f
Adapt example and button props to current Devup UI
owjs3901 Oct 30, 2026
2406073
Adapt header props to current Devup UI
owjs3901 Oct 30, 2026
d3df78d
Adapt search props and ordering to current tooling
owjs3901 Oct 30, 2026
1079adc
Adapt sheet props to current Devup UI
owjs3901 Oct 30, 2026
20cde70
Adapt theme and mobile menu props to current Devup UI
owjs3901 Oct 30, 2026
208bb67
Preserve fixed MDX heading elements
owjs3901 Oct 30, 2026
5b7bd59
Refresh CI toolchain actions
owjs3901 Oct 30, 2026
c3317a5
Record dependency refresh patch releases
owjs3901 Oct 30, 2026
243e3ea
Restore baseline lockfile before switching benchmark revisions
owjs3901 Oct 30, 2026
973cdc6
Compare metadata version by content instead of pointer identity
owjs3901 Oct 30, 2026
33c6caa
Test version fast path by content, not address
owjs3901 Oct 30, 2026
ef81468
Replace typed-empty-array assertions with Vec::<T>::new()
owjs3901 Oct 30, 2026
5ddbafb
Add bisect-dep scripts for dependency perf regressions
owjs3901 Oct 30, 2026
4b67082
Measure bench baseline and PR interleaved in one job
owjs3901 Oct 30, 2026
3e043cf
Replace tokio-cron-scheduler with a built-in croner-4 tokio scheduler
owjs3901 Oct 30, 2026
dd3e90c
Add scale/extreme benchmarks and refresh lockfiles to latest
owjs3901 Oct 30, 2026
fdb0b0c
Upgrade Java side to Spring Boot 4.1, Spring 7, JUnit 6, Gradle 9.8
owjs3901 Oct 30, 2026
03158fa
Record Java upgrade and cron scheduler swap in the changepack
owjs3901 Oct 30, 2026
0c88370
Stop forcing axum's default features on vespera and vespera_inprocess
owjs3901 Oct 30, 2026
e17600a
Borrow hoisted 422 validation-error strings from the response body
owjs3901 Oct 30, 2026
8caf404
Simplify BorrowedStr visitor to fix the tarpaulin gap
owjs3901 Oct 30, 2026
a9041e5
Drop the BorrowedStr helper: borrow &str directly in the 422 hoist
owjs3901 Oct 30, 2026
c32ca4d
Trim proc-macro codegen and drop the unused TLS stack from the example
owjs3901 Oct 31, 2026
97879d7
Cut default features and unused dev-dependencies from the dependency …
owjs3901 Oct 31, 2026
04aa5c1
Replace axum-test with a 200-line tower-based test client
owjs3901 Oct 31, 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
1 change: 1 addition & 0 deletions .changepacks/changepack_log_dependency-refresh.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"changes":{"crates/vespera/Cargo.toml":"Minor","crates/vespera_core/Cargo.toml":"Minor","crates/vespera_inprocess/Cargo.toml":"Patch","crates/vespera_macro/Cargo.toml":"Minor","libs/vespera-bridge/build.gradle.kts":"Minor","libs/vespera-bridge-gradle-plugin/build.gradle.kts":"Patch"},"note":"Refresh Rust and Java dependencies and support Rust 1.99 Clippy diagnostics. Replace tokio-cron-scheduler (and its second croner/chrono-tz/uuid/tracing tree) with a built-in tokio scheduler driven by croner 4; the compile-time #[cron] validator and the runtime scheduler now share one parser (vespera_core::cron), removing the croner 3/4 mismatch (compile-time-accepted expressions could be rejected at runtime). vespera-bridge now targets Spring Boot 4.1 / Spring Framework 7 / JUnit 6 / Gradle 9.8. axum is now depended on with default-features off (its tracing/tower-log features are no longer forced on; vespera_inprocess drops 27 transitive crates) and vespera_inprocess declares the tokio sync feature it uses. BREAKING: vespera::tokio_cron_scheduler re-export removed (use vespera::cron); vespera-bridge requires Spring Boot 4. vespera now depends on chrono without default features (serde + now): iana-time-zone and the windows-core proc-macro stack are no longer compiled, and chrono's Local clock (the clock feature) is no longer enabled by vespera itself; re-enable it in your own manifest if you use vespera::chrono::Local. vespera_macro no longer compiles a serde Value (de)serializer copy of the OpenAPI types (export_app! namespacing round-trips through JSON text) and schema! borrows its schema maps.","date":"2026-10-30T12:20:00.000Z"}
8 changes: 4 additions & 4 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ jobs:
- uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- uses: actions-rust-lang/setup-rust-toolchain@v1
- uses: actions-rust-lang/setup-rust-toolchain@v2
- name: Install
uses: dtolnay/rust-toolchain@stable
with:
Expand Down Expand Up @@ -150,7 +150,7 @@ jobs:
packages: write
steps:
- uses: actions/checkout@v7
- uses: actions/setup-java@v5
- uses: actions/setup-java@v6
with:
distribution: 'temurin'
java-version: '17'
Expand Down Expand Up @@ -207,12 +207,12 @@ jobs:
os: [ubuntu-latest, windows-latest, macos-latest]
steps:
- uses: actions/checkout@v7
- uses: actions/setup-java@v5
- uses: actions/setup-java@v6
with:
distribution: 'temurin'
java-version: '17'
cache: 'gradle'
- uses: actions-rust-lang/setup-rust-toolchain@v1
- uses: actions-rust-lang/setup-rust-toolchain@v2
- name: Build rust-jni-demo cdylib (release)
# The vespera-bridge Gradle plugin's bundleNativeLib task copies
# this cdylib from target/release into demo-app's resources, so it
Expand Down
81 changes: 28 additions & 53 deletions .github/workflows/bench.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,37 @@ name: Bench
# Criterion regression gate for the in-process dispatch hot path.
#
# - push to main: runs the gated bench groups as an informational smoke run.
# - pull_request: measures the PR merge base as the `main` baseline, then
# measures the PR head in the same job on the same runner. The job FAILS
# when any bench regresses by more than 30% mean change AND the 95%
# confidence interval lower bound exceeds +20%.
# - pull_request: builds the PR merge base and the PR head (two separate target
# dirs, one bench binary each), then measures them INTERLEAVED in this one job
# on this one runner: 5 rounds alternating base,head / head,base / ... with
# per-side CRITERION_HOME. The job FAILS when a bench's median PAIRED change
# (head/base - 1 within a round) exceeds +30% AND every one of the 5 paired
# rounds exceeds +20% (min of N paired samples = the sign-test lower
# confidence limit of the median, 93.75% for N=5). See the header of
# scripts/bench-interleaved.sh for the full method and why a shared/prebuilt
# target dir would silently benchmark one binary against itself.
# - Cargo.toml remains a trigger so dependency/profile changes are covered,
# but a PR whose only relevant edits are version assignments is skipped.
# - Cargo build outputs are reused between the two measurements within the job;
# target/criterion is never restored from a cross-run cache.
#
# Why interleaved: runner conditions drift on a minutes scale, so measuring the
# baseline in one block and the PR in a later block turned drift into fake
# regressions (two back-to-back runs of PR #144, whose merge base and head hold
# byte-identical Rust, swung -12.33%..+14.92% and tripped a DIFFERENT bench
# each time). Pairing each head measurement with a base measurement taken
# seconds apart cancels that drift, and requiring the +20% floor in every
# round means one noisy round cannot trip the gate while a real >30% slowdown
# (present in every round) still does. The thresholds are unchanged.
#
# Gated groups are the stable per-request paths (wire_path,
# headers_path, request_headers_path, resolve_path, dispatch_path). The
# streaming and contended groups are noisier (spawn_blocking / scheduler
# timing) and the router_path setup micro-bench is low-signal, so those
# are validated locally instead — see PERF_REPORT.md.
#
# The +30% / +20% thresholds are calibrated to the measured noise floor of this
# runner class. Two back-to-back runs of PR #144, whose merge base and head hold
# byte-identical Rust, still swung -12.33%..+14.92% and tripped a DIFFERENT
# benchmark each time, so anything tighter is structurally unachievable here.
# This TIMING gate therefore catches only BIG regressions. Small, deterministic
# ALLOCATION regressions are caught noise-free by the `alloc_budget` integration
# test (a counting global allocator asserting exact per-dispatch allocation
# budgets) in the normal `cargo test` job — the two gates are complementary.
# Small, deterministic ALLOCATION regressions are caught noise-free by the
# `alloc_budget` integration test (a counting global allocator asserting exact
# per-dispatch allocation budgets) in the normal `cargo test` job — the two
# gates are complementary.

on:
push:
Expand All @@ -36,12 +44,14 @@ on:
- 'Cargo.toml'
- 'Cargo.lock'
- '.github/workflows/bench.yml'
- 'scripts/bench-interleaved.sh'
pull_request:
paths:
- 'crates/**'
- 'Cargo.toml'
- 'Cargo.lock'
- '.github/workflows/bench.yml'
- 'scripts/bench-interleaved.sh'

concurrency:
group: bench-${{ github.ref }}
Expand All @@ -59,7 +69,7 @@ jobs:
with:
fetch-depth: 0

- uses: actions-rust-lang/setup-rust-toolchain@v1
- uses: actions-rust-lang/setup-rust-toolchain@v2

- name: Determine PR benchmark scope
if: github.event_name == 'pull_request'
Expand All @@ -75,7 +85,7 @@ jobs:
should_run=false
while IFS= read -r path; do
case "$path" in
crates/*|Cargo.lock|.github/workflows/bench.yml)
crates/*|Cargo.lock|.github/workflows/bench.yml|scripts/bench-interleaved.sh)
should_run=true
;;
esac
Expand Down Expand Up @@ -142,46 +152,11 @@ jobs:
if: github.event_name == 'push'
run: cargo bench -p vespera_inprocess --bench dispatch -- "${BENCH_FILTER}"

- name: Measure merge-base baseline
- name: Interleaved merge-base vs PR head benchmark gate
if: github.event_name == 'pull_request' && steps.scope.outputs.should_run == 'true'
env:
MERGE_BASE: ${{ steps.scope.outputs.merge_base }}
run: |
git checkout --detach "$MERGE_BASE"
rm -rf target/criterion
cargo bench -p vespera_inprocess --bench dispatch -- \
--save-baseline main "${BENCH_FILTER}"

- name: Compare PR head against merge-base baseline
if: github.event_name == 'pull_request' && steps.scope.outputs.should_run == 'true'
env:
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: |
git checkout --detach "$HEAD_SHA"
cargo bench -p vespera_inprocess --bench dispatch -- \
--baseline main "${BENCH_FILTER}"

- name: Enforce regression gate
if: github.event_name == 'pull_request' && steps.scope.outputs.should_run == 'true'
run: |
shopt -s nullglob
fail=0
found=0
while IFS= read -r f; do
found=1
mean=$(jq -r '.mean.point_estimate' "$f")
lower=$(jq -r '.mean.confidence_interval.lower_bound' "$f")
bench=$(dirname "$(dirname "$f")")
bench=${bench#target/criterion/}
printf '%s: mean %+.2f%% (CI lower %+.2f%%)\n' \
"$bench" "$(awk -v v="$mean" 'BEGIN{print v*100}')" \
"$(awk -v v="$lower" 'BEGIN{print v*100}')"
if awk -v m="$mean" -v l="$lower" 'BEGIN{exit !(m > 0.30 && l > 0.20)}'; then
echo "::error::Performance regression: ${bench} mean change exceeds +30% with CI lower bound > +20%"
fail=1
fi
done < <(find target/criterion -path '*/change/estimates.json')
if [ "$found" -eq 0 ]; then
echo "::notice::No change estimates found (first run against this baseline?) — nothing to gate."
fi
exit $fail
scripts/bench-interleaved.sh "$MERGE_BASE" "${BENCH_FILTER}"
4 changes: 2 additions & 2 deletions .github/workflows/jni-bench.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,12 @@ jobs:
timeout-minutes: 30
steps:
- uses: actions/checkout@v7
- uses: actions/setup-java@v5
- uses: actions/setup-java@v6
with:
distribution: 'temurin'
java-version: '17'
cache: 'gradle'
- uses: actions-rust-lang/setup-rust-toolchain@v1
- uses: actions-rust-lang/setup-rust-toolchain@v2
- name: Build rust-jni-demo cdylib (release)
# mimalloc is opt-in; the bench numbers reflect the default
# allocator unless the cdylib is built with --features mimalloc.
Expand Down
9 changes: 8 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Also provides in-process dispatch (`vespera_inprocess` crate) and JNI integratio
| **Binary wire format (JNI)** | `vespera_inprocess` | `[u32 BE len | UTF-8 JSON header | raw body]` — multipart / PDFs / images travel as raw bytes; **`422` validation errors hoisted** into the wire header as `"validation_errors": [...]` so Java decoders never special-case error shapes |
| **Multi-app routing (JNI/FFI)** | `vespera::jni_apps! { "_default" => app, "admin" => admin_app }` | Wire header carries optional `"app"` field; Java side picks per request via `X-Vespera-App` header (configurable via `AppNameResolver`) |
| **Zero-config Spring autoconfigure** | `libs/vespera-bridge/.../VesperaBridgeAutoConfiguration` | `VesperaProxyController` + `AppNameResolver` + `DispatchModeResolver` beans auto-registered; replace any of them via `@ConditionalOnMissingBean` |
| **Cron jobs** | `#[vespera::cron("...")]` | Auto-discovered like routes; runs via `tokio-cron-scheduler` |
| **Cron jobs** | `#[vespera::cron("...")]` | Auto-discovered like routes; runs via the built-in `vespera::cron` tokio scheduler (croner 4, shared parser in `vespera_core::cron`) |

## STRUCTURE

Expand All @@ -50,6 +50,7 @@ vespera/
│ └── VesperaProxyController.java # Auto-configured Spring proxy
├── examples/
│ ├── axum-example/ # Standard axum server demo
│ ├── testkit/ # Tiny in-process test client (tower oneshot) for the examples' integration tests
│ └── rust-jni-demo/ # JNI + standalone server demo
│ ├── src/ # Rust: routes, create_app(), jni_app!
│ └── java/demo-app/ # Java: Spring Boot proxy
Expand Down Expand Up @@ -353,6 +354,12 @@ cd apps/landing && bun test # Same tests from the app directory

# --- Release tooling ---
bun run changepacks # @changepacks/cli version bumps

# --- Perf regression triage ---
# Bisect ONE dependency across crates.io releases (exit 0 = good, non-zero = bad;
# Cargo.lock is restored afterwards). See the header of each script.
scripts/bisect-dep.sh <crate> <good> <bad> <probe command...>
./scripts/bisect-dep.ps1 -Crate <crate> -Good <v> -Bad <v> -Command '<probe>'
```

> **`prelint` gotcha:** any Rust warning fails the JS lint. Run `bun run
Expand Down
Loading
Loading