Observation from #3, filed for triage rather than as a defect — the current behaviour may well be correct, but nothing states it.
Behaviour
Inserting a task with status: 'done' is refused:
awaitdata.insert('duly_task',{subject: 'x',owner: 'u',source: 'self',status: 'done'});// ValidationError: A completed task must carry a completion timestamp.This falls out of two decisions that are each individually right:
So on the insert path there is no writer for completed_at, and completed_at_required_when_done refuses the row. status: 'done' is unreachable at creation for every non-isSystem caller. A system importer is unaffected (it can supply completed_at directly).
test/task-hook.test.ts pins this refusal deliberately, as the negative control proving the validation rule is live — so whichever way this is decided, that test is the place to change it.
Why it may be fine
Duly's tasks are dispatched as open; completion is a later transition. Already-finished ad-hoc work is what duly_log_entry is for, and the work log is deliberately unscoreable. On that reading there is no legitimate caller.
Why it may not be
source: 'self' ("Self-declared") suggests a person can raise a task they own. If any UI ever offers "add something I already did", it hard-fails with a message about timestamps that names nothing the user did wrong.
The decision
Either:
- Confirm it — creation is always
open/in_progress; no code change, and the refusal stays as the pin it is today. Worth a line in docs/product/data-model.md so the next person does not re-discover it. - Allow it — extend the hook's
beforeInsert to stamp completed_at when the incoming status is done, symmetric with the update path.
Not decided in #3 because that issue specified beforeInsert as last_update_at only, and inventing the other half would have been scope the issue did not ask for.
Observation from #3, filed for triage rather than as a defect — the current behaviour may well be correct, but nothing states it.
Behaviour
Inserting a task with
status: 'done'is refused:This falls out of two decisions that are each individually right:
completed_atisreadonly: true, so a caller cannot supply it.beforeInsertstampslast_update_atonly — per that issue's explicit specification, it does not stampcompleted_at.So on the insert path there is no writer for
completed_at, andcompleted_at_required_when_donerefuses the row.status: 'done'is unreachable at creation for every non-isSystemcaller. A system importer is unaffected (it can supplycompleted_atdirectly).test/task-hook.test.tspins this refusal deliberately, as the negative control proving the validation rule is live — so whichever way this is decided, that test is the place to change it.Why it may be fine
Duly's tasks are dispatched as
open; completion is a later transition. Already-finished ad-hoc work is whatduly_log_entryis for, and the work log is deliberately unscoreable. On that reading there is no legitimate caller.Why it may not be
source: 'self'("Self-declared") suggests a person can raise a task they own. If any UI ever offers "add something I already did", it hard-fails with a message about timestamps that names nothing the user did wrong.The decision
Either:
open/in_progress; no code change, and the refusal stays as the pin it is today. Worth a line indocs/product/data-model.mdso the next person does not re-discover it.beforeInsertto stampcompleted_atwhen the incoming status isdone, symmetric with the update path.Not decided in #3 because that issue specified
beforeInsertaslast_update_atonly, and inventing the other half would have been scope the issue did not ask for.