fix(gateway): heartbeat on content-write success, single-claimant gate, progress soft-warn

commit()/progress()/note() now refresh last_heartbeat_at on the success
path (best-effort, suppressed), not only on rejection — an actively
writing agent no longer looks idle to the reaper between verb successes.

commit()/progress() verify the caller holds the active claim
(active_claimant_id), not merely the historical assigned_to, so a reaped
or handed-off assignee can no longer write onto a freed task; a non-holder
gets a not_authorized envelope with a clear remediate.

progress() with no plan_step on a task that has steps is accepted (product
decision for narrative mid-step updates) and logs a soft warning instead of
rejecting.
This commit is contained in:
Renn F
2026-06-03 18:44:33 +02:00
parent 61e80495c3
commit 2c96bd09f6
5 changed files with 415 additions and 26 deletions
+3
View File
@@ -60,6 +60,7 @@ async def test_commit_prefixes_with_task_id_short() -> None:
t = MagicMock(
id=tid,
assigned_to=aid,
active_claimant_id=aid,
plan="x",
status="in_progress",
branch_name="feature/backend/abcd1234",
@@ -95,6 +96,7 @@ async def test_commit_strips_then_re_adds_prefix() -> None:
t = MagicMock(
id=tid,
assigned_to=aid,
active_claimant_id=aid,
plan="x",
status="in_progress",
branch_name="feature/backend/abcd1234",
@@ -127,6 +129,7 @@ async def test_commit_prefix_collapses_multiple_spaces() -> None:
t = MagicMock(
id=tid,
assigned_to=aid,
active_claimant_id=aid,
plan="x",
status="in_progress",
branch_name="feature/backend/abcd1234",