Assigning is the only write a manager makes in this product. Everything about this flow should protect that.
Files you own
src/flows/assignment.flow.ts (new), added to dulyFlows in src/flows/index.tstest/assignment-fanout.test.ts (new)
The flow
type: 'record_change' on duly_assignment, firing when status transitions to 'dispatched'. Runs runAs: 'system' — the assignees are not the actor.
A loop node over record.assignees creating one duly_task per name:
| Task field | Value |
|---|
subject | the assignment's subject |
owner | the iterated assignee |
business_unit | that assignee's unit |
assignment | the assignment record |
source | 'assigned' |
due_date | the assignment's due_date |
visible_from | same as due_date — an assignment has no lead time to spread |
period_key | empty. An assignment has no period; the unique dispatch index does not apply to it |
status | 'open' |
Then, only ifrecord.needs_collection is true, create one further task owned by the assigner ("follow up once everyone is in"). A manager who assigns work must not automatically inherit a to-do list from having assigned it — that is the opt-in.
Why N tasks and not one shared task
Five names on one record produces something everybody can see and nobody owns, and it is the most reliable way a task tool starts being ignored. Five records, one owner each, each updated only by its owner. The manager's "3 of 5" is a rollup over the children (duly_assignment.task_count already exists as a Field.summary) — computed on read, maintained by nobody.
Idempotency
Re-saving a dispatched assignment must not fan out again. Guard on existing duly_task rows for the assignment. Adding a name to assignees on an already-dispatched assignment should create only the missing task — check per assignee, not per assignment.
Note the unique index on duly_task covers (duty, owner, period_key), which is all null/empty here, so it will not protect you. The guard has to be explicit.
Do not
- create a status-rollup field the flow writes into
- give the assigner a task unless
needs_collection is set - set
period_key to anything
Acceptance
- dispatching to 5 assignees creates exactly 5 tasks, each with one owner and
source: 'assigned' task_count reads 5 without anything having written it- re-saving the dispatched assignment creates 0 more
- adding a 6th assignee and re-dispatching creates exactly 1
needs_collection: false creates no assigner task; true creates exactly one- flow conditions use
record.<field>; pnpm validate passes
Gates
pnpm validate && pnpm typecheck && pnpm test && pnpm build.
Assigning is the only write a manager makes in this product. Everything about this flow should protect that.
Files you own
src/flows/assignment.flow.ts(new), added todulyFlowsinsrc/flows/index.tstest/assignment-fanout.test.ts(new)The flow
type: 'record_change'onduly_assignment, firing whenstatustransitions to'dispatched'. RunsrunAs: 'system'— the assignees are not the actor.A
loopnode overrecord.assigneescreating oneduly_taskper name:subjectsubjectownerbusiness_unitassignmentsource'assigned'due_datedue_datevisible_fromdue_date— an assignment has no lead time to spreadperiod_keystatus'open'Then, only if
record.needs_collectionis true, create one further task owned by the assigner ("follow up once everyone is in"). A manager who assigns work must not automatically inherit a to-do list from having assigned it — that is the opt-in.Why N tasks and not one shared task
Five names on one record produces something everybody can see and nobody owns, and it is the most reliable way a task tool starts being ignored. Five records, one owner each, each updated only by its owner. The manager's "3 of 5" is a rollup over the children (
duly_assignment.task_countalready exists as aField.summary) — computed on read, maintained by nobody.Idempotency
Re-saving a dispatched assignment must not fan out again. Guard on existing
duly_taskrows for the assignment. Adding a name toassigneeson an already-dispatched assignment should create only the missing task — check per assignee, not per assignment.Note the unique index on
duly_taskcovers(duty, owner, period_key), which is all null/empty here, so it will not protect you. The guard has to be explicit.Do not
needs_collectionis setperiod_keyto anythingAcceptance
source: 'assigned'task_countreads 5 without anything having written itneeds_collection: falsecreates no assigner task;truecreates exactly onerecord.<field>;pnpm validatepassesGates
pnpm validate && pnpm typecheck && pnpm test && pnpm build.