Mix resolves tasks by module name, so Mix.Tasks.Castle.Relup compiled into Forecastle sits in a namespace Castle could also write into. If both ever defined the same module, whichever ebin came first on the code path would win, silently.
The containment is an invariant:
Castle ships no Mix tasks. Every task lives in Forecastle, whatever it is called.
A convention nobody enforces is a convention that lapses, so it needs to fail a build rather than be remembered. This is the same move the project already makes for the coverage floor, verify_relup!/2 and the restart_new_emulator refusal.
Scope
- A test that walks Castle's own compiled modules (or
lib/) and fails if anything under Mix.Tasks. appears.
- The failure message should say why the invariant exists, not just that it was violated.
Acceptance
- The test fails when a
Mix.Tasks.* module is added to Castle, and passes otherwise.
- It runs under
mix precommit.
Rationale: design/upgrade-tooling.md §D1.
Mix resolves tasks by module name, so
Mix.Tasks.Castle.Relupcompiled into Forecastle sits in a namespace Castle could also write into. If both ever defined the same module, whicheverebincame first on the code path would win, silently.The containment is an invariant:
A convention nobody enforces is a convention that lapses, so it needs to fail a build rather than be remembered. This is the same move the project already makes for the coverage floor,
verify_relup!/2and therestart_new_emulatorrefusal.Scope
lib/) and fails if anything underMix.Tasks.appears.Acceptance
Mix.Tasks.*module is added to Castle, and passes otherwise.mix precommit.Rationale:
design/upgrade-tooling.md§D1.