Tasks and QA checks¶
luplo models tasks and QA checks as item types on top of the
substrate (see Data model), not as dedicated tables.
The domain logic — status machines, block handling, coverage, target
arrays — lives in typed context JSONB fields that are strictly
validated via the item_types.schema.
This guide is the working pattern: create a task, progress it, block it, attach QA to it, and close the loop.
Task state machine¶
proposed ──► in_progress ──► done
│ │
│ ├─► blocked (writes a decision item explaining why)
│ │
└─────────────┴─► skipped (terminal, human-declined)
Invariant: at most one task per work unit may be in_progress at
any time. The invariant is enforced as a domain check via
SELECT ... FOR UPDATE, not as a partial UNIQUE index — see P7 in the
decision log for why the obvious index doesn’t compose with the
supersede pattern.
Add a task¶
uv run lp task add "Add JWT validation middleware" \
--wu a85a4555 \
--system auth \
--body "Register middleware on the protected router; verify signature, \
expiry, and issuer before passing to handlers."
--wu is required: tasks always belong to a work unit.
Advance it¶
uv run lp task start <task-id> # proposed → in_progress
uv run lp task done <task-id> # in_progress → done
uv run lp task skip <task-id> # any → skipped
start refuses if another task in the same work unit is already
in_progress. Finish the first task or skip it before starting the
next.
List and find¶
uv run lp task ls --wu a85a4555
uv run lp task ls --wu a85a4555 --status in_progress
uv run lp task in-progress --wu a85a4555 # the one, if any
Order is sort_order within the work unit. Reorder with:
uv run lp task reorder <task-id-1> <task-id-2> <task-id-3>
Reorder writes an in-place sort_order update plus one audit row,
not a supersede. See P10 in the decision log — sort_order is
presentation state and shouldn’t bloat the chain.
Block a task¶
uv run lp task blocked <task-id> \
--reason "Waiting on logging library decision (stdlib vs structlog)."
blocked does three things in one transaction:
Transitions the task to
blockedstatus and storesblocked_reason.Automatically creates a
decisionitem with:title—Task blocked: <task title>body— the reason you passedwork_unit_id— inherited from the tasksystem_ids— inherited from the task
Writes an
audit_logrow (task.blocked).
The auto-generated decision is how a blocked task surfaces in search:
reasons stored only in task fields would not be found by
lp items search. Promoting to an item puts the block into the
decision memory.
QA checks¶
A QA check (item_type='qa_check') is a bounded verification task
— manual or automated — against a target task or item. Where tasks
are “do this”, QA checks are “verify this was done right”.
Coverage and area¶
QA checks carry two classification fields in their context:
coverage— how it can be verified.auto_partial— at least one part is automated; a human must still verify edges.human_only— cannot be automated meaningfully. This is the conservative default for unclassified checks.
area— what aspect is being verified, any subset of:vfx,sfx,ux,edge_case,perf,a11y,sec.
Add a QA check¶
uv run lp qa add "Login form responsive at 320px viewport" \
--coverage human_only \
--area ux,a11y \
--task <task-id-1> --task <task-id-2> \
--wu a85a4555 \
--body "Visit /login at 320px width. Verify layout doesn't overflow, \
focus order is top-to-bottom, and keyboard submit works."
Target many tasks or items via repeated --task / --item — the
arrays land in context.target_task_ids / context.target_item_ids
with GIN indexes on both so lookup in either direction is fast.
Drive it to a terminal state¶
uv run lp qa start <qa-id> # pending → in_progress
uv run lp qa pass <qa-id> # in_progress → passed
uv run lp qa fail <qa-id> # in_progress → failed
uv run lp qa block <qa-id> # in_progress → blocked
uv run lp qa assign <qa-id> --to <actor-uuid>
List QA by target¶
uv run lp qa ls --status pending
uv run lp qa ls --task <task-id> # pending QA for a task
uv run lp qa ls --item <item-id> # pending QA for an item
uv run lp qa ls --wu <work-unit-id>
Revalidation¶
When an item that a QA check targets is superseded by a new edit, the QA check is flagged for re-verification. This is one of the two documented in-place updates that skip supersede (the other is task reorder): the re-verification trigger is a system-initiated “please look again”, not a human decision, and re-supersedeing every affected check would create write amplification and chain bloat. See P9 for the full reasoning.
Closing a work unit with open tasks¶
uv run lp work close <work-id>
# error: 1 task still in_progress — use --force or finish the task.
uv run lp work close <work-id> --force # closes anyway, audit records the override
Deferred (not in v0.5.x)¶
Automatic task ↔ item linkage on
task done. Proposed as v0.6 — an LLM pass over the work-unit diff to suggest new decision items. Until then, add follow-up items manually.Supersede of tasks via natural-language edit. You can supersede a task with
lp items ... --supersedes, but there is no dedicatedlp task editsurface yet.