Files
stack/agents/darkwing/work/queue-63/build.md
T

8.6 KiB

Row 63, task verbs across the tree (row B), build (Darkwing)

Issue #1556. Brief: docs/plans/2026-10-11_multi-project-sync.md, section "Multi-project verbs: task verbs across the tree (row B)" (blob 9301de2e). Base 08941d8b. No commit from the base to the packet's commit touches the six paths.

build.patch is git diff --cached --binary 08941d8b of the six paths. Apply it with git apply --index to a detached worktree of the base, and sha256sum -c candidate-manifest.sha256 then gives 6/6 OK.

File What changed
packages/tasks/src/verbs.mjs verbs act in any installed project of the tree
packages/tasks/tests/verbs.test.mjs six tests across a root, an installed child and a watch-only grandchild
packages/tasks/tests/tree.test.mjs one assertion: the watch-only verb refusal code
packages/tasks/README.md the new step, project, the new refusal
packages/harness/src/tools.mjs task_create gets an optional integer project
packages/harness/tests/tools.test.mjs asserts its type, that it isn't required, that only task_create has it

What the code does

  • installed(project) is the one gate. A project the tree doesn't hold refuses task-project, as a foreign ref always has. A project in the tree with no view, a watch-only one, refuses with the new code project-not-installed. With descendants off the tree holds the root alone, so nothing changes for a business that hasn't opted in.
  • target() returns {project, view, id} in place of a bare id, and read(), step() and every plan.check take that. A bucket move, a close and the board read all use the task's own project and view. No ctx.project or ctx.view is left in read(), task.update, task.close or create()'s writes; create's default project is the one remaining use.
  • relation() accepts a task in any installed project and keeps its project. A ref's project is only the caller's claim, so located() reads each related task once with the sync token before broker.authorize. Missing gives the tracker's refusal (task-not-found), a task in another project than its ref names gives task-moved. This covers relations on task.create and both the add and the remove lists of task.schedule. Without it, a relation to a task the ref placed in the child but which sits in a watch-only project would have been written.
  • task.create takes an optional project, Mos's ruling as the brief states it. Left out, it's the root. It must be a positive safe integer (invalid-request otherwise, so "1", 1.5, 0 and null refuse before any read) and an installed project of the tree (project-not-installed for watch-only, unknown and outside alike).

Choices to review

  1. One code for create's unknown project. The brief asks for "a new code the README names" for a watch-only or unknown project on create. I used project-not-installed for both and for a project outside the tree. A ref outside the tree keeps task-project, because a ref names a task and that code already means "not this business's task". A create's project names where to write, and the brief puts unknown with watch-only.
  2. Relations are read before authority, which the brief doesn't ask for. The brief says relation() accepts any installed project's task. Taking the ref's project on trust would let a caller write a relation to a task in a watch-only or foreign project by naming an installed one. One sync read per related task closes that. It costs a read per relation (create caps relations at 50). The README's verb steps gain a step 5 for it.
  3. tree.test.mjs is outside the brief's owned files. One assertion there expected task-project for a verb on a watch-only child's task, which was row A's v0 behaviour. Row B changes that code by design, so the assertion moves to project-not-installed and its comment says where the installed cases are tested. Nothing else in the file changed.
  4. The create decision still binds no target. task.create authorizes with target: null, as before, so a decision approving a create doesn't name which project it approved. A decision raised for the root can authorize a create into an installed child. The tree is the business's own and installed projects are writable by design, so I left it. Binding the project would change the decision record's shape, which is the broker's and not this row's. It's a follow-up if a reviewer wants it.
  5. fake.mjs is untouched. moveToProject already existed from row A, which was all the moved-task test needed.

Tests

The brief asks for each verb across two installed projects, a watch-only refusal and a relation between projects. The six new tests in verbs.test.mjs run on a tree of root, installed child and watch-only grandchild (the grandchild holds one task the UI made):

  1. Every verb in the installed child, on the child's own board: create into the child with a relation to a root task, create with the root named explicitly, assign (and a reassign that refuses task-assigned), task.update with a state on the child's view, task.scope.change by the coder and task.priority.change by the pm (each with a decision the cto resolves), reassign, schedule with a due date and a relation removal, a root-to-child relation add, and close into the child's done bucket.
  2. task.create refuses the watch-only grandchild, a project outside the tree and 999 with project-not-installed; null, 0, -1, 1.5, "1" and [id] with invalid-request. No writes, no allowed creates.
  3. With descendants off, create takes only the root.
  4. A verb on the watch-only task and a relation to it refuse project-not-installed; vikunja:999/1 still gives task-project.
  5. Relations are read before authority: a ref claiming the child for a root task gives task-moved, a missing child task gives task-not-found, for schedule add, schedule remove and create, with no writes; then a correct relation goes through.
  6. A task moved to another project of the tree refuses its old ref with task-moved and takes its new one.

Gate

Row 63's gate (tasks, harness, every scripts/test-*.sh) plus row A's business and bus, sequential in the candidate worktree, TMPDIR in my scratch, test-task.sh with DOCKER_HOST=unix:///nonexistent. It ran 2026-10-12 19:39:19Z to 19:42:45Z, after Sage's row 81 gate and Rocko's row 77 run had ended. Every suite exits 0 (gate-summary.txt).

Suite Result
tasks 80 pass (74 at the base plus the six)
harness 59 pass
business 60 pass
bus 95 pass
test-auth 15 passed
test-conductor 17 passed
test-config 24 passed
test-discord 66 passed
test-extension-package 18 passed
test-foundation 44 passed
test-queue 27 passed
test-release 14 passed
test-task 26 passed (the live recall pair skips without Docker)

Mutants

mutants.sh with agents/darkwing/work/queue-59-review/r1/mut.mjs. Each mutant edits one site (exactly one match), runs verbs.test.mjs and tree.test.mjs (or the harness tools.test.mjs), and restores the file. 18 of 18 killed (mutants.txt, run 2026-10-12 19:20:11Z to 19:21:24Z).

Mutant What it undoes Killed by
watch-only-writable the watch-only refusal in installed() assertion, 2 tests
create-root-only create posts to the root whatever project says assertion
create-watch-only-ok tree.has in place of tree.get on create assertion
create-project-any the integer check on project assertion
move-root-view a state move looks up the root's bucket ids tracker-not-found
move-path-root-view the bucket move path uses the root's view tracker-not-found
close-root-done close moves to the root's done bucket tracker-not-found
read-root-board read() reads the root's board task-placement, 2 tests
read-no-project read() drops the project check assertion (test 6)
relation-any-project relation() drops installed() assertion, 2 tests
handle-no-located verbs skip the relation read assertion
create-no-located create skips the relation read assertion
located-no-project the relation read drops the project check assertion
schedule-add-only schedule reads only the add list assertion
located-after-authorize the relation read moves after authorize assertion
tool-project-string the tool's project becomes a string assertion
tool-project-zero the tool's minimum becomes 0 assertion
tool-project-required the tool requires project assertion