152 lines
6.4 KiB
Markdown
152 lines
6.4 KiB
Markdown
# 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 on `task_snapshots`, `decisions` and the `events`
|
|
subject (`packages/bus/schema.sql`), and `task_snapshots` is
|
|
append-only. Neither row changes `packages/bus/`. Never key a task by
|
|
its id alone.
|
|
- `tracker.descendants` defaults 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.mjs` and `packages/business/tests/vars.test.mjs`
|
|
- `packages/tasks/src/adapter.mjs`, `startup.mjs`, `sync.mjs`,
|
|
`fake.mjs`, `vikunja.mjs`, `index.mjs`
|
|
- `packages/tasks/tests/` (all files and fixtures)
|
|
- `packages/tasks/README.md`, `packages/tasks/live/README.md`
|
|
- `agents/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.project` stays the root.
|
|
- With descendants on, the tree is the root plus every descendant the
|
|
sync bot can see through `GET /projects` (key 9 has `projects
|
|
read_all`), followed by `parent_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
|
|
become `task.missing` with 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 `moved` tombstone 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.mjs` models `parent_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 every
|
|
`scripts/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.mjs`
|
|
- `packages/tasks/tests/verbs.test.mjs` and fixtures
|
|
- `packages/tasks/README.md`
|
|
- `packages/harness/src/tools.mjs` (the `task_create` tool schema gets
|
|
the optional project) and its test
|
|
- `agents/darkwing/work/queue-<row>/`
|
|
|
|
### What ships
|
|
|
|
- Mos's ruling: `task.create` takes an optional `project`. 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()` and `read()` 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.
|