Repository navigation
feat(tasks): ask a decision on a task and answer it with task.decide - #163
chriscoveries wants to merge 4 commits into
Conversation
A task that groups subtasks has to be closed by hand once the last of them
is done, and nothing reopens it when one of them comes back, so a parent
can say done while work under it is open again.
A task can now set `rollup: true`. Its completion then follows its
subtasks. When the last subtask that is not archived is done, the task
moves to done in the same write. When a subtask leaves done, or a new one
is added, while the task is done, the task moves back to active (or to
planned if no subtask is active), and the same rule applies to its own
parent. A rollup task cannot be moved to done while a subtask is open.
Only a change to the subtasks, or setting the flag, triggers this, so a
direct move of the task itself stands. Each automatic move is listed in
the change entry of the operation that caused it
(`rollup: [{task_id, status}]`), so the `context.sync` event records it.
The flag is off unless set, and existing tasks are unchanged.
A task that waits on the user has no place for the question, and no way
for the user to answer it: the answer travels as chat, someone moves the
task to done by hand, and nothing records what was decided, by whom or
when.
A task may now carry `decision_request {question, options?,
recommendation?}`, set on create or update. `task.decide {task_id,
choice, text?, source?}` answers it: it records `decision {choice, text,
source, by, at}` on the task, sets waiting_on to none, moves the task to
done, and appends a `decision` coordination note with the task's
`task_id`. When the request lists options, the choice must be one of
them. A task is decided once; after that its request cannot change, and a
create cannot carry a decision of its own. A rollup task is decided only
once its subtasks are closed, the same check as moving it to done.
The user may decide, and so may actors listed in the new group setting
`task_approvers` (stored as `tasks.approvers`); an empty or absent list
names none. `system` and an empty `by` take the user's path here, as they
do across the permission model, for deciding and for changing the list.
The foreman is not an approver unless listed, and only the user's path
may change the list, so a foreman cannot approve its own requests. The
summary view of `context_get` shows both fields, and the MCP `cccc_task`
tool gains a `decide` action and passes `decision_request` through.
|
I am deferring this along with its #157 dependency. The main semantic issue is that every Please separate recording a decision from completing the work, or explicitly model a decision-only task whose deliverable is the decision itself. Keep the decision author and audit note, but make the subsequent work transition explicit. The approver policy also needs a usable Web path to inspect the question/options and answer as the user; adding the write contract without that leaves the central workflow incomplete. Please propose that smaller end-to-end flow independently of automatic subtask rollup before extending the permission/settings surface further. |
Brings in caller task ids (`client_request_id`, `parent_ref`, `blocked_by_refs`) and the ordered persistence of task targets. - context/apply.rs: `apply_all` keeps main's `task.create` path, which records the created or replayed task id, and wraps every task op in the rollup shape check, so a create's rollup moves land in the same change entry. - context/task_apply.rs: `create` is main's, with the `rollup` flag validated before the task is built. - MCP mapping, tool schema and the Context ops standard list both sets of fields. - Tests: main's versions, with the rollup tests appended unchanged. - Persistence (yaml_storage.rs, yaml_files.rs) is main's, unchanged.
This branch builds on feat/task-rollup, which now includes upstream main. Merging that head brings in main, with the same resolutions, so the two stay one stack. - context/task_apply.rs: `create` is main's (caller ids and refs), with the rollup flag check and this branch's `decision_request` validation and the refusal of a direct `decision` kept ahead of building the task. - MCP mapping: the create field list keeps main's caller-id fields, then `rollup` and `decision_request`; both mapping tests are kept whole. - Everything else merged cleanly. This branch's own changes on top of feat/task-rollup are unchanged line for line, and persistence (yaml_storage.rs, yaml_files.rs) is main's.
Depends on #157 (subtask rollup). The branch is cut from #157's head, so
its one commit on top is this change alone; it lands after #157.
A task can wait on the user (
waiting_on: "user"), but it has no placefor the question, and nothing answers it. The answer goes by chat,
someone moves the task to done by hand, and the board does not record
what was decided, by whom or when.
A task may now carry
decision_request {question, options?, recommendation?}, set on create or update.task.decide {task_id, choice, text?, source?}answers it. It recordsdecision {choice, text, source, by, at}on the task, setswaiting_ontonone, moves the taskto done, and appends a
decisioncoordination note that carries thetask's
task_id. When the request lists options, the choice must be oneof them. A task is decided once: after that its request cannot change, and
a create cannot carry a decision of its own. A rollup task (#157) is
decided only once its subtasks are closed, the same check as moving it to
done.
The user may decide, and so may actors listed in a new group setting,
task_approvers(stored astasks.approvers); an empty or absent listnames none. As everywhere in cccc's permission model,
systemand anempty
bytake the user's path here, both for deciding and for changingthe list. The foreman is not an approver unless it is listed, and only
the user's path may change the list, so a foreman cannot approve its own
requests. The summary view of
context_getnow showsdecision_requestand
decision. The MCPcccc_tasktool gains adecideaction andpasses
decision_requestthrough.How we built it. Base: #157's head
9cd296f8.task_apply::decidechecks the request and the choice, records the decision and returns the
note, which
apply.rsadds with the existingadd_note. Its open-subtaskcheck is #157's, moved into one helper that
task.moveandtask.decideshare. Create and update check
decision_request. The daemon's contextauthorization admits
task.decidefor the user's path and for listedapprovers;
group_settings_updatemapstask_approversto its section andlets only the user's path change it. The MCP mapping, the tool schema,
CCCC_CONTEXT_OPS_V1.mdand the IPC doc describe it. 11 files,+540/−15, of which 252 lines are tests.
How we tested it.
context_storage.rs), through the real context store: arequest is stored trimmed; a request without a question, an empty
option, a create carrying a decision, a choice outside the options and a
decide on a task without a request are rejected and write nothing; a
decide records the decision, clears
waiting_on, moves the task to doneand adds one decision note with the task id; a second decide and a
change to the decided request are rejected; a rollup task with an open
subtask cannot be decided, and deciding that subtask completes it.
integration_ops.rs), throughcontext_sync,group_settings_updateandcontext_get: the summary shows the request;the foreman and an unlisted peer are refused; the foreman cannot list
itself; with an empty list the peer is still refused;
systemlists theforeman, which then decides; an empty
bylists the peer instead, andthe foreman is refused again while the peer decides; the user,
systemand an empty
byeach decide a task; each decision records itsby,and the notes name all five tasks.
decision_request; decide maps totask.decidewith only its four fields.the tests on feat(tasks): let a task complete with its subtasks #157's head, https://github.com/chriscoveries/cccc/actions/runs/39735827187
(2685 passed, 3 failed).
rest of the core and MCP suites, the daemon's integration ops and its
settings and context unit tests (606 tests); clippy
-D warningsand rustfmt clean. At the red reference each of the three targets fails
exactly its new test.
the
cccc mcpstdio server and over the daemon socket; a peer answersthrough MCP; the user,
systemand an emptybyanswer over the socket.With 0.4.44 and upstream main, 11 of the 12 checks fail: MCP drops the
request, the summary view shows none,
decideis unknown, a foreman canlist itself as an approver, and nothing is recorded. The one that passes,
systemand an emptybychanging the list, passes only because theykeep
task_approversas an unknown flat setting that nothing reads.With this build all 12 checks pass: the summary shows the question; the foreman and an unlisted peer
are refused, and so is the peer under an empty list; the foreman cannot
list itself; a listed peer decides and the user decides; a second answer
is refused;
systemand an emptybyeach decide and each change thelist; a rollup task is refused while its subtask is open, and the peer's
decision on the subtask completes it; each decision leaves a note naming
its task.
-D warnings, nextest, Windows smoke):https://github.com/chriscoveries/cccc/actions/runs/39735823645
Tested on: claw, Debian 13, Skylake 8vCPU AVX2/AVX-512, 15G RAM.