Promote a helper into axm-ingot
axm-ingot grows by promotion: a helper does not start here, it earns its
way in once it is duplicated across the forge and its dependencies are light
enough to keep the leaf a leaf. This guide is the operational ritual — the two
gates a candidate must clear, and the mechanical steps to move it in and retire
the copies.
The two gates
A helper may be promoted only if it clears both gates. The Rule of Three is necessary but not sufficient — the dependency gate is the second lock.
- Rule of Three (reuse gate) — the same logic is copy-pasted, with the same intent, in at least two packages (a third consumer is imminent or already wants it). One consumer is not a shared helper; it is that package's own code.
- Dependency gate (light-leaf gate) — the helper's dependencies are
stdlib only.
axm-ingotis imported across the whole galaxy; every consumer inherits all of its dependencies, sodependenciesstays empty. A helper that needshttpx,pandas, an SDK — or Pydantic — does not come here. Keep itreuse_in_place(import it from the owning package) or give it a thematic light lib of its own (e.g. anaxm-netfor resilient HTTP).
No Pydantic
Even though Pydantic is a near-universal AXM dependency, it is a
dependency: adding it to axm-ingot breaks the leaf invariant. Value
objects here are stdlib @dataclass(frozen=True), not BaseModel. See
Architecture — Design Decisions.
Checklist
Before opening the PR, confirm every box:
- [ ] Two+ real consumers copy-paste the logic today (Rule of Three).
- [ ] Stdlib-only dependencies — nothing new enters
dependencies(it stays[]). - [ ] The helper is pure and defensive — hostile inputs degrade to
None/[]/empty, they never raise. This is the leaf's core contract. - [ ] A frozen dataclass (not Pydantic) for any value object it returns.
- [ ] Tests move with it — the helper is tested here, once, so consumers stop re-testing the primitive.
- [ ] Public symbols are re-exported from
axm_ingot(or documented asaxm_ingot.uv-only, likeparse_workspace_members).
Steps
1. Land the helper in axm-ingot
Add the function/class to the relevant subpackage (e.g. axm_ingot.uv), export
it in that subpackage's __all__, and — if it is top-level public — re-export it
from axm_ingot/__init__.py. Move the tests alongside it (unit under
tests/unit/, real-I/O scenarios under tests/integration/).
# src/axm_ingot/uv/__init__.py
from axm_ingot.uv.resolve import my_new_helper
__all__ = [..., "my_new_helper"]
2. Depend on axm-ingot from each consumer
In each consuming package's pyproject.toml:
3. Migrate the consumers to the shared helper
Replace each copy-pasted implementation with an import:
Run each consumer's suite (uv run --package <consumer> pytest) — since the
primitive is now tested in axm-ingot, the consumer's own tests should only
cover its integration, not re-test the helper.
4. Delete the source copies
Remove the now-dead local implementations and their duplicated tests from every consumer. This is the payoff: the logic exists once, is tested once, and a bug fixed here is fixed everywhere.
5. Verify the leaf stayed a leaf
Confirm dependencies = [] is unchanged in axm-ingot's pyproject.toml, then
run the workspace gate:
If the promotion added a runtime dependency, it was the wrong call — revert and use a thematic light lib instead.