Current section
Files
Jump to
Current section
Files
documentation/upgrading.md
# Upgrading
Version-to-version migration notes. `ash_onetime` follows semantic versioning: breaking DSL
or contract changes bump the minor version (the library is pre-1.0), and each breaking
change lands here with the exact edit to make.
The published package is [v0.4.0](https://hex.pm/packages/ash_onetime). Set your dependency
to the minor range to pick up patches automatically and review this page on each minor bump:
```elixir
{:ash_onetime, "~> 0.4"}
```
## v0.4.0 — hardening, ops-readiness, and enhancement
v0.4.0 is a hardening + ops-readiness + enhancement release. No breaking contract change for
existing consumers — the minor bump carries new public observability surfaces (the telemetry
default attach helper, the `:worker_timeout` result class, the ETS cache reference adapter)
and the span-events-out-of-scope decision (H22). Existing consumers are unchanged; all new
surfaces are opt-in.
- **External-recovery adversarial-absence proof + normative doc (H10):** a test proving the
re-execution invariant against a lying-`:absent` adapter, and a normative section in
`documentation/external-effects.md` stating the adapter MUST prove absence and the peer
MUST enforce idempotency by operation key. No runtime guard — the trust is inherent to the
design (ADR-0001).
- **Worker timeout distinguished from disconnect (H11):** the committed-claim worker's 30s
timeout now surfaces as a distinct `:worker_timeout` result_class on
`[:ash_onetime, :store_uncertainty]`, separate from `:disconnected` and `:unknown`. All
three fail closed; the distinction is operational triage (pool/lock contention vs network
partition).
- **Oban worker backoff + discard alert (H20, ADR-0005):** the three maintenance workers
(Cleanup, Partition, Reap) declare a bounded, jittered `backoff/1` (30–120 s) instead of
the default exponential, so transient failures retry within the retention window. A
documented discard-alert SQL names the operational signal for a stranded partition roll.
- **Telemetry default attach handler (H21):** `AshOnetime.Telemetry.attach/0` — an opt-in
helper that routes the closed event surface into a downstream `:metric` stream for a
consumer's own aggregator. No `telemetry_metrics` dependency.
- **Telemetry span structure (H22):** documented that the library emits point events only
(never span events), with the reason (`span/3` cannot preserve the value-free invariant)
and a recommended consumer-applied `:telemetry.span/3` wrapper.
- **Operations runbook (H23):** three named procedures (backlog-stuck,
partition-discard-detected, pool-saturated) with exact SQL/telemetry queries.
- **ETS cache reference adapter (H30):** `AshOnetime.Cache.Ets` — bounded, TTL-aware,
supervised, no third-party dependency. Makes the cache-degradation path reachable.
- **Admission unit tests (H31), key_source/claim property tests (H32):** direct test
coverage for the pure decision functions and the security-boundary invariants.
- **Runtime security-surface docs (H33):** `@doc` on `token.ex`, `key_source.ex`,
`fingerprint.ex`, `telemetry.ex` public functions.
- **CI-matrix-asserted compatibility documented (H34):** CONTRIBUTING names the CI matrix as
the guard against transitive semantic drift (not the dep bounds).
## Ash floor raised to 3.31.1 (v0.3.0, security-driven)
v0.3.0 tightens the Ash requirement from `>= 3.29.3 and < 4.0.0` to
`>= 3.31.1 and < 4.0.0`. Two Ash advisories published during the v0.2.0 window affect Ash
below 3.31.1, both patched in 3.31.1: EEF-CVE-2026-70395 (predicate injection in
`manage_relationship` belongs_to lookup disclosing secret lookup keys) and
EEF-CVE-2026-69659 (memory exhaustion via unbounded keyset-cursor deserialization). A security
library must not admit a vulnerable floor. **Bump Ash to ≥ 3.31.1**, then bump `ash_onetime`:
```elixir
{:ash_onetime, "~> 0.3"}
```
See ADR-0004 (Security-driven Ash floor). The CI compatibility matrix narrows from
`[3.29.3, 3.30.1, latest]` to `[3.31.1, latest]`.
## Forward response-partition maintenance (apply once to existing installs)
Two data-layer fixes shipped for the response store: the `response_partition` index (cleanup's
partition-empty check was a full scan) and forward monthly partition creation (payloads past
the install window routed to `_default` and were never dropped, silently defeating bounded
retention). Greenfield installs on the current version get both automatically from the install
migration. **Existing installs should generate and run the forward migration once:**
```sh
mix ash_onetime.gen.roll_forward --repo MyApp.Repo --months 18
mix ecto.migrate
```
It adds the index, back-fills the elapsed+forward partitions, and drains past-retention
payloads stranded in `_default`. After that, schedule `mix ash_onetime.roll_partitions` (or
`AshOnetime.Oban.PartitionWorker`) on a cadence ahead of your retention horizon — see
[Operations](operations.md#forward-response-partitions). This is additive (no DSL/contract
change) and does not require a dependency version bump.
## DPoP replay fence (`commit: :independent`)
Additive: a new opt-in `commit:` option on `:one_time_nonce` protections (default
`:with_action`). No migration, no schema change, no new error code. Existing nonce consumers are
unchanged. Adopt by adding `commit :independent` to a nonce `protect` block whose proof should
survive action-body failure (RFC 9449 §11.1). See ADR-0003 (Independent-commit nonce) and the
[operational characteristics](operations.md#dpop-replay-fence-operational-characteristics) for
pool-sizing notes. Declaring `commit:` on `:idempotency` is now a compile error (idempotency
commits with its effect).
## Single `limits` surface (shipped in v0.1.0)
The dual `limits` surface collapsed to one `protect`-level vocabulary in v0.1.0. Response-size
limits are declared on the `protect` block, not the `response` entity:
```elixir
protect :charge do
strategy :idempotency
# ...
response MyApp.ChargeCodec, fields: [:id, :status], classify: MyApp.ChargeClassifier
limits max_response_bytes: 8_388_608
end
```
The `protect limits:` option accepts the full 11-key union — the key/verification/cache
keys (`max_key_bytes`, `max_token_bytes`, `max_scope_components`, `max_fingerprint_bytes`,
`verifier_timeout_ms`, `max_cache_entry_bytes`) and the response-payload keys
(`max_response_bytes`, `max_response_depth`, `max_response_nodes`, `max_response_entries`,
`max_response_scalar_bytes`). All keys are validated at compile time. If you are upgrading
from a pre-v0.1.0 snapshot, move any `response ..., limits: [max_response_*: ...]` onto
`protect ..., limits: [...]`.
This change is additive in coverage (no limit is lost) and removes a redundant configuration
surface where two places could spell overlapping limits.
## Between releases
Non-breaking additions (new optional integrations, new introspection helpers, new guides)
ship in patch or minor releases and need no migration. Check the
[CHANGELOG](https://github.com/baselabs/ash_onetime/blob/main/CHANGELOG.md) for the full list
per release.
If you depend on a private (non-documented) module or function, it may change in any release
— the public contract is the documented DSL, the modules in the API reference, and the
behaviours (`AshOnetime.Codec`, the `classify/2` contract on `AshOnetime.ResponseClassifier`,
verification callbacks returning `AshOnetime.Verified`).