mirror of
https://github.com/andrewyng/openworker.git
synced 2026-09-13 07:40:18 +00:00
Address action-demanding transitions to the assignee; neutral verdict label
Send-back/unblock/cancel by someone else now land in the worker's queue (only cancel did — send-backs woke nobody); done stays unaddressed. The Request changes button drops the send-to-X phrasing — it's a board write, delivery is the queue's job.
This commit is contained in:
+13
-4
@@ -545,12 +545,21 @@ class TeamStore:
|
||||
f"illegal transition {current.value} → {target.value}"
|
||||
)
|
||||
self._check_transition_authority(actor, item, current, target)
|
||||
# Canceling an assigned item ADDRESSES the notice to its assignee — the
|
||||
# top-priority queue entry whose delivery (or in-flight interrupt) makes
|
||||
# the worker actually stop, instead of finishing into the void.
|
||||
# When someone ELSE moves your assigned item to a state that needs
|
||||
# YOUR action, the event is addressed to you: a send-back
|
||||
# (review→in_progress with feedback), an unblock, a cancel (whose
|
||||
# delivery/in-flight interrupt makes the worker actually stop). This
|
||||
# is a board write, not a message — delivery is the queue projection
|
||||
# doing its job. DONE is deliberately unaddressed: waking a worker to
|
||||
# say its finished item is finished would burn a turn for nothing.
|
||||
action_needed = {
|
||||
ItemState.IN_PROGRESS,
|
||||
ItemState.BLOCKED,
|
||||
ItemState.CANCELED,
|
||||
}
|
||||
recipient = (
|
||||
item["assignee"]
|
||||
if target is ItemState.CANCELED
|
||||
if target in action_needed
|
||||
and item["assignee"]
|
||||
and item["assignee"] != actor.id
|
||||
else None
|
||||
|
||||
@@ -111,7 +111,7 @@ test("item detail: timeline with attachment, worker link, request changes", asyn
|
||||
// Request changes… discloses a comment box; sending returns the item to work
|
||||
await detail.getByRole("button", { name: "Request changes…" }).click();
|
||||
await detail.getByPlaceholder("What needs to change?").fill("totals drift on Tom");
|
||||
await detail.getByRole("button", { name: "Send to security" }).click();
|
||||
await detail.getByRole("button", { name: "Request changes", exact: true }).click();
|
||||
await expect(detail).toContainText("In progress");
|
||||
// switching rows switches the pane
|
||||
await page.getByTestId("board-item-3").click();
|
||||
|
||||
@@ -358,6 +358,8 @@ function DetailActions({
|
||||
onChange={(e) => setChangesText(e.target.value)}
|
||||
/>
|
||||
<div className="board-changes-row">
|
||||
{/* A board write, not a message: review → in_progress with the
|
||||
comment attached; delivery to the assignee is the queue's job. */}
|
||||
<button
|
||||
className="board-btn primary"
|
||||
disabled={!changesText.trim()}
|
||||
@@ -365,7 +367,7 @@ function DetailActions({
|
||||
onTransition(detail.id, "in_progress", changesText.trim())
|
||||
}
|
||||
>
|
||||
Send to {detail.assignee || "worker"}
|
||||
Request changes
|
||||
</button>
|
||||
<button className="board-btn ghost" onClick={() => setChangesOpen(false)}>
|
||||
Cancel
|
||||
|
||||
@@ -408,6 +408,27 @@ def test_item_detail_timeline_and_blocker_fact(manager):
|
||||
assert blocked["blocker"] == "need the staging tfvars"
|
||||
|
||||
|
||||
def test_transitions_by_others_address_the_assignee(store):
|
||||
"""A send-back or unblock is a board write, not a message — but the queue
|
||||
projection addresses it to the worker whose action it demands (owner
|
||||
question 2026-08-17 exposed the gap: only cancel was addressed)."""
|
||||
item_id = assigned(store)
|
||||
store.consume("swe-worker", store.pending_for("swe-worker")[-1]["seq"])
|
||||
store.transition(SPACE, WORKER, item_id, "in_progress")
|
||||
store.transition(SPACE, WORKER, item_id, "review", comment="ready")
|
||||
assert store.pending_for("swe-worker") == [] # own moves never self-address
|
||||
# the lead's send-back reaches the worker's queue, feedback attached
|
||||
store.transition(SPACE, LEAD, item_id, "in_progress", comment="totals drift")
|
||||
pending = store.pending_for("swe-worker")
|
||||
assert [e["payload"]["to"] for e in pending] == ["in_progress"]
|
||||
assert pending[-1]["payload"]["comment"] == "totals drift"
|
||||
store.consume("swe-worker", pending[-1]["seq"])
|
||||
# ...but done is deliberately unaddressed — no wake to hear "it's finished"
|
||||
store.transition(SPACE, WORKER, item_id, "review", comment="fixed")
|
||||
store.transition(SPACE, LEAD, item_id, "done")
|
||||
assert store.pending_for("swe-worker") == []
|
||||
|
||||
|
||||
def test_cancel_notice_is_addressed_to_the_assignee(store):
|
||||
item_id = assigned(store)
|
||||
store.consume("swe-worker", store.pending_for("swe-worker")[-1]["seq"])
|
||||
|
||||
Reference in New Issue
Block a user