Packages
macula
18.1.1
18.3.0
18.2.0
18.1.1
18.1.0
18.0.0
17.2.0
17.1.1
17.1.0
17.0.0
16.1.0
16.0.1
16.0.0
15.6.0
15.5.0
15.4.0
15.3.0
15.2.0
15.1.0
15.0.0
15.0.0-rc.3
15.0.0-rc.2
15.0.0-rc.1
14.11.0
14.10.0
14.9.0
14.8.2
14.8.1
14.8.0
14.7.0
14.6.0
14.5.0
14.4.0
14.3.0
14.2.1
14.2.0
14.1.0
14.0.0
13.6.0
13.5.0
13.4.0
13.3.0
13.2.2
13.2.1
13.2.0
13.1.0
13.0.1
13.0.0
12.12.0
12.11.1
12.11.0
12.10.0
12.9.1
12.9.0
12.8.0
12.7.0
12.6.0
12.5.1
12.5.0
12.4.0
12.3.0
12.2.1
12.2.0
12.1.0
12.0.0
11.5.0
11.4.0
11.3.1
11.3.0
11.2.0
11.1.0
11.0.0
10.25.0
10.24.0
10.23.0
10.22.0
10.21.0
10.20.3
10.20.2
10.20.0
10.19.2
10.19.1
10.19.0
10.18.0
10.17.0
10.16.0
10.15.0
10.14.5
10.14.4
10.14.2
10.14.1
10.14.0
10.13.2
10.13.1
10.11.0
10.10.2
10.10.1
10.10.0
10.9.1
10.9.0
10.8.0
10.7.0
10.5.8
10.5.7
10.5.6
10.5.5
10.5.4
10.5.3
10.5.2
10.5.1
10.5.0
10.4.0
10.2.0
10.1.1
10.1.0
10.0.2
10.0.1
10.0.0
9.13.8
9.8.2
9.8.1
9.8.0
9.5.0
9.4.0
9.3.1
9.3.0
9.2.0
9.1.1
9.1.0
9.0.0
8.7.0
8.6.0
8.5.0
8.4.1
8.4.0
8.3.0
8.2.0
8.1.0
8.0.2
8.0.1
8.0.0
7.1.0
7.0.0
6.0.0
5.2.2
5.2.1
5.2.0
5.1.0
5.0.0
4.8.0
4.7.1
4.7.0
4.6.0
4.5.0
4.4.10
4.4.9
4.4.8
4.4.7
4.4.6
4.4.5
4.4.4
4.4.3
4.4.2
4.4.1
4.4.0
4.3.1
4.3.0
4.2.9
4.2.8
4.2.7
4.2.6
4.2.5
4.2.4
4.2.3
4.2.2
4.2.1
4.2.0
4.1.1
4.1.0
4.0.0
3.16.0
3.15.3
3.15.2
3.15.1
3.14.0
3.13.0
3.12.1
3.12.0
3.11.1
3.11.0
3.10.3
3.10.2
3.10.1
3.9.0
3.8.0
3.7.0
3.5.0
3.4.0
3.3.0
3.2.0
3.1.0
3.0.0
2.1.1
2.1.0
2.0.0
1.5.2
1.5.1
1.4.30
1.4.29
1.4.28
1.4.27
1.4.26
1.4.25
1.4.24
1.4.23
1.4.22
1.4.21
1.4.20
1.4.19
1.4.18
1.4.17
1.4.16
1.4.15
1.4.14
1.4.13
1.4.11
1.4.10
1.4.9
1.4.8
1.4.7
1.4.6
1.4.5
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.3.1
1.3.0
1.2.0
1.1.0
1.0.10
1.0.9
1.0.8
1.0.7
1.0.6
1.0.5
1.0.4
1.0.3
1.0.2
1.0.1
1.0.0
0.48.6
0.48.5
0.48.4
0.48.3
0.48.2
0.48.1
0.48.0
0.47.1
0.47.0
0.46.3
0.46.1
0.46.0
0.45.3
0.45.2
0.45.1
0.45.0
0.44.2
0.44.1
0.44.0
0.43.3
0.43.2
0.43.1
0.43.0
0.42.9
0.42.8
0.42.7
0.42.6
0.42.5
0.42.4
0.42.3
0.42.2
0.42.1
0.42.0
0.41.1
0.41.0
0.40.1
0.40.0
0.39.9
0.39.8
0.39.7
0.39.6
0.39.5
0.39.4
0.39.3
0.39.2
0.39.1
0.39.0
0.38.8
0.38.7
0.38.6
0.38.5
0.38.4
0.38.3
0.38.2
0.38.1
0.38.0
0.37.7
0.37.6
0.37.5
0.37.4
0.37.3
0.37.2
0.37.1
0.37.0
0.36.6
0.36.5
0.36.4
0.36.3
0.36.2
0.36.1
0.36.0
0.35.4
0.35.3
0.35.2
0.35.1
0.35.0
0.34.1
0.34.0
0.33.1
0.33.0
0.32.5
0.32.4
0.32.3
0.32.2
0.32.1
0.32.0
0.31.9
0.31.8
0.31.7
0.31.6
0.31.5
0.31.4
0.31.3
0.31.2
0.31.1
0.31.0
0.30.10
0.30.9
0.30.8
0.30.7
0.30.6
0.30.5
0.30.4
0.30.3
0.30.2
0.30.1
0.30.0
0.29.0
0.28.3
0.28.2
0.28.1
0.28.0
0.27.1
0.27.0
0.26.1
0.26.0
0.25.6
0.25.5
0.25.4
0.25.3
0.25.2
0.25.1
0.25.0
0.24.6
0.24.5
0.24.4
0.24.3
0.24.2
0.24.1
0.24.0
0.23.3
0.23.2
0.23.1
0.23.0
0.22.12
0.22.11
0.22.10
0.22.9
0.22.8
0.22.7
0.22.6
0.22.5
0.22.4
0.22.3
0.22.2
0.22.1
0.22.0
0.21.7
0.21.6
0.21.5
0.21.4
0.21.2
0.21.1
0.21.0
0.20.25
0.20.24
0.20.23
0.20.22
0.20.21
0.20.20
0.20.19
0.20.18
0.20.17
0.20.16
0.20.15
0.20.14
0.20.13
0.20.12
0.20.11
0.20.10
0.20.9
0.20.8
0.20.7
0.20.6
0.20.5
0.20.3
0.20.2
0.20.1
0.20.0
0.19.2
0.19.1
0.19.0
0.18.1
0.18.0
0.17.4
0.17.3
0.17.2
0.17.1
0.17.0
0.16.6
0.16.5
0.16.4
0.16.3
0.16.2
0.16.1
0.16.0
0.15.1
0.15.0
0.14.3
0.14.2
0.14.1
0.14.0
0.12.6
0.12.5
0.12.3
0.11.3
0.10.2
0.10.1
0.10.0
0.9.2
0.9.1
0.9.0
0.8.25
0.8.24
0.8.23
0.8.22
0.8.21
0.8.20
0.8.19
0.8.18
0.8.17
0.8.16
0.8.15
0.8.14
0.8.13
0.8.12
0.8.11
0.8.10
0.8.9
0.8.8
0.8.7
0.8.6
0.8.5
0.8.4
0.8.3
0.8.2
0.8.1
0.8.0
0.7.30
0.7.29
0.7.28
0.7.27
0.7.26
0.7.25
0.7.24
0.7.23
0.7.22
0.7.21
0.7.20
0.7.19
0.7.18
0.7.17
0.7.16
0.7.15
0.7.14
0.7.13
0.7.12
0.7.11
0.7.10
0.7.9
0.7.8
0.7.7
0.7.6
0.7.5
0.7.4
0.7.3
0.7.2
0.7.1
0.7.0
0.6.7
0.6.6
0.6.5
0.6.4
0.6.3
0.6.2
0.6.1
0.6.0
0.5.0
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.3.4
0.3.3
0.3.2
0.3.1
Macula HTTP/3 Mesh SDK — connect, subscribe, publish, call, advertise
Current section
Files
Jump to
Current section
Files
CONTRIBUTING.md
# Contributing to Macula
Thank you for considering contributing to Macula! This document outlines how to contribute effectively.
## Code of Conduct
Please read and follow our [Code of Conduct](CODE_OF_CONDUCT.md).
## Getting Started
1. Fork the repository
2. Clone your fork: `git clone https://github.com/YOUR_USERNAME/macula.git`
3. Add upstream remote: `git remote add upstream https://github.com/macula-io/macula.git`
4. Create a feature branch: `git checkout -b feature/your-feature`
## Development Setup
```bash
# Install Erlang/OTP 26+
# Install rebar3
# Fetch dependencies
rebar3 get-deps
# Compile
rebar3 compile
# Run tests: the unit tests, then the fleet suites in test/overlay
rebar3 eunit
rebar3 ct
# The wire property tests: CBOR, frames, records and UCAN, over generated inputs
bash scripts/is_the_property_suite_running.sh
# The same eunit tests in parallel shards, for the run before a push.
# About half the wall clock, same tests, one result.
bash scripts/test-parallel.sh
```
### Before you push: the gate that is CI
```bash
bash scripts/is_tree_pushable.sh
```
It runs the steps of the gating workflows themselves - `.github/workflows/test.yml` and
`docs.yml`, including the doc-snippet check, whose escript lives in `macula-ci-images` at the sha `docs.yml` pins -
inside the image those workflows pin, as root. It exports `HEAD` with `git archive` and runs on that export, never on
your working directory: a file git ignores - a `_checkouts/<dep>` shadowing the locked one, an `_build` from another
branch - cannot make the gate disagree with CI, and the container's files land outside your checkout.
**It is derived from the workflows, not copied from them (macula#163).** The steps are read out of the workflow files
at run time, so a step CI gains is a step the gate gains, and the two cannot drift apart in silence. The thing that
used to happen instead is worth knowing, because it is why the script exists: a package deleted several exported
arities, the hand-written local gate reported eight green steps, and CI's docs job then went red because three runnable
examples in the guides still called the deleted functions. The gap was real, invisible locally, and found after the
work had been handed over.
Three things follow from that, and they are the rule:
- **`scripts/ci_gate_plan.sh` is the only place a job can be excluded.** A job that cannot run inside the build image
(cargo clippy on a plain runner, the advisory scans) is ruled there with the reason a member is told. A job nobody
has ruled on is a refusal, not a silent skip: a new job is a decision, and the decision belongs in a commit.
- **`scripts/is_the_local_gate_in_step_with_ci.sh` fails when the two drift**, and CI runs it. A ruling that outlives
its job, a matrix check whose steps all skipped, a reusable workflow not pinned by a full sha - each is red there
rather than discovered by the next member.
- **A green gate names what it does not cover.** The `NOT-RUN:` lines before the steps are the jobs and the workflows
CI still runs and the gate does not - `rust-lint`, the advisory scans, the PDF build, the release - each with the
reason. A job nobody has ruled on, a workflow file in `.github/workflows` in neither the plan nor the ruled list, a
shell other than the default bash, a `${{ ... }}` the gate cannot resolve: each is a refusal, not a skip.
Read those lines before believing a green gate is a green push.
It exits 3, never 0, when no container runtime is installed: a gate that could not run has not passed.
`scripts/test-parallel.sh` is also how to check that a test is honest rather
than merely passing. A test can be green because of the order or timing the
suite happens to give it; running it under the shards, and on its own with
`rebar3 eunit --module=<module>`, is what exposes that. Both are worth doing for
a test you have just written.
## Coding Standards
### Erlang Style
- Follow idiomatic Erlang patterns
- Use pattern matching on function heads instead of `case`/`if`
- Keep nesting to 1-2 levels maximum
- Prefer guards over conditional logic
- All exported functions must have `-spec` type specifications
- Add EDoc documentation to public functions
### Architecture Patterns
- `*_system` modules are supervisors ONLY (no business logic)
- Follow OTP supervision patterns
- Use CRDT for state that needs eventual consistency
- Events use past tense (facts that happened)
- RPC methods use present tense imperatives (commands)
### Testing
- Write tests first (TDD approach)
- Tests go in `test/` mirroring `src/` structure
- Use EUnit for unit tests, and common_test for a suite that runs several
processes as a fleet (`test/overlay/`); CI runs both
- Use PropEr for a property about the wire — a value that must round-trip, a
decoder that must answer whatever arrives, a signature that must not verify
once a bit of it changes. Properties live in `test/prop_*.erl` and run with
`rebar3 proper`; CI runs them, and fails when zero properties ran, so a
renamed module is a red run rather than a check that silently tested nothing.
A property is only as good as its generator: `scripts/is_the_property_suite_running.sh`
and a deliberate break for a moment are how to see yours fail before you
trust it.
- Clean up test fixtures properly (see `ensure_stopped/0` pattern)
- **A run passes on its exit code, never on the absence of "Failed".** A
cancelled eunit test (a fixture whose setup crashed, a test past its
timeout) prints `Failed: 0. Skipped: 0. Passed: N-1.` and
`One or more tests were cancelled.`: no failure line, one test short.
`rebar3 eunit` exits 1 on it, so trust the exit code, or read the summary
for `All N tests passed.` with the N you expect. A script or filter that
looks only for "Failed" reads that run as green (macula#43).
- **A skipped Common Test case fails the run** unless `rebar.config` allows
it with a reason: `rebar3 ct` exits 0 on a case that skipped itself, so
`cth_skip_is_failure` (from `macula_testkit`) turns an unallowed skip into
a failure.
- **Line coverage has a floor, and it only goes up.** CI merges the eunit and
ct suites' line coverage and fails below 82%
(`scripts/is_line_coverage_at_least_the_floor.sh`). The floor is main's
measured value rounded down to a whole percent: on 28 October 2026 that was
just under 83%, so 82. Neither count is a property of the tree — the covered
count moved by up to three lines across consecutive runs of one commit, and
a rebase onto a new base moves both — so the job's own output is the
authority for them. A whole percent here is about 131 lines, so a
whole-percent floor cannot sit closer than that, and 82 leaves about a
hundred covered lines of headroom. A change that adds more than
that many untested lines is red here. A change that raises coverage raises
the number in the same commit. Lowering it is a reviewed decision about
which lines stop being tested, stated in the commit that does
it; it is never done to turn a branch green. To see where a change stands:
```bash
rebar3 eunit --cover
rebar3 ct --cover
bash scripts/is_line_coverage_at_least_the_floor.sh \
_build/test/cover/eunit.coverdata _build/test/cover/ct.coverdata
```
Both suites, merged: the floor is a property of the suite, not of one of
its halves. The two are not equal contributors — eunit accounts for all but
a couple of the covered lines today — but a number that silently leaves half
the suite out is not the suite's coverage, and which half matters is not
something to re-decide each time. The number is OTP's `cover` on the pinned
Erlang, which is why CI asserts that pin before it measures.
## Submitting Changes
1. Ensure the tree is fit to push: `scripts/is_tree_pushable.sh` (a clean tree,
the pinned Erlang/OTP, `rebar3 xref`, then every eunit test)
2. Run dialyzer as CI does, with the calls a consumer makes: `rebar3 as consumer_contracts dialyzer`
3. Generate docs to check for warnings: `rebar3 edoc`
4. Commit with clear messages
5. Push to your fork
6. Open a Pull Request
### Commit Messages
Use clear, descriptive commit messages:
```
Add QUIC connection pooling with LRU eviction
- Implement max connection limit (1000)
- Add LRU eviction when limit reached
- 22 tests for pool lifecycle
```
### Pull Request Guidelines
- Reference related issues
- Describe what changes you made and why
- Include test coverage for new functionality
- Update documentation if needed
- Update CHANGELOG.md under "Unreleased" section
## Reporting Issues
When reporting issues:
1. Check existing issues first
2. Provide clear reproduction steps
3. Include Erlang/OTP version
4. Include relevant logs or error messages
## Questions
For questions about the codebase:
- Open a GitHub Discussion
- Check existing documentation in `docs/` and `architecture/`
## License
By contributing, you agree that your contributions will be licensed under the Apache-2.0 License.