diff --git a/README.md b/README.md
index bac123f..188b406 100644
--- a/README.md
+++ b/README.md
@@ -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
diff --git a/docs/recipes/deploy-versioning.md b/docs/recipes/deploy-versioning.md
new file mode 100644
index 0000000..bfe3ec4
--- /dev/null
+++ b/docs/recipes/deploy-versioning.md
@@ -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
+
+```
+
+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` 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
diff --git a/examples/saas/README.md b/examples/saas/README.md
index 30b71a5..b76cddb 100644
--- a/examples/saas/README.md
+++ b/examples/saas/README.md
@@ -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: