docs(recipes): add deploy versioning boundary

Document deployment and version compatibility for hemx apps: release unit, runtime asset caching, rolling deploy policy, fingerprint mismatch behavior, semver buckets, upgrade notes, and deployment checks without adding platform code to core.

req: abi/001

req: abi/002

req: abi/003

req: abi/004

req: abi/005

req: runtime/004

req: failure/005
This commit is contained in:
slhx agent
2026-06-05 09:32:25 +02:00
parent a7012b94c2
commit bf6add03ef
3 changed files with 147 additions and 3 deletions
+2 -2
View File
@@ -85,7 +85,7 @@ and integrate at explicit boundaries. req: laws/002 req: auth/001
fields and credentials semantics. See `docs/recipes/auth-session-csrf.md`. req: auth/004 req: auth/005
- **Observability, feature flags, killswitches, deploy:** use explicit platform
integrations around handlers, routes, and runtime assets. Core hemx must not
vendor providers or add framework-specific magic.
vendor providers or add framework-specific magic. See `docs/recipes/deploy-versioning.md`.
- **PWA/offline/sync:** optional adapters may reuse generated targets/effects,
but core hemx must not gain a mandatory client state graph or local app
runtime. req: canonical_authoring/008
@@ -104,7 +104,7 @@ The generated API, symbols, effect wire schema, and JavaScript runtime carry
schema/ABI versions. Deploy a matching server, generated output, and runtime
asset together. Build fingerprints are derived from the generated surface and ABI
parts; the runtime refuses incompatible effect responses and integrations should
fall back to a full page reload when possible. req: abi/001 req: abi/002 req: failure/005
fall back to a full page reload when possible. See `docs/recipes/deploy-versioning.md`. req: abi/001 req: abi/002 req: failure/005
Before a v1 release, the semver policy and upgrade notes should explicitly state
which surfaces are stable: beginner generated helpers and handler shapes; the
+144
View File
@@ -0,0 +1,144 @@
# 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.rs` and symbols produced during that build
- the `hemx-js` runtime 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:
```rust
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:
```html
<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.js` come 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:
```text
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`, tuple `IntoEffect`, and
`Result<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.
## Deployment checklist
Before promoting a release:
```sh
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_mismatch`
- `hemx.effect_decode_error`
- `hemx.missing_target`
- `hemx.sse_reconnect`
- `hemx.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
+1 -1
View File
@@ -12,7 +12,7 @@ What it proves today:
- page shell with plain CSS and one explicit metrics island script
- compile-time surface generation plus interaction tests
For provider-explicit boundaries, see `../../docs/recipes/sqlx-persistence.md` and `../../docs/recipes/auth-session-csrf.md`.
For provider-explicit boundaries, see `../../docs/recipes/sqlx-persistence.md`, `../../docs/recipes/auth-session-csrf.md`, and `../../docs/recipes/deploy-versioning.md`.
What it deliberately does not claim yet: