Repository navigation
1.3.0
Historical release record for the existing v1.3.0 tag, dated October 5, 2026 (commit a9f98095d632a28aa4861a5fc4154a24451367c0). This GitHub record was reconciled on October 6, 2026; the original tag and package are unchanged. The notes below include the subsequent correction to the delivery guarantee. See current releases for upgrade guidance.
1.3.0 — October 5, 2026
Additive options for domain naming and lifecycle, a compile
fix for generated (non-writable) primary keys, typed subscription
matching, loud documentation of the policy/id/adapter obligations, and a
Linux CI leg exercising an AshPostgres uuid_v7 consumer shape.
Added (additive options — non-breaking per ADR-0010)
payload_attributeon theoutbound_deliverysection. The exact
payload-bytes column's name is configurable (default:payload):
rename it when the consumer's domain reservespayloadfor its own
sole payload store. The injected column, the:dispatchaccept list,
and every signing/sending read follow the configured name; a name
colliding with another injected field fails closed at compile.prune_action :noneon theoutbound_deliverysection. Opt out
of the retentiondestroy :pruneaction's injection for append-only
audit ledgers (no destroy action exists on the resource at all);
AshHooks.Delivery.prune/2then fails loud with a named error —
deletion is the consumer's own surface. Default:destroyis
unchanged.status_attribute/enabled_values/disabled_valueon the new
endpointsection. Map the durable enable/disable onto the
consumer's OWN switch (e.g. anactiveboolean):statusis then
not injected — one switch, so the consumer's off switch and the
package's can never silently disagree. The dispatcher's skip and the
send path's dead-letter both route throughAshHooks.Endpoint.enabled?/1;
the 410 auto-disable flips the mapped attribute. The mapped attribute
must be consumer-declared (fail-closed at compile).- A Linux CI leg with a Postgres service exercising the transformers
against an AshPostgres, uuid_v7-keyed, sole-store-payload consumer
shape (postgresjob; the suite is env-gated byASH_HOOKS_POSTGRES=1
and excluded from ordinary dev runs).ash_postgresis a dev/test-only
dependency — never ships, never constrains consumers.
Fixed
:dispatch/:ingestno longer accept:idon resources whose
primary key is a non-writable:id(e.g.uuid_v7_primary_key):
Ash'sValidateAcceptraised at compile on exactly that consumer
shape, making the extensions unadoptable without replacing the PK.
The accept lists carry:idonly when the PK is a writable:id
(the package-injected default — byte-identical classification); the
transformer's decision is persisted and the runtimes read it back, so
the compiled action and the classification path can never disagree
(composite and non-:idprimary keys included). For the non-writable
shape, created/duplicate classification pre-reads the unique identity
instead: exact in every sequential case (provider redeliveries,
producer re-fires) and wherever the primary read can see the existing
row (a base-filtered read that hides it classifies:created). Under
a misclassification the storage upsert preserves one row.
A double send stays impossible through theCorrection in 2.0.0: the 1.3 outboundmark_sendingCAS and
inbound claim fence.
state transition did not fence every late result or expired owner.
Version 2.0 adds attempt tokens, leases, and separate enqueue claims;
network sends remain at-least-once and require receiver deduplication.
In 1.3 the enqueue seam was the configured
enqueuer's own fence — the default Oban enqueuer's
endpoint_id+event_uuid job uniqueness, or the consumer's own dedup
for a customenqueue:. ADR-0010 note: the injected action accept
lists are covered surface — this is a behavior correction (the
previous accept list required a writable PK that consumers with
generated PKs cannot provide), not a removal; no working consumer can
regress (the shape did not compile).- Covered-surface tightening alongside the H4 mapping: the send path
dead-letters an endpoint whose status is anything other than
:enabled(previously only an exact:disableddead-lettered, so a
consumer-redeclaredstatuswith extra values delivered). The
dispatcher's skip and the send path now route through the ONE
AshHooks.Endpoint.enabled?/1check — fail-closed by design. AshHooks.Subscription.matches?/2normalizesevent_typesentries
withto_string/1. An atom-typed register ({:array, :atom}— a
closed enum) previously matched NOTHING: zero deliveries, no error.
Atom and string entries (and the"*"/:"*"wildcard in either
representation) now match identically. Consumers redeclaring
event_typeskeep their own constraints — the injected["*"]
default is not forced on them.
Changed
- CI is Linux-only; the macOS and Windows build legs are removed.
Tri-OS stays a developer-portability requirement (clone, set up, and
test must work on every OS) — it was never meant to be a CI topology,
and the README no longer claims dedicated per-OS legs. Also hygiene:
the workflow's pinned GitHub Actions (checkout, cache, upload-artifact)
moved off the Node 20 runtime to their current releases. No library
code changed. - The DSL cheat-sheet drift gate is no longer vacuous. The package
leg'smix spark.cheat_sheets --checkran without an extension list
and checked nothing; it now passes the five extensions explicitly, so
a staledocumentation/dsls/actually fails the build (the sheets in
this release are regenerated under that invocation). - Dev/test
mintbumped 1.10.1 → 1.11.0 (EEF-CVE-2026-94194 /
-91043 / -92103, via the dev-only igniter→req→finch tree; never ships
in the package).ash_postgres2.13.1 is added dev/test-only for the
Postgres leg above.
Docs
- Policy obligation at the extension site (
OutboundDelivery,
Endpointmoduledocs): the injected write actions are named and the
consumer-owned policy obligation is stated loudly — the inbound half's
"you write your own" posture, mirrored outbound. The seven delivery
actions plus the endpoint's:disable. - Deterministic
Event.idguidance (Event,Dispatcher
moduledocs): derive the id from the artifact's stable id — a generated
id turns every producer re-fire into a duplicate POST per sweep. - The adapter-author contract (
AshHooks.Httpmoduledoc): headers
must be a LIST of{name, value}tuples (a map silently loses
Retry-After), and the resolve-once/connect-pinned obligation that
closes the DNS-rebinding TOCTOU is the adapter's, with the driver's
send-time check as the residual. retry_after_cap_secondssemantics (Workermoduledoc): the
86,400 default is receiver-held-state budget — up to 24 hours per
attempt on one header; lower it for an exhaust-fast posture.- The 410 auto-disable posture (
Deliverymoduledoc): an
unattributed system bulk write by design (no actor in a versioned
register), no built-in tenant-facing signal — a receiver that answers
410 once goes dark silently unless the app wires the
[:ash_hooks, :delivery, :disable]telemetry event or accepts the
posture in writing.