Problem
A delegation widens which work a person reaches, and every door that asks "is this the viewer's?" reads it: the inbox and the pending-actions list widen the assignee they match (WorkItemManager::getAccountVisibilityCondition()), and so does the per-task check (isAssigneeOrStandIn()). One door does not. WorkItemManager::getWorkedByAccountCondition(), which backs isAccountParticipant() and isAccountTaskParticipant(), matches the acting account's own uid alone.
That condition is the whole of the default read access. Under ReadAccess::WORKERS, the shipped default, a run's read-only page admits whoever holds or completed one of its steps. So a stand-in who has just been handed a colleague's task can open that task and complete it, and cannot read the run it belongs to.
The visible symptom is on the pending-actions list, whose Process column links the run the listed work item is said to entitle the reader to. For a delegate that link is not open, so the run's name renders as plain text where the person they cover for sees a link.
The widest scope hides the gap rather than sharing it: ReadAccess::AUDIENCE ORs in getAccountVisibilityCondition(), which does read cover, so it admits the stand-in. The default scope is the one with the hole.
Proposed resolution
Match assignee and completer against AssignmentMatcher::getSelfAndDelegatorIds(), the collaborator WorkItemManager already injects and already uses at the two doors that do read cover. Both callers of the condition are fixed by the one change, so the per-run and the per-step question cannot come to disagree.
Cover is read at the moment of asking, so what it widens lapses with the delegation, and the verdicts built on this already declare that window through AssignmentMatcher::calculateCacheableMetadata().
One consumer did not declare it, and is corrected with it. The webform handler's render gate asks whether a step may be shown; for a branch that has moved on that answer is isAccountTaskParticipant(), which now reads cover, and the gate's verdict is reduced to a boolean and dropped, as the gateway contract says it is. That build declared the token list and a hand-named user context, which partitions per account without ever expiring, so a page cached while somebody covered the holder went on being served to them after the cover lapsed. It now asks the matcher what a verdict about the account rests on, replacing the hand-named subset rather than adding a second one.
User interface changes
A delegate now reads the run behind the work they cover, at the same address and under the same read access its holder reads it by. Nothing else changes: cover still grants no permission of its own, and a person who covers nobody is unaffected.
API changes
None. WorkItemManagerInterface::isAccountParticipant() keeps its signature and answers more accurately for a delegate.
Data model changes
None.
AI-Generated: Yes (Claude Code was used to help draft this issue summary and to write the code and tests on the merge request. I reviewed and ran the work myself before posting it.)
Issue fork orchestra-3624430
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #3
mably commentedComment #4
mably commentedComment #6
mably commented