6.4 KiB
Multi-project sync: rows A and B (2026-10-11)
Status: written by Sage, lead. It follows docs/plans/BRIEF-TEMPLATE.md.
Jason answered Q61a, relayed by Mos: agents see and work his nested
projects ("just share them. Get it done."). Mos ruled the shape: row A
now, row B after row A is done, and task.create takes an optional
project. Lead decision 86 records it.
Three constraints hold for both rows. A change that breaks one of them is out of scope, whatever else it fixes.
- A task's ledger ref stays
vikunja:<project>/<task>. The ledger checks that form ontask_snapshots,decisionsand theeventssubject (packages/bus/schema.sql), andtask_snapshotsis append-only. Neither row changespackages/bus/. Never key a task by its id alone. tracker.descendantsdefaults to false. With it false, the adapter behaves exactly as v0 does, and every v0 test passes unchanged.- The adapter never writes to a board. A child project is installed
only if its one manual kanban view already has the five buckets
(
todo,in-progress,in-review,blocked,done), done and default wired, as the root's install check requires. Any other child is watch-only. No bucket, view or project write ships in either row.
Multi-project sync: the adapter reads a project tree (row A)
Problem
The adapter watches one project. settings() takes one
tracker.project (packages/tasks/src/adapter.mjs:21), startup checks
that project alone (startup.mjs:37), and sync reads its board and task
list only (sync.mjs:21, sync.mjs:45). A task in any other project is
a shape error in the walk (sync.mjs:59), and a move to another project
is a tombstone (sync.mjs:123). Jason's projects nest. The bus binds
to #19 "Mosaic Stack", and he wants agents to follow work into its
child projects.
Owner and reviewer
- Owner: Darkwing.
- Reviewers: Dewey and Filbert.
Files owned
packages/business/src/vars.mjsandpackages/business/tests/vars.test.mjspackages/tasks/src/adapter.mjs,startup.mjs,sync.mjs,fake.mjs,vikunja.mjs,index.mjspackages/tasks/tests/(all files and fixtures)packages/tasks/README.md,packages/tasks/live/README.mdagents/darkwing/work/queue-<row>/
verbs.mjs is read-only in this row. Verbs keep refusing a task outside
the root with task-project, as they do now.
What ships
tracker.descendants, a project-layer boolean var, default false.tracker.projectstays the root.- With descendants on, the tree is the root plus every descendant the
sync bot can see through
GET /projects(key 9 hasprojects read_all), followed byparent_project_id. Discovery runs at startup and at each reconcile, not each tick. Archived projects are not in the tree. Record your rule for a project that leaves the tree (archived, moved out, unshared or deleted) in the README and test it. Its tasks becometask.missingwith a reason. The ledger is never rewritten. - Install state per project. The root's install check refuses the business, exactly as today. A child that passes the same check is installed. A child that fails it is watch-only and never refuses the business. Sync records title, done, assignees and comments for a watch-only child, and no bucket state.
- Sync covers each project in the tree. A task moved between two
projects in the tree gets the
movedtombstone naming the new project and a snapshot under its new ref. A move out of the tree stays the tombstone it is today. - Poll cost. Probe whether v2.7.0 serves a cross-project task read to the
sync key's scopes. If it does, use one read per tick for the tree. If
not, read per project and state the per-tick call count in the README.
The probe results go in
agents/darkwing/work/queue-<row>/probes.md. fake.mjsmodelsparent_project_id, the project list route, archived projects and access inherited from an ancestor's share.- Tests: descendants off matches v0; a three-level tree; a watch-only child keeps the business ready; a root without buckets refuses; moves root to child, child to child and child to outside; a new child appears at reconcile; a child leaves the tree.
- Gate suites:
node --test 'packages/tasks/tests/*.test.mjs',node --test 'packages/business/tests/*.test.mjs',node --test 'packages/bus/tests/*.test.mjs', and everyscripts/test-*.sh.
Out of scope
Verbs on child projects (row B). Any change to packages/bus/. Any write
to a Vikunja board or project. An archive guard: the adapter has no
project verb, so the pm's projects.update can't reach a project through
the bus. A row that adds a project verb adds the guard.
Gate
Dewey and Filbert approve on the issue, naming the candidate manifest. The suites above pass on Sage's gate rerun. Sage then runs one live read against tasks.woltje.com with descendants on, root #19, read-only, and attaches the tick log.
Multi-project verbs: task verbs across the tree (row B)
Problem
After row A, the bus sees child projects but can't act in them.
target() and relation() refuse a ref from another project
(verbs.mjs:74, verbs.mjs:79), read() refuses a task that isn't in
the root (verbs.mjs:88), and task.create always posts to the root
(verbs.mjs:355) with no project argument.
Owner and reviewer
- Owner: Darkwing, after row A is done.
- Reviewers: Dewey and Filbert.
Files owned
packages/tasks/src/verbs.mjs,fake.mjspackages/tasks/tests/verbs.test.mjsand fixturespackages/tasks/README.mdpackages/harness/src/tools.mjs(thetask_createtool schema gets the optional project) and its testagents/darkwing/work/queue-<row>/
What ships
- Mos's ruling:
task.createtakes an optionalproject. It must be an installed project in the tree; a watch-only or unknown project refuses with a new code the README names. Without it, the task goes to the root, as today. target(),relation()andread()accept a task in any installed project of the tree. A bucket move uses that project's own view and buckets, not the root's.- A verb on a task in a watch-only project refuses with the same new code.
- Tests for each verb across two installed projects, a watch-only refusal and a relation between projects.
- Gate suites as row A.
Out of scope
Project verbs (create, rename, archive, move). They need their own brief, an archive guard and a ruling.
Gate
Dewey and Filbert approve on the issue, naming the candidate manifest, and the suites pass on Sage's gate rerun.