Skip to content

Releases: macula-io/macula

v10.25.0

Choose a tag to compare

@github-actions github-actions released this 14 Sep 20:42
chore: release v10.25.0

Moves the unreleased changes under 10.25.0 and sets the app version.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011RWqkFb5xWX63dCJttFouW

v10.24.0

Choose a tag to compare

@github-actions github-actions released this 10 Sep 21:57
chore: release v10.24.0

Moves the unreleased changes under 10.24.0 and sets the app version.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011pNC2B1XzrjGoZtRygZRCp

v10.23.0

Choose a tag to compare

@github-actions github-actions released this 10 Sep 18:44
chore: release v10.23.0

Moves the unreleased changes under 10.23.0 and sets the app version.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011pNC2B1XzrjGoZtRygZRCp

v10.22.0

Choose a tag to compare

@github-actions github-actions released this 07 Sep 06:14
chore: release v10.22.0

Covers the six commits since v10.21.0 (529d288, 69fd639, ca15537,
76747bc, deaf01b, 4a0b5ce). CHANGELOG documents the two previously
undocumented feature/fix commits (76747bc, deaf01b); 69fd639 + its
revert ca15537 verified to net to a true no-op (git diff v10.21.0..ca15537
touches only the unrelated 529d288 script, confirmed by inspection) so
neither gets its own entry.

Minor bump (10.21.0 -> 10.22.0) per this repo's own version table: deaf01b
adds new backward-compatible functionality (the draining notification +
dedicated_streams_idle cast), which is MINOR regardless of the two
accompanying Fixed entries.

Checklist:
- rebar3 eunit --module=macula_station_link_tests: 58/58 pass.
- rebar3 eunit (full suite), twice: both runs hit the documented
  pre-existing flake (macula_full_eunit_suite_flaky_under_load) on
  macula_station_link_tests:disconnect_notifies_subscribers_test_ (a
  setelement/tuple-index race, unrelated to this change) -- passes
  cleanly in isolation both times, confirmed not a regression.
- rebar3 dialyzer: clean, no warnings.
- rebar3 ex_doc: builds clean; only pre-existing CHANGELOG
  cross-reference warnings from older, unrelated entries remain (none
  introduced by this release).

Not run: rebar3 hex publish -- Raf's call to fire, per standing policy.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FhMehfurzfWzTW4bTLzrPs

v10.21.0

Choose a tag to compare

@github-actions github-actions released this 05 Sep 22:10
chore: release v10.21.0

Covers everything currently on main: the realm_member_required auth
policy (4a4a928, a6907c3) and the ucan_nif never-expiring-token fix
(6844d8f). Minor bump -- realm_member_required is a genuine new
feature.

CHANGELOG leads with the ucan_nif security fix (silent exp-omission on
any opts JSON encoding failure, affecting every UCAN this library has
ever issued) stated plainly and prominently rather than buried under
the new feature, then realm_member_required itself with the device-
tier-bypass finding folded into its own history (found and fixed
within the same work, not shipped as a separate line), then the
advertise/5 fail-fast policy-shape guard as its own bullet.

Also synced docs/guides/shared/AUTHORIZATION_GUIDE.md, which still
listed only open/ucan_required in both its overview callout and its
worked-example section -- added realm_member_required to both, plus a
worked example matching the existing ucan_required one.

Pre-publish checklist run: rebar3 eunit (2131 passed, 1 pre-existing
flaky failure -- disconnect_notifies_subscribers_test_, confirmed
present on the unmodified pre-realm_member_required baseline earlier
today, unrelated to anything in this release), rebar3 dialyzer (clean),
rebar3 ex_doc (exit 0; confirmed zero new cross-reference warnings
introduced by this changeset specifically -- the ~50 existing warnings
are pre-existing stale backtick references inside old CHANGELOG
entries, unchanged by this commit).

Not published to hex.pm -- that's Raf's own call per this repo's
release process.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QFHPxb2H9GANFJFMw1yB6F

v10.20.3

Choose a tag to compare

@github-actions github-actions released this 05 Sep 13:00
Fix silent stale-NIF caching in build-nifs.sh and fetch-nif.sh

Both scripts skipped rebuilding purely on [ -f "$NIF_FILE" ] -- whether
the artifact existed at all, never whether it was still current. This
is exactly what masked several of today's earlier verification runs:
editing deterministic.rs and running `rebar3 compile`/`eunit` kept
silently loading an Aug-27 .so through a full day of "clean" results,
until an unrelated regression test's crash (from decoding against
genuinely stale code) surfaced it.

Fixed by also checking, via `find -newer` (POSIX, portable across GNU
and BSD find), whether any .rs/Cargo.toml/Cargo.lock file under the
crate is newer than the built artifact -- rebuild if so.

fetch-nif.sh had a second, related bug fixed in the same pass:
MACULA_FORCE_SOURCE_BUILD=1 did nothing at all whenever a cached
artifact already existed, because the bare existence check ran BEFORE
that env var was ever consulted -- the one thing that flag exists for
was silently defeated by a cache hit.

Verified RED then GREEN, not just read:
- Reproduced the original silent-staleness bug in isolation (an aged
  .so next to a newer source file, old check says skip).
- Confirmed the new check catches it (says rebuild).
- Confirmed it doesn't over-trigger once genuinely up to date, and
  that a Cargo.toml-only change (no .rs edit) is caught too.
- Confirmed the MACULA_FORCE_SOURCE_BUILD fix specifically: old logic
  exits before ever checking the env var, new logic proceeds past the
  skip as intended.
- Confirmed end-to-end against this repo's own build: touching
  deterministic.rs and running `rebar3 compile` now visibly logs
  "[macula_cbor_nif] Building NIF from source..." and produces a new
  .so, where before it silently did neither. Reran immediately after
  to confirm no rebuild loop (correctly skips once genuinely current).

Full rebar3 eunit (2099+, same 1 documented pre-existing flake) and
dialyzer (0 warnings) clean afterward.

Same defect (priv/build-nifs.sh only -- no fetch-nif.sh equivalent
there) found and fixed the same way in reckon-db-org/reckon-db.
Surveyed every other native-NIF-having repo in the workspace
(hecate-graph, hecate-embed, hecate-vector, hecate-rag): all four
already run `cargo build` unconditionally on every invocation, with no
existence-only skip -- not affected, left untouched.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WcgZMqfUYLdsS4JfKQzLSA

v10.20.2

Choose a tag to compare

@github-actions github-actions released this 05 Sep 11:25
Document a known sha256 per-chunk-verification gap in chunk_mcid/3

No behavior change. Found while auditing content-transfer code
adjacent to today's CBOR decoder work: macula_content_transfer:
verify_block_hash/2 verifies every fetched chunk with hardcoded
blake3, regardless of a manifest's hash_algorithm field, while
create/2 bakes whichever algorithm was requested into each chunk's
hash (and therefore its MCID). A manifest built with
hash_algorithm => sha256 produces chunk MCIDs that can never actually
verify on fetch as a result -- hash_algorithm today only affects the
manifest's own root-hash Merkle combination step in verify/2, not
per-chunk integrity. chunk_mcid/3's own Algorithm argument is already
unused (the chunk's hash was already computed at create time), which
is what led to tracing this.

Same shape confirmed independently in macula-io/macula-rust's
content.rs (block_mcid is likewise hardcoded to blake3). Checked
macula-io/macula-py's equivalent call too -- it already matches this
reference behavior correctly, not a bug there.

Recorded rather than fixed: this is an open design question (was
per-chunk sha256 verification ever meant to work, or is per-chunk
hashing deliberately always-blake3 with hash_algorithm scoped to
root-hash only) rather than a bug with an obvious fix, and nothing
exercises sha256 in practice today.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WcgZMqfUYLdsS4JfKQzLSA

v10.20.1

Choose a tag to compare

@github-actions github-actions released this 05 Sep 09:00
Fix 3 pre-auth vulnerabilities in the deterministic CBOR decoder

deterministic.rs's decode_one/decode_array/decode_map is the codec
every network-received frame decodes through before any signature
check (macula_frame.erl, macula_record.erl), running as a NIF
directly on a BEAM scheduler thread. Found across four rounds of
adversarial review of the same function:

1. Uncatchable process crash via stack overflow: plain recursive
   descent had no nesting-depth limit. A list-of-one-list-of-one-
   list... encodes extreme nesting in one byte per level; a ~10KB
   frame reliably segfaulted the whole BEAM VM, not just one
   connection. Fixed with MAX_NESTING_DEPTH = 128, adapted from a
   sibling Rust reimplementation of this codec already through 3
   rounds of its own adversarial review plus 1M fuzzed payloads.

2. O(depth x key size) blowup reintroduced by 10.20.0's O(n^2) dedup
   fix: re-encoding a key fresh via encode_value at every ancestor
   level, for a map whose key is itself a large nested structure,
   turned back into real seconds of pre-auth CPU on a frame still
   under the size cap. Fixed by threading a need_canon flag alongside
   depth and building each decoded value's own canonical bytes
   bottom-up (computed once, only ever concatenated/sorted by
   ancestors, never re-derived) -- bounding the cost to the same
   MAX_NESTING_DEPTH bound already enforced elsewhere. A 128-deep
   chain of single-entry maps wrapping a 4MB blob key now decodes in
   ~400ms; it took 8+ seconds and 400MB+ transient at just 100 levels
   before this fix.

3. Uncatchable VM abort on a NaN/Infinity map key: decode_major7's
   float32/float64 arms built an Erlang float term directly from the
   wire bits with no finiteness check, unlike the adjacent half-float
   arm which already rejects it. BEAM has no NaN/Infinity float
   representation; the resulting invalid term worked fine right up
   until this fix's own encode_value call inspected it as a map key,
   aborting the whole VM with an ERTS assertion failure
   (tag_val_def(), SIGABRT) -- not a catchable Erlang exception.
   Fixed by rejecting non-finite floats in both arms, matching the
   half-float arm's existing behavior.

All three verified empirically -- RED then GREEN against isolated
pre-fix builds, not reasoned about -- and adversarially reviewed (4
rounds total across this function today, the final round finding
nothing blocking). Depth limit: accepts exactly at 128, rejects at
129, rejects 100,000 without crashing. NaN/Infinity: 6 variants (f32/
f64, NaN/+Inf/-Inf, nested-in-a-key, bare top-level) all now return
clean catchable errors; finite floats unaffected. O(n^2): 20,000 keys
in 48ms. O(depth x size): 128-deep/4MB-blob-key in ~400ms.

6 new regression tests: nesting-depth boundary (accept 128/reject
129/reject 100,000-without-crashing), duplicate-key-slot preservation
(flat and, per the final review round, nested-map-key cases -- the
branch the differential fuzz suite structurally can't reach, since it
only ever feeds already-canonical bytes), both quadratic-complexity
guards, and 6 NaN/Infinity malformed-input vectors. Full differential
fuzz suite (3000 randomized terms against the pure-Erlang reference)
and full rebar3 eunit/dialyzer clean throughout.

Also exposed and worked around (not fixed here, tracked separately):
priv/*.so under native/*/priv/build-nifs.sh's "skip if already built"
caching can go stale relative to source changes with zero warning --
this silently invalidated several "clean rebar3 eunit" verification
runs earlier today until caught by comparing against isolated scratch
builds. Deleting priv/macula_cbor_nif.so before each real verification
pass is what made today's later results trustworthy.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WcgZMqfUYLdsS4JfKQzLSA

v10.20.0

Choose a tag to compare

@github-actions github-actions released this 05 Sep 03:57
Bump gproc/telemetry, fix phantom msgpack dep, unblock on macula-mdns

gproc 0.9.1 -> 1.3.0 (exact pin -> ~> 1.3): audited commit-by-commit
across all 4 tag jumps via the GitHub compare API. Purely additive
(new reg_remote/unreg_remote/set_value_remote/update_counter_remote
remote-registration API) plus internal non-exported refactors
(gproc_pool's setup_wait/do_wait/clear_wait timer-to-monotonic-time
rewrite) plus an OTP 29 warnings fix in the final jump. No exported
function removed or changed signature. This repo only calls
gproc:lookup_pids/1 (standalone script + test helper), untouched by
any of it.

telemetry constraint loosened 1.3.0 -> ~> 1.3 (was exact). Two-segment
~> still resolves to the real latest (1.4.2) in an isolated build.
Originally drafted as ~> 1.4, but adversarial review caught that this
would be unsatisfiable for any consumer resolving macula alongside
evoq/reckon_db/reckon_gater, which all pin telemetry exactly to
1.3.0 -- ~> 1.3 is compatible with both.

macula_mdns floor raised 0.1.0 -> ~> 0.1.1. 0.1.0 pinned gproc exactly
to 0.9.1, which made this repo's own gproc ~> 1.3 unsatisfiable for
any consumer resolving both packages together -- rebar3 masked this
locally by letting the root project's constraint win with just a
warning, but a real solver has no solution for gproc == 0.9.1 AND
gproc >= 1.3.0. macula_mdns 0.1.1 fixes that pin and, caught by the
same review, a real bug: mdns:vsn/0 referenced the pre-rename OTP
application name and crash-looped the built-in mDNS advertiser on
every distributed node. See macula-mdns's own CHANGELOG/commit for the
full trace. Release order: macula_mdns 0.1.1 must be hex-published
before or together with this release, or this package's own build
breaks for every fresh consumer in between.

Also fixes a phantom msgpack runtime dependency present on every hex
release since v3.0.0: rebar.config's deps list has had zero msgpack
entries since the CBOR migration, but a stale local (untracked)
rebar.lock kept resolving and republishing it anyway, because
`rebar3 hex publish` derives its declared requirements from the
resolved/locked graph rather than purely from rebar.config. The only
thing still requiring the real msgpack library was
test/dht_address_serialization_test.erl, a debugging-era test that
doesn't call any macula function -- it only exercises the third-party
msgpack library's own tuple-vs-binary packing behavior, disconnected
from the current CBOR wire path (the `msgpack` atom used elsewhere as
a macula_stream encoding tag is unrelated and needs no library).
Deleted that test; a clean rebuild now resolves zero msgpack anywhere.

Verified via a from-scratch _build wipe against the real macula_mdns
0.1.1 source (checkouts override, since it isn't hex-published yet):
compile clean, eunit 2084/2085 (1 failure is the documented
pre-existing macula_station_link_tests flake, confirmed clean 3/3 in
isolation), dialyzer 0 warnings, ex_doc 48 warnings (byte-identical to
the pre-change baseline via git stash). Both dependency-bump diffs
went through adversarial review before landing.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WcgZMqfUYLdsS4JfKQzLSA

v10.19.2

Choose a tag to compare

@github-actions github-actions released this 05 Sep 00:44
Stop the prod profile from stripping consumers' debug_info

rebar3 merges a fetched dependency's erl_opts across every profile
that dependency defines, not just whichever one the consumer actually
activated. The prod profile here (no_debug_info, deterministic) was
silently stripping debug_info from every consumer's copy of macula,
whether or not they ever selected prod themselves -- breaking
`rebar3 dialyzer` for anyone with macula in plt_extra_apps.

Confirmed with a controlled A/B rebuild: a bare git dependency fetch
of this exact repo, only variable changed was this profile's
erl_opts. With it: consumer's compile options for macula came back
missing debug_info, deterministic present despite nobody selecting
prod. Without it: debug_info present, deterministic gone, full
abstract_code confirmed via beam_lib:chunks.

Nothing in this repo's own CI/scripts invokes `rebar3 as prod` or a
relx release build (grepped, zero hits) -- the profile's relx block
is a local-dev-only convenience, left untouched. Only the erl_opts
override is removed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WcgZMqfUYLdsS4JfKQzLSA