Issue hierarchy
One nullable relation carries epics, tasks, and subtasks: an issue may name a parent, and issues nest arbitrarily. There is no separate epic type.
Create a child
The server resolves the reference within the selected project and stores the
full canonical ID. A parent is assigned only when the child is created —
there is no re-parenting — so cycles are structurally impossible. Creating a
child under a done or cancelled parent is a typed conflict.
When you hold a parent’s representation, its advertised create_child
action carries the parent as a pre-filled constant, so you never type the ID.
Read the hierarchy
fp2 issue show returns:
parent— reference, title, and status, or null;children— a bounded list of the most recently updated children;children_totalandchildren_open— the authoritative counts;children_in_progress— how many of the open ones are underway;children_heldandchildren_stalled— how many open children somebody is holding right now, and how many were claimed, moved toin_progress, and then left for the lease to run out.
State ratios and completeness from the counts, never from the bounded list. The same five counts ride every row of a project’s top-level slice, which is how an overview says that work is happening one level below it: the children themselves are not in that payload.
children_held and children_stalled are the server’s comparison against its
own clock at the moment of the read. Every other lease reading — held_until
on a holder — is yours to make, and a client whose clock disagrees with the
Date header should decline to make it rather than state a countdown it cannot
justify.
The close gate
A parent cannot transition to done while any child is open (todo,
in_progress, or review):
- compliant clients simply do not see the
doneaction; theblockedentry explains withreason_code: "open_children"and the count; - a constructed or raced attempt is rejected with a typed
issue_children_openconflict carrying the count and the currently legal actions; - the check is atomic with the status write, so a child created concurrently cannot slip under a closing parent.
Cancellation is deliberately not gated: abandoning a parent asserts nothing
about its children’s records. Close or cancel the children, re-read the
parent, and done reappears.
Retries are safe throughout: a child create replayed with its original client_id returns the
original child even if the parent has since closed.