Skip to content

Comments

chore: sort discovered deps for stable ordering across environments#51

Merged
zachdaniel merged 1 commit intoash-project:mainfrom
nallwhy:chore/stable-deps-ordering
Feb 16, 2026
Merged

chore: sort discovered deps for stable ordering across environments#51
zachdaniel merged 1 commit intoash-project:mainfrom
nallwhy:chore/stable-deps-ordering

Conversation

@nallwhy
Copy link
Contributor

@nallwhy nallwhy commented Feb 16, 2026

Contributor checklist

Leave anything that you believe does not apply unchecked.

  • I accept the AI Policy, or AI was not used in the creation of this PR.
  • Bug fixes include regression tests
  • Chores
  • Documentation changes
  • Features include unit/acceptance tests
  • Refactoring
  • Update dependencies

Problem

Different environments (machines, Elixir versions) running mix usage_rules.sync can produce different output for the same config. This causes unnecessary diffs and noisy commits when collaborators sync on their own machines.

The root cause is discover_deps/1 returning deps from Mix.Project.deps_paths() (a Map) without sorting. Since Erlang Map iteration order is not guaranteed, regex-matched deps in usage_rules config can appear in different orders.

Config style Ordering
Explicit atoms (:ash) Stable (config order)
Regex (~r/^ash_/) Unstable (Map order dependent)
Sub-rules Stable (Enum.sort already applied)

Solution

Add Enum.sort_by(&elem(&1, 0)) to the end of discover_deps/1 so that all_deps is always in alphabetical order. This makes regex matching produce deterministic results without affecting explicit atom ordering (which is handled in resolve_usage_rules).

Notes

The test verifies the intended sorted order but cannot reproduce the non-deterministic behavior in test mode, since Erlang small maps (<=32 keys) store keys in sorted order internally.

Copilot AI review requested due to automatic review settings February 16, 2026 06:32
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR addresses non-deterministic output from mix usage_rules.sync by ensuring discovered dependencies are always sorted alphabetically. Previously, regex-matched dependencies could appear in different orders across different environments due to Erlang's Map iteration order being non-deterministic for larger maps.

Changes:

  • Added Enum.sort_by(&elem(&1, 0)) to discover_deps/1 to ensure alphabetical ordering of dependencies
  • Added test to verify regex-matched dependencies appear in stable sorted order

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
lib/mix/tasks/usage_rules.sync.ex Added sorting to discover_deps/1 to ensure deterministic alphabetical ordering of dependencies
test/mix/tasks/usage_rules.sync_test.exs Added test verifying regex produces sections in stable sorted order

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +409 to +412
alpha_pos = :binary.match(content, "## alpha usage") |> elem(0)
mango_pos = :binary.match(content, "## mango usage") |> elem(0)
zeta_pos = :binary.match(content, "## zeta usage") |> elem(0)

Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test uses :binary.match/2 followed by elem(0) to extract positions. If any of these matches fail and return :nomatch, calling elem(0) on the atom :nomatch will crash with a BadArgumentError rather than providing a clear test failure message. Consider using pattern matching to provide better error messages, for example: {alpha_pos, _} = :binary.match(content, "## alpha usage") or adding explicit assertions that the content contains these strings before extracting positions.

Suggested change
alpha_pos = :binary.match(content, "## alpha usage") |> elem(0)
mango_pos = :binary.match(content, "## mango usage") |> elem(0)
zeta_pos = :binary.match(content, "## zeta usage") |> elem(0)
assert content =~ "## alpha usage"
assert content =~ "## mango usage"
assert content =~ "## zeta usage"
{alpha_pos, _} = :binary.match(content, "## alpha usage")
{mango_pos, _} = :binary.match(content, "## mango usage")
{zeta_pos, _} = :binary.match(content, "## zeta usage")

Copilot uses AI. Check for mistakes.
@zachdaniel zachdaniel merged commit b07a71f into ash-project:main Feb 16, 2026
11 of 12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants