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

Command icon 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

mably created an issue. See original summary.

mably’s picture

Status: Active » Needs review
mably’s picture

Issue summary: View changes

  • mably committed cebfd800 on 1.x
    fix: #3624430 Let a stand-in read the run behind the work they cover
    
    By...
mably’s picture

Status: Needs review » Fixed

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.