feat(maintenance): add stale-task cleanup scheme

- scripts/cleanup_stale_tasks.py: daily script that cancels open tasks
  in completed/archived workstreams; handles 307 redirects; emits a
  cleanup progress event summarising results
- Makefile: add cleanup-stale target (also suitable for cron)
- ADR-001: append Workstream Closure Protocol section — mandatory closure
  review before marking workstream completed, with task classification
  table (done/cancelled/carry-forward) and Closure Review file format
- WP-0002 + WP-0005: append Closure Review sections documenting the
  2026-03-02 cleanup run (26 stale DB rows cancelled — all were legacy
  pre-ADR-001 DB-first records; file status was already done)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-02 00:32:35 +01:00
parent 412b7e6bf8
commit d96ed44c57
5 changed files with 271 additions and 1 deletions

View File

@@ -174,6 +174,77 @@ and others as "file-authoritative."
Rejected: introduces ambiguity about which records matter; violates the
"single source of truth" principle.
## Workstream Closure Protocol
When a workstream is about to be marked `completed`, the responsible agent
MUST perform a closure review before writing the status change. This prevents
the stale-task accumulation that this ADR was designed to make detectable.
### Steps
1. **Query all non-done tasks** in the workstream via
`GET /tasks/?workstream_id=<uuid>` (filter for `todo`, `in_progress`,
`blocked`).
2. **Classify each task** into one of three outcomes:
| Outcome | Action |
|---------|--------|
| **Done** — work was completed, DB record just wasn't updated | `PATCH /tasks/{id}/ {"status": "done"}` |
| **Cancelled** — dropped, superseded, or out of scope | `PATCH /tasks/{id}/ {"status": "cancelled", "blocking_reason": "<why>"}` |
| **Carry-forward** — genuinely unfinished, belongs in the next run | Leave open; note in closure review; trigger new workplan |
3. **Append a `## Closure Review` section** to the workplan file:
```markdown
## Closure Review — YYYY-MM-DD
**Outcome:** All tasks completed / N tasks carried forward / N tasks dropped.
### Completed (DB updated)
- TASK-ID — title
### Cancelled (dropped)
| Task | Reason |
|------|--------|
| TASK-ID — title | Superseded by X |
### Carried forward
| Task | Target workplan |
|------|----------------|
| TASK-ID — title | CUST-WP-XXXX |
```
4. **If any tasks are carried forward**: do not mark the workstream
`completed` yet. Create the new workplan file (or amend an existing active
one), then close the current workstream.
5. **Update the workplan frontmatter** `status: completed` and `updated:` date.
6. **Mark the workstream `completed`** in the state hub via MCP or API.
### Daily Stale-Task Cleanup
As a safety net for cases where the closure review was skipped or incomplete,
a cleanup script cancels any surviving open tasks in completed/archived
workstreams:
```bash
cd ~/the-custodian/state-hub
make cleanup-stale # run immediately
# or add to cron:
# 0 3 * * * cd ~/the-custodian/state-hub && make cleanup-stale
```
The script (`scripts/cleanup_stale_tasks.py`) emits a `cleanup` progress event
recording which tasks were cancelled and in which workstreams. Tasks cancelled
by the cleanup carry a `blocking_reason` noting they should be verified against
the workplan file.
The closure review is the primary mechanism; the cleanup is the fallback. If
the cleanup regularly cancels tasks, it signals that closure reviews are being
skipped — that is the process failure to address, not just the stale tasks.
## Related
- Custodian Constitution v0.1 §2 (Powers) — canon changes require review gate