Files
stack/packages/tasks/live/README.md
T
jason.woltjeandClaude Opus 5.5 7e73c2cd13 feat(tasks): the Vikunja v2 adapter, broker task verbs and sync (row 38, S3, darkwing)
packages/tasks adds the Vikunja v2 client, the eight task verbs, the
board-plus-cursor poll with its 60 s window and the digest. The broker
gains the task verbs and boots trackers from the boot config (lead
decisions 66 to 68). Due dates are truncated to the second and recorded
as truncated (B1). A write that lands but whose final read fails counts
as landed, in update and in create (B2).

Candidate agents/darkwing/work/slice1-s3, build-r2.patch 71ce87e6,
manifest e10e30e3 (28 files). Filbert approved round 2 on #1520
(comment 26853). Darkwing's post-reset rerun: test-release 14/14,
test-task 98/98 (comment 26857).

Integration gate in a worktree on c4baf779 with the patch applied:
bus 67, business 60, control-board 124, discord 173, ledger 78,
mosaic 69, queue 148, seat 19, tasks 51 and webui 14, all with no
failures. Conversation is 149/3. The three cohort kill cases (K1, K3,
K10) fail the same on the unpatched base, and the patch doesn't touch
the package. Every scripts/test-*.sh is green. test-release 14/14 and
test-task 98/98 ran on the existing gate2 compose network, because the
host's Docker address pools are exhausted. No network was created or
pruned.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-09 07:40:48 -05:00

87 lines
3.8 KiB
Markdown

# S3 live run
`run.mjs` takes one task through every verb against a real Vikunja, with
the real broker and adapter in one process. It's the row 38 acceptance
run against the estate instance, and it was rehearsed against a scratch
container first.
```sh
node packages/tasks/live/run.mjs setup.json out/ --base https://tasks.example.org
```
`--base` has to repeat `baseUrl` from the setup file, or the run exits 2
before it reads a token. A setup file copied from another install can't
send the run to the wrong host without the operator typing that host.
## Setup file
```json
{
"business": "demo",
"baseUrl": "https://tasks.example.org",
"project": 12,
"sync": {"botId": 41, "file": "/home/op/.config/mosaic-dev/tokens/demo-sync", "expires": "2027-01-01"},
"roles": {
"pm": {"botId": 42, "env": "VK_PM", "expires": "2027-01-01"},
"coder": {"botId": 43, "env": "VK_CODER", "expires": "2027-01-01"},
"reviewer": {"botId": 44, "env": "VK_REVIEWER", "expires": "2027-01-01"}
},
"labels": {"slice-1": 7}
}
```
Each token is a file or an environment variable, never a value in this
file. A token file follows the broker's rules: an absolute path, mode
0600, owned by the operator, not a symlink. `expires` is the day the
token was minted to expire, as `YYYY-MM-DD`. The project, buckets, bots
and labels come from runbook sections 2 and 3
(`docs/guides/slice-1-identities.md`). The first label in `labels` is added at create and removed at schedule.
Leave `labels` empty to skip both.
## What it does
The run builds a business with an `operator` human and the pm as arbiter
for both domains, then starts the broker with the adapter in a fresh
data directory under `TMPDIR`. The three roles claim their launches and
the adapter runs its startup checks and one reconcile.
Then the steps, in order. Each one either succeeds or refuses with the
code the step expects:
| Step | Role | Verb | Expected |
|---|---|---|---|
| create-by-coder | coder | `task.create` | `field-writer` |
| create | pm | `task.create` | ok, with the label |
| assign | pm | `task.assign` to coder | ok |
| update-by-unassigned | reviewer | `task.update.assigned` | `not-assigned` |
| start | coder | `task.update.assigned` in-progress, 25%, comment | ok |
| conflict | pm | `task.priority.change` with a wrong `expect` | `task-conflict` |
| priority-undecided | pm | `task.priority.change`, no decision | `decision-required` |
| priority | pm | `task.priority.change` with a resolved decision | ok |
| priority-reused | pm | the same decision again | `decision-consumed` |
| schedule | pm | `task.schedule` due date, label removed | ok |
| scope | pm | `task.scope.change` with a resolved decision | ok |
| review | coder | `task.update.assigned` in-review, 100% | ok |
| reassign | pm | `task.reassign` to reviewer | ok |
| close | pm | `task.close` | ok |
| after-close | reviewer | `task.update.assigned` | `task-done` |
Priority and scope are cross-role for the pm, so each needs a decision.
The pm raises it, and the operator resolves it through the broker's
human CLI binding. A tick after the start step and a tick and reconcile
at the end must each record 0 snapshots, since the only writes were the
run's own. The run then prints the event counts by kind and the snapshot
counts by source and role, read from `bus.sqlite`.
It exits 0 when every step went as expected, 1 when one didn't or the
run stopped, and 2 on a bad command line or setup file. The task stays
in the project, done, with "Safe to delete" in its description.
## The log
`out/live-run.txt`, mode 0600, holds verbs, refusal codes, counts and
task refs. It holds no token, title, description or comment. Before it
writes, the run checks the log text for every token and for the word
"bearer". A hit writes nothing and exits 3. The data directory is
removed when the run ends.