Sub-issue of #30, which names the catalog actions. #4 has since landed three more that need the same treatment: duly_task_complete, duly_task_undo, duly_task_skip.
None declares requiredPermissions, so the ADR-0066 D4 capability gate (403 on the platform action route, mirrored as a UI hide) does not apply to any of them.
What #4 did instead, and what it does not cover
Each handler re-reads the subject under the caller's own scope and refuses when it did not come back:
conststatus=text(record.status);if(!status)throwrefuse(`Task ${id} is not available to you.`,'DULY_TASK_NOT_AVAILABLE',404);That is load-bearing — ctx.engine is system-elevated and RLS-bypassing by design — and it does close the "complete anyone's task" hole on a private object. Pinned in test/task-actions.test.ts → authorization.
What it does not do is express who is allowed to complete tasks at all as a declared capability. Those are different questions: the read-scope check answers "is this row yours to see", the capability gate answers "is completing a thing you may do". A read-only auditor with visibility over a business unit passes the first and should fail the second.
Why it was not done in #4
dulyPermissionSets is empty (objectstack validate reports Security: 0 Positions 0 Permissions). Naming a capability that no permission set grants would either hide the buttons from everyone or be inert — declared-and-unenforced, which is the shape this repo tries hardest to avoid. The vocabulary has to exist first: #8 (security model — positions, permission sets, sharing rules), which #30 is already blocked behind.
So this is deliberately queued behind the same dependency as its parent, not an oversight. Filed unassigned so it is not lost when #8 lands.
Sub-issue of #30, which names the catalog actions. #4 has since landed three more that need the same treatment:
duly_task_complete,duly_task_undo,duly_task_skip.None declares
requiredPermissions, so the ADR-0066 D4 capability gate (403 on the platform action route, mirrored as a UI hide) does not apply to any of them.What #4 did instead, and what it does not cover
Each handler re-reads the subject under the caller's own scope and refuses when it did not come back:
That is load-bearing —
ctx.engineis system-elevated and RLS-bypassing by design — and it does close the "complete anyone's task" hole on aprivateobject. Pinned intest/task-actions.test.ts→authorization.What it does not do is express who is allowed to complete tasks at all as a declared capability. Those are different questions: the read-scope check answers "is this row yours to see", the capability gate answers "is completing a thing you may do". A read-only auditor with visibility over a business unit passes the first and should fail the second.
Why it was not done in #4
dulyPermissionSetsis empty (objectstack validatereportsSecurity: 0 Positions 0 Permissions). Naming a capability that no permission set grants would either hide the buttons from everyone or be inert — declared-and-unenforced, which is the shape this repo tries hardest to avoid. The vocabulary has to exist first: #8 (security model — positions, permission sets, sharing rules), which #30 is already blocked behind.So this is deliberately queued behind the same dependency as its parent, not an oversight. Filed unassigned so it is not lost when #8 lands.