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. Set your dependency to the minor range to pick up patches automatically and review this page on each minor bump:
{: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-
:absentadapter, and a normative section indocumentation/external-effects.mdstating 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_timeoutresult_class on[:ash_onetime, :store_uncertainty], separate from:disconnectedand: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:metricstream for a consumer's own aggregator. Notelemetry_metricsdependency. - Telemetry span structure (H22): documented that the library emits point events only
(never span events), with the reason (
span/3cannot preserve the value-free invariant) and a recommended consumer-applied:telemetry.span/3wrapper. - 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):
@docontoken.ex,key_source.ex,fingerprint.ex,telemetry.expublic 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:
{: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:
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. 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 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:
protect :charge do
strategy :idempotency
# ...
response MyApp.ChargeCodec, fields: [:id, :status], classify: MyApp.ChargeClassifier
limits max_response_bytes: 8_388_608
endThe 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 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).