Allow moving tasks between parents in task_update and task_patch #243
Description
Tasks could only get a parent at creation. Re-parenting meant cancel plus recreate, which burns the project-local number that memories, relations and cross-agent references point at via #N.
Design decision and full rationale: #241.
Contract as delivered
task_update:parentTaskNumberis required with no default, so omitting it is a schema error rather than a silent move to top level. Deliberate top level is an explicitnull. Tasks have no revision history, so an accidental unparent would be unrecoverable and would drag the whole subtree along.task_patch:parentTaskNumberis a normal explicit field — omitted preserves,nullmoves to top level, a number moves under that parent.- Both tools report the new parent. Previously the number came from the state read before the change and was wrong after a move.
Validation on a move
TaskService.RequireMovableParent rejects a parent from another project, a closed parent, the task itself, and any of its own descendants; the ancestor walk terminates because the tree is acyclic. The version check locks only the moved task; a concurrent close of the target relies on the Serializable transaction. Depth stays unbounded and rollups stay computed.
Resolved
The ProposalService caveat is gone: task proposals are being removed entirely (#247), because tasks are work organization rather than reviewed knowledge (#238). No move needs to pass proposal validation.
The parallel-session blocker is also gone — that work reverted its own TaskService change and the task routing it had added to TaskTools was withdrawn.
Delivered
| Commit | Change |
|---|---|
6d6df1d |
RequireMovableParent, TaskService.Update takes the parent, TaskPatchChanges gains HasParent, a parent-only patch stops being a no-op. |
83beeef |
parentTaskNumber on task_update (required) and task_patch, both echoing the new parent; service and MCP coverage plus the inputSchema.required check. |
Still open: no parent picker in TaskEditor.razor — the UI keeps preserving the current parent.