You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
duly_task.late_after (due_date + duty.grace_days) is stamped once, by whoever creates the task:
The dispatcher stamps it with the duty's own grace (DISPATCH_DUTY_FIELDS now reads grace_days).
Everything else gets task.hook.ts's fallback, late_after = due_date — zero grace. That is correct for the assignment fan-out (assignment.flow.ts creates tasks with duty unset) and for a member's own task: no duty governs the row, and zero grace is exactly how the overdue escalation already reads it.
The gap
It is not correct for a task created by hand on a duty that grants grace — which today means every one_off duty. #61 deliberately keeps grace_days on the one-off form ("a one-off duty's task has a real due date to be late against"), and a one-off is dispatched by hand, so the two rules meet in the wrong place:
So for a one-off duty granting 7 days, the owner gets no escalation email for a week — correct — and appears on the Late list the morning after the due date — wrong. That is the same "one system, two answers about the same person" shape #52 was filed against, surviving in the corner #52 does not reach.
Live in the demo today: Commissioning file handover is form: 'one_off' with graceDays: 7 (src/data/demo-catalog.ts), and its single task is written directly by src/data/demo-assignments.ts. It carries late_after = due_date.
Why the hook cannot just fix it
task.hook.ts is one self-contained function on purpose: objectstack build lowers an inline handler into a metadata body that ships without its module scope, and the handler has no engine to read duly_duty with. Reaching the duty from there is not a small change to this file — it is a different mechanism.
Options (not adjudicated here)
A record-triggered flow on duly_task insert: get_record the duty, stamp late_after when the row carries a duty and no stamp. runAs: 'system' is already the pattern in assignment.flow.ts, and CEL has addDays. Costs a flow, and puts a second writer on a write-once column — it would need the same "fill a blank, never rewrite" discipline the hook leg has.
A producer-side stamp: whoever creates a one-off duty's task computes it, the way the dispatcher does. Cheap where there is a producer in code; there is no such producer for a task created through the form.
Option 3 is the enforce-or-remove answer and option 1 is the make-it-true answer; the choice is a product call about whether a one-off duty is allowed to grant grace at all.
Not a regression
Before #52 the late view was grace-free for every task, so this row behaved exactly as it does now. #52 fixes the ~178 dispatched tasks in the demo and leaves this one where it was.
Found while implementing #52; filed rather than fixed because the fix is a producer this card does not touch.
What #52 established
duly_task.late_after(due_date + duty.grace_days) is stamped once, by whoever creates the task:DISPATCH_DUTY_FIELDSnow readsgrace_days).task.hook.ts's fallback,late_after = due_date— zero grace. That is correct for the assignment fan-out (assignment.flow.tscreates tasks withdutyunset) and for a member's own task: no duty governs the row, and zero grace is exactly how the overdue escalation already reads it.The gap
It is not correct for a task created by hand on a duty that grants grace — which today means every
one_offduty.#61deliberately keepsgrace_dayson the one-off form ("a one-off duty's task has a real due date to be late against"), and a one-off is dispatched by hand, so the two rules meet in the wrong place:reminders.flow.ts)duty.grace_daysthrough aget_recorddue_date + grace + 1lateview /completed_late(after #52)late_afterdue_date + 1So for a one-off duty granting 7 days, the owner gets no escalation email for a week — correct — and appears on the Late list the morning after the due date — wrong. That is the same "one system, two answers about the same person" shape #52 was filed against, surviving in the corner #52 does not reach.
Live in the demo today:
Commissioning file handoverisform: 'one_off'withgraceDays: 7(src/data/demo-catalog.ts), and its single task is written directly bysrc/data/demo-assignments.ts. It carrieslate_after = due_date.Why the hook cannot just fix it
task.hook.tsis one self-contained function on purpose:objectstack buildlowers an inline handler into a metadatabodythat ships without its module scope, and the handler has no engine to readduly_dutywith. Reaching the duty from there is not a small change to this file — it is a different mechanism.Options (not adjudicated here)
duly_taskinsert:get_recordthe duty, stamplate_afterwhen the row carries a duty and no stamp.runAs: 'system'is already the pattern inassignment.flow.ts, and CEL hasaddDays. Costs a flow, and puts a second writer on a write-once column — it would need the same "fill a blank, never rewrite" discipline the hook leg has.one_off— the converse of A standing duty carries a frequency — "Standing / Monthly" reads as if it dispatches #61: if no producer can apply it,standing_no_grace_daysgrows a sibling and the field is blank for one-off too. Honest and smallest, but it removes a real concept from a form that legitimately has a due date.Option 3 is the enforce-or-remove answer and option 1 is the make-it-true answer; the choice is a product call about whether a one-off duty is allowed to grant grace at all.
Not a regression
Before #52 the
lateview was grace-free for every task, so this row behaved exactly as it does now. #52 fixes the ~178 dispatched tasks in the demo and leaves this one where it was.