Add a compact semver and upgrade policy for v1: stable beginner APIs, stable wire/runtime compatibility, advanced escape-hatch tier, breaking-change rules, upgrade-note template, and release checklist. req: abi/001 req: abi/002 req: abi/003 req: abi/004 req: abi/005 req: public_api/001 req: public_api/002 req: public_api/005 req: runtime/003 req: runtime/004
5.8 KiB
Recipe: deploy and version compatibility
This recipe describes the production deployment boundary for a hemx app. The server binary, generated Rust helpers, generated symbol/fingerprint metadata, and JavaScript runtime asset must be treated as one release unit. hemx core provides the ABI/fingerprint checks; the application and platform own rollout, caching, observability, and rollback policy. req: abi/001 req: abi/002 req: runtime/004
Use this for examples/saas-style apps before putting multiple app versions
behind a load balancer or CDN.
Release unit
A compatible release contains:
- the Rust server binary built from the same checkout as
build.rs - generated
hemx.generated.rsand symbols produced during that build - the
hemx-jsruntime asset served by that server or deployed with the same release - templates, CSS, island JavaScript, migrations, and app config for that release
Do not mix a newly built server with an old /hemx.js, old generated output, or
old cached page shell. Build fingerprints are derived from Surface/schema/ABI
parts, so mismatches are detected and partial updates fail closed instead of
mutating the wrong DOM. req: abi/003 req: abi/004 req: failure/005
Asset serving
Serve /hemx.js from the same release as the server when possible:
async fn runtime() -> impl IntoResponse {
hemx_axum::runtime_js()
}
If a CDN is required, publish the runtime under a content-addressed release path and keep the page shell pointing at that exact path:
<script src="/assets/2026-06-05T120000Z/hemx.js" defer></script>
Avoid long-lived unversioned CDN caching for /hemx.js. If an unversioned path
is used, set a short cache TTL or purge it atomically with the server rollout.
CSS and explicit island scripts should follow the same release path policy.
Rolling deploys
Rolling deploys are safe when every response serves a self-consistent release. The easiest policy is sticky-by-release routing:
- page HTML, interaction POSTs, SSE/polling endpoints, and
/hemx.jscome from the same server revision - a load balancer cookie or platform routing key keeps an active browser on one revision during the rollout window
- old revisions stay alive until active SSE connections and in-flight forms have drained
If sticky routing is not available, make the mismatch behavior user-safe:
- keep full page GETs compatible across one adjacent version when practical
- allow interaction responses to fail closed on fingerprint mismatch
- prefer redirect/reload fallback over best-effort partial mutation
- report mismatch counts so rollouts can be paused quickly
The runtime must not grow a negotiation protocol or compatibility shim in core; capability negotiation belongs to optional integration crates. req: runtime/004
Fingerprint and mismatch behavior
Initial roots carry the build fingerprint, and effect responses carry the fingerprint for the batch. The runtime compares them before applying effects. On mismatch, the app should recover by reloading or navigating to a full page owned by the current server revision. req: abi/003 req: abi/004
Recommended app behavior:
fingerprint mismatch
-> record metric: hemx.fingerprint_mismatch
-> show a short-lived "Updating…" notice if possible
-> perform full page reload/navigation
Never ignore a mismatch to preserve a partial update. Resource ids are stable within a build and best-effort across compatible symbol paths, but they are not a persistence or cross-version addressing contract. req: abi/005
Semver policy for v1 apps
For v1, document changes in three buckets:
- Beginner API: generated helpers,
#[hemx::app],#[hemx::component],#[hemx::handler],#[hemx::form],hemx::page, tupleIntoEffect, andResult<impl IntoEffect, E>mapping. Breaking changes require a major version or an explicit migration note. - Wire/runtime ABI: EffectBatch schema, runtime ABI version, and fingerprint inputs. Incompatible changes must bump ABI versions and fail closed at runtime.
- Advanced escape hatches: raw effects, manual registries, low-level ids, raw render/target construction, runtime hooks, SSE internals, and island internals. These may evolve faster, but must remain named as advanced and must not leak into beginner docs. req: public_api/002 req: public_api/005
Upgrade notes should explain what changed, whether generated code must be
regenerated, whether /hemx.js must be rolled with the server, and what fallback
users see if an old page talks to a new server. Use docs/versioning.md as the
release-policy checklist.
Deployment checklist
Before promoting a release:
cargo run -p hemx-xtask -- test
cargo check --workspace
redgate health --strict
Then verify deployment-specific behavior:
- page HTML includes the intended
/hemx.js, CSS, and island asset release paths - interaction endpoints return the same build fingerprint as the initial root
- SSE/polling endpoints stream batches from the same revision
- a stale page talking to the new server reloads or navigates instead of applying a partial update
- fingerprint mismatch metrics/logs are visible to the platform team
- rollback serves a self-consistent old server/runtime pair
These checks belong in the app/platform pipeline. hemx should provide the small runtime handshake and clear failure boundary, not a deployment platform.
Observability hooks
Track deployment compatibility as app/platform metrics:
hemx.fingerprint_mismatchhemx.effect_decode_errorhemx.missing_targethemx.sse_reconnecthemx.full_reload_fallback
The metric names are suggestions, not core API. The important behavior is that a team can see mismatches, pause a rollout, and recover with a full page response without weakening the runtime's tiny, selectorless contract. req: failure/001 req: failure/005