Skip to content

A task created by hand on a duty that grants grace is stamped late_after = due_date — the dispatcher is the only producer that applies grace_days #100

Description

@os-warren

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:

  • 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:

reads day one
overdue escalation (reminders.flow.ts) duty.grace_days through a get_record due_date + grace + 1
late view / completed_late (after #52) the row's late_after due_date + 1

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)

  1. 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.
  2. 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.
  3. Retire grace on 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_days grows 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.
  4. Leave it, and say so on the duty 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions