add_child_collection: enforce DAG by rejecting indirect cycles
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 0
- Forks
- 1
- Avg merge
- 26m
- Merged PRs (30d)
- 24
Description
Problem
A Collection nests other Collections (CollectionMember parent→child) and is described as a DAG (acyclic). add_child_collection only guards the direct self-loop:
parent_id == child_id→ rejected- already-a-child →
ConflictError
There is no check for indirect cycles. Nesting B under A, C under B, then A under C closes a 3-hop loop, and the code accepts it. So acyclicity is enforced only at depth 1.
Impact
Any recursive walk of the nesting then loops (infinite recursion / stack blow-up). Live consumers:
- children expansion in the collection response (
collection_service~L493) get_effective_access_level, which walks a collection's relations
Footgun in normal use; a DoS vector if collection nesting is ever exposed to less-trusted writers.
Fix
In add_child_collection, reject the edge if parent is already a descendant of child (ancestor/descendant check), not just the direct parent_id == child_id case.
Scope note
Missing validation, not a data-model flaw — surfaced while pressure-testing the context/run/curation model. Independent of that ADR.
Migrated from the internal tracker, where it was #17, opened 2026-06-25.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by locating add_child_collection and the children expansion in collection_service around L493. Trace how parent and child relations are traversed, then verify that adding an edge from a collection to one of its descendants is rejected while direct self-loops and duplicate children retain their existing behavior. Done means indirect cycles cannot be created and recursive collection access remains bounded.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- backend
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 68/100