All notable changes to this project are documented in this file.
The format is based on Keep a Changelog.
Unreleased
1.0.3 — 2026-08-23
Docs-and-tests release (no functional changes; 1.0.2's gate and fixes unchanged):
Changed
- The send-failure contract tests dropped their 512MB bodies for 16MB with the peer's receive window pinned to 1KB — the same in-flight guarantee at 1/32nd the allocation, closing the resource-pressure residual from the 1.0.2 ship report. CI uploads the cover HTML when the coverage gate reds (triage data, not guesswork).
Docs
- UPGRADING carries the 1.0.2 behavior notes (the
:httpcliteral-IP HTTPS refusal, the:http_optsthreading). - The
:httpcandBoundedmoduledocs document the literal-IP posture and the:cacertstrust-store seam; ADR-0009 records the seam decision (wayfinder D3) in Consequences.
1.0.2 — 2026-08-22
The 100% coverage release: the maintainership directive (2026-08-22)
superseding 1.0.1's print-only posture, worked through the wayfinder
decisions (#20-#22) — line coverage of lib/ now GATES in CI, and the
release carries the surfaces that build required.
Added
Target.ssl_options/2and both HTTP adapters accept an injectable:cacertstrust bundle ([cacerts: der_list]in the adapter opts — on the Oban path,use AshHooks.Worker, http_opts: ...): pin a private CA for private-CA endpoints. Default UNCHANGED (the OTP CA store). The worker'shttp_optstakes compile-time literals, or an{m, f, a}resolved per-perform for computed bundles.- TLS success paths are covered by REAL CA-verified loopback sessions against committed local fixtures, including the literal-IP iPAddress-SAN floor and its fail-closed mismatch.
Fixed
AshHooks.Http.CertSan.ip_san_match?/2fails closed (returnsfalse) instead of CRASHING its caller when a certificate's SAN extension carries non-DER bytes: asn1's decode EXITS on invalid tags, which the previous error-only rescue could not catch.AshHooks.Http.Httpc(the alternate adapter) refuses literal-IP HTTPS destinations with{:error, :ip_literal_https_needs_bounded}: it never holds the socket, so the iPAddress-SAN floor cannot run there and chain-validation alone would let any chain-valid certificate authenticate the endpoint IP. UseAshHooks.Http.Bounded(the default) for literal-IP HTTPS endpoints. Cross-vendor-reviewed (both peers), pre-existing hazard class.use AshHooks.Workerthreads:http_optsinto the delivery config (the option was previously accepted and silently dropped).
Changed
mix test --coverGATES at 100% line coverage oflib/'s runtime-reachable lines (CI fails on a miss). The ignore list carries exactly the:cover-invisible classes: Spark's generated DSL sections, test-support modules, and four compile-window module-body lines. 31 provably-dead defensive arms were DELETED with proofs (commit d6f3788) rather than exempted; the shipped surface behaves identically.
1.0.1 — 2026-08-22
Docs-and-tests release (no functional changes). The 1.0.0 ship report's three residual postures are probed and pinned instead of documented:
Added
- The fenced ledger's exactly-once claim is now pinned by a TRUE
concurrent race test (8 simultaneous same-key ingests on a
3-connection sqlite pool: exactly one
:created, one row) — and the same race without the storage unique index proves the failure mode: sqlite fails CLOSED with a loud storage error, zero rows. TheInboundDeliverymoduledoc cites both. - The
:httpcadapter's giant-non-2xx window is now MEASURED (dribble probe: ~4.65MB transient for a 4MB error body against a 16-byte bound) with a committed containment test pinning the final cut; ADR-0009 carries the numbers.
Fixed
- The missing-index hazard is documented accurately: on storage layers
with native conflict support (sqlite, postgres) a missing unique
index errors the ingest loudly — the previously documented
"two rows, both
:created" shape applies to degraded-upsert layers. The as-built floor is stronger than the docs claimed.
1.0.0 — 2026-08-22
1.0.0 is the semantic-versioning baseline (ADR-0010): no public API removals or renames from 0.2.x. Three behavior corrections below; migration notes in UPGRADING.md.
Fixed
- Truncated chunked responses no longer classify as success. The
default HTTP adapter returned a partial body as
{:ok, ...}when a chunked response was cut by early close — a 2xx on partial bytes could mark a delivery:succeeded. Chunked now returns{:error, :truncated_body}exactly like the Content-Length framing, and the driver retries. - The literal-IP https certificate check actually works now. The
iPAddress-SAN matcher was unreachable since birth (found by the new
dialyzer gate): every literal-IP https endpoint was rejected
fail-closed with
:cert_ip_mismatch. Extracted to the fixture-testedAshHooks.Http.CertSan(ADR-0009). The IPv6 direction initially matched nothing (a cross-vendor review finding, fixed before release) — both address families now verify against fixture certificates. - A hostile chunked response can no longer balloon worker memory.
The chunk consumer accumulated the ENTIRE attacker-declared chunk
before trimming to the body bound — an 8MB declaration held ~16.8MB
in the worker against a 16-byte bound (cross-vendor security review,
executable probe). Consumption is now phase-bounded: keep at most the
allowance, discard the excess slice-wise (one receive slice held at a
time), and a chunk terminator that is not CRLF is now
{:error, :malformed_chunked}instead of being silently consumed (a malformed 2xx can no longer classify as succeeded). Run-on chunk-size lines without a terminator are refused past a sane bound instead of being buffered. AshHooks.Delivery.prune/2returns{:error, error}when the resource lacksinserted_at— the same contract asAshHooks.Ingress.prune/2its@specalways promised (previously raisedArgumentError).
Added
- Semver and support policy (ADR-0010): the covered public surface is named, deprecations run two minors, and the minimum supported versions are CI-tested (Elixir ~> 1.17 / OTP 27+ / Ash ~> 3.0 / Oban ~> 2.20 optional). The previously claimed Elixir 1.15 floor was never buildable with current Ash and is corrected here — 1.0.0 is the first release whose floor is actually tested.
- SECURITY.md with a private disclosure channel (GitHub private vulnerability reporting, enabled), scope, and known posture notes; CONTRIBUTING.md and UPGRADING.md — all three ship in the tarball and render on hexdocs.
- Dialyzer gate on the public API (local + CI) — first run caught the dead IP-SAN matcher above.
- CI: a floor leg resolving dependencies at the declared minimums (it disproved the old 1.15 claim on its first run), a package leg checking the hex tarball ships every documentation file and that the DSL cheat sheets match the DSL, and an advisory coverage report.
@specon the HTTP behaviour'srequest/5(both adapters),AshHooks.dispatch/4, the resource extensions'statuses/0/signing_modes/0(now documented), and theAshHooks.Http.Targethelpers.- A dedicated extension-shape test suite for
AshHooks.OutboundDelivery, pinning the injected attributes, theunique_deliveryidentity, and the:dispatchno-touch-upsert contract.
Changed
- Read posture documented honestly (ADR-0005 amendment): read access to the ledger/delivery resources is consumer-governed — the package injects no read policies. README → Security carries the deny-by-default policy recipe; the resource moduledocs now carry READ-EXPOSURE warnings. (No code change: reads were always consumer-governed; the docs previously implied otherwise.)
- Install guidance is
{:ash_hooks, "~> 1.0"}; the README (previously pinned~> 0.1.0, one feature-release behind) and tutorial both corrected, with the install-constraint check added to the release checklist.
0.2.2 — 2026-08-22
Fixed
- usage-rules.md now ships in the hex tarball (the Ash AI-assistant convention reads it from the package) and renders on hexdocs. It was previously GitHub-only.
0.2.1 — 2026-08-22
Added
- A runnable get-started Livebook (guided tour: inbound verify/dedup, a live local delivery, telemetry, retention) — CI-verified headless on every push. No functional changes.
0.2.0 — 2026-08-22
Added
- Retention hooks (ADR-0005 floor completed):
AshHooks.Ingress.prune/2andAshHooks.Delivery.prune/2(terminal rows older than a cutoff, keyed on the resource'stimestamps(); non-terminal rows never deleted), andAshHooks.Ingress.redact_payload/4(payload field-redaction under the claim fence; the original-bytes digest is preserved). Deleting a terminal row re-opens its dedup identity — set TTLs beyond replay/re-emission horizons.
0.1.1 — 2026-08-22
Changed
- README rewritten as a package front door (no functional changes): audience/register pass; internal build references removed.
0.1.0 — 2026-08-22
Added
- Inbound machine:
AshHooks+AshHooks.InboundDeliveryextensions,AshHooks.Ingress(verify-before-trust, fenced unique-ingest dedup ledger, lease-based claim, crash-window re-drive), provider behaviour with ComplyCube/HubSpot v3 references and a test provider. - Outbound machine:
AshHooks.dispatch/4fanout with per-endpoint isolation and enqueue-repair CAS;AshHooks.OutboundDelivery,AshHooks.Subscription,AshHooks.Endpointextensions. - Delivery runtime (
AshHooks.Delivery) + host-injecteduse AshHooks.Worker: Standard-Webhooks signing (standard/dual/legacy envelopes), row-owned retry policy (Retry-After, jittered backoff, dead-letter ceiling), 410 durable disable, redirect refusal, send-time SSRF re-check. AshHooks.Httpadapter behaviour with a memory-bounded native HTTP/1.1 default (AshHooks.Http.Bounded— every read capped under all framings) and an OTP:httpcalternative.- Response-snippet redaction floor (ADR-0005 amendment): no body bytes
by default (fixed-grammar status + allowlisted content-type summary);
per-call
snippet_captureopt-in under the in-package redaction floor (NFKC homoglyph folding, bounded-fixpoint decode chain, separator-tolerant markers, ≥16-char union-alphabet entropy rule); fail-closed consumersnippet_redactorcallback;[captured]marking. - Telemetry (ADR-0005 floor: ids/integers/fixed atoms/classified
reasons only — never secrets or bodies): ingress verify/dedup/claim,
dispatch enqueue_failed, delivery attempt/result/backoff/dead_letter/
disable;
AshHooks.Telemetry.fingerprint/1. - Igniter installer, DSL cheat sheets, get-started tutorial, usage-rules.md.
Security
- Package floors shipped in-code (ADR-0005): secrets as sources only (literals rejected at parse), default-deny machine-written ledger fields, SSRF guard at registration and send, NO headers stored on either ledger at all, no response-body persistence by default, classified-only error strings.