vikunja: Q61a re-mint, pm key 8 and sync key 9; rows A and B brief; decision 86 (sage)

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-10-11 13:12:39 -05:00
co-authored by Claude Opus 5.5
parent b644f3c230
commit 8060426035
4 changed files with 413 additions and 0 deletions
+30
View File
@@ -1811,3 +1811,33 @@ which stay with him. Each item names who decided it and what happened.
- Open: projects.create for the pm (asked of Jason via Mos), and Q61
(which project the bus watches). Project 32 stays deleted. Jason
removed it as a duplicate of #19, and recreate.mjs is gone.
86. **The agents bind the bus to #19 and follow nested projects (2026-10-11,
Q61a).** Jason, relayed by Mos: "just share them. Get it done. You
keep wanting me to do all the manual steps." The agents do every
step, and Jason does none of them. This replaces the holds in 85.
- ops-01 inserts the team shares on #3, #4 and #33 (team 1 at admin,
team 2 at read). Access cascades to their descendants.
- Keys re-minted with "full agentic access" and still no task or
project delete, no duplicate and no share, team, user or webhook
route (Q60). The pm is key 8: key 7's scopes plus projects create,
views_buckets, views_buckets_put and views_buckets_post, and
projects_views read_all and update. The sync bot is key 9: key 6's
scopes plus projects read_all. Keys 7 and 6 are revoked and answer
401. Evidence: `agents/sage/work/vikunja-setup/2026-10-11_remint.json`.
- The bus binds to #19 "Mosaic Stack". Its kanban gets the five
buckets by renaming where the mapping is obvious (To-Do to todo,
Doing to in-progress, Done to done) and adding in-review and
blocked. Jason's tasks stay where they are. A before-state file
comes first.
- Child boards are watch-only. The agents never rename or add
buckets on them.
- Rows A and B (`docs/plans/2026-10-11_multi-project-sync.md`):
read-only nested sync now, and verbs across the tree after row A
is done. Mos ruled that task.create takes an optional project,
which must be an installed project in the tree.
- Correction to 85: no adapter archive guard is needed now. The
adapter has no project verb, so the pm's projects.update can't
reach a project through the bus. The row that adds a project verb
adds the guard.
- Projects.create is used once now, for "SetSpark" and "Launchpad"
under #33, with no tasks.
+151
View File
@@ -0,0 +1,151 @@
# 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.