feat(composition): cycles + recursive + version pinning (013, US4+US5 / PR 5)#255
Merged
mlieberman85 merged 1 commit intoMay 13, 2026
Merged
Conversation
US4 — cycle-chain error messages, recursive composition (FR-018),
F-1 regression test through the public loader path.
US5 — PEP 440 version pinning per [[compose]] block (FR-013/FR-014).
What lands in this PR (T043-T050 + T046b):
- T043 already shipped in PR 1: CompositionCycleError.__str__
renders chain joined by " → " and stores `chain: list[str]`.
- T047 already shipped in PR 2: _check_version_constraint runs at
compose-block iteration time and raises
CompositionVersionMismatchError on miss.
- 8 new fixtures: cycle-a (self), cycle-x/y (two-cycle), three-
level-chain (positive recursive), loader-cycle-x/y (F-1 regression),
version-pin-satisfied, version-pin-violated.
- 6 new tests:
- test_self_cycle_raises (T044): chain == ["cycle-a", "cycle-a"];
error renders "cycle-a → cycle-a".
- test_two_cycle_raises (T045): 3-element chain, starts+ends with
same slug, contains both cycle members.
- test_three_level_chain_resolves (T046, positive): the load-bearing
FR-018 + FR-015 assertion — every resolved control's
_composed_from points at the ULTIMATE non-composite source
(mock-source-c-leaf), NOT the intermediate composite.
- test_loader_path_cycle_through_public_loader (T046b): F-1
regression — loads a cycle through PUBLIC load_framework_config
(monkeypatching _default_source_loader to find fixtures, since
PluginRegistry doesn't know about test files). Asserts the cycle
is detected in <1 second. Pre-F-1 this would have hung
indefinitely. This is the canonical guarantee that the
resolver/loader split is correct.
- test_version_pin_satisfied (T048): 1.5.0 satisfies >=1.0,<2.0.
- test_version_pin_violated (T049): 1.5.0 does NOT satisfy >=2.0;
CompositionVersionMismatchError with .source / .constraint /
.installed populated.
- test_version_pin_missing_uses_floating (T050): no
version_constraint reuses basic-include-all fixture, resolves
cleanly (FR-014).
Verification:
- ruff check: clean
- full suite: 2102 passed (6 new) / 6 skipped / 0 failed
Status: every spec FR (1-18) and every SC (1-8) is now covered by
fixture-driven tests. What remains is the Polish phase — perf
benchmark, SC-008 explicit test, FR-017 CLI test, F-7 dual MCP tool
test, doc sync, lint, full suite, quickstart smoke.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
PR 5 of 5 functional PRs for feature 013-plugin-composition, stacked on US3 (#254). Bundles US4 — cycle detection + recursive composition (P3) and US5 — version pinning (P3) because both are small P3 guardrails on top of the now-feature-complete resolver. The two stories share no overlapping code, and the task plan explicitly suggested this bundle (PR 5 in suggested boundaries).
Of particular note: this PR adds
test_loader_path_cycle_through_public_loader— the canonical F-1 regression test that loads a composite-of-composite cycle through the productiondarnit.config.merger.load_framework_configpath and asserts it's detected in under 1 second. Pre-F-1 this would have hung indefinitely. This is the load-bearing guarantee that the resolver/loader split from PR 1 is correct.What's in this PR
US4 — Cycle detection + recursive composition (T043–T046b)
CompositionCycleError.__str__renders chainchain: list[str]attribute;" → "join in messagechain == ["cycle-a", "cycle-a"]_composed_frompoints at the ULTIMATE non-composite source (mock-source-c-leaf), NOT the intermediate composite_default_source_loaderto find fixtures; asserts cycle detected in <1s throughload_framework_configUS5 — Version pinning (T047–T050)
_check_version_constraintruns in compose-block iteration; raisesCompositionVersionMismatchError(source, constraint, installed)mock-source-a@1.5.0satisfies>=1.0,<2.0mock-source-a@1.5.0does NOT satisfy>=2.0; error names source / constraint / installedbasic-include-allfixture; resolves regardless of source version (FR-014)Test plan
uv run ruff check .— cleanuv run pytest tests/darnit/test_composition.py -v— 28 passed (12 US1 + 6 US2 + 4 US3 + 4 US4 + 3 US5 + 1 foundational idempotence)uv run pytest tests/ --ignore=tests/integration/ -q— 2102 passed (6 new) / 6 skipped / 0 failedtest_loader_path_cycle_through_public_loaderexercises the F-1 regression through the production loader path (would have hung pre-fix)mock-source-c-leaf, never tomock-source-mid-compositeStatus of feature
Every spec FR (1–18) and every SC (1–8) is now covered by fixture-driven tests. The resolver is functionally complete; this is the last PR that adds new behavior to
darnit.core.composition.Subsequent PRs
🤖 Generated with Claude Code