Let the operator act on a leased task from the browser

The store fences TaskReleased, TaskBlocked and TaskCompleted on a leased task
against the live harness_id and lease_epoch. The UI action handler sent
neither, so all three returned 409 on exactly the tasks the UI listed them as
enabled for. Found live: eight block attempts against a stuck run at a stable
version, all 409 "task version conflict".

The fence is there to reject a stale writer, not the operator. Carry the lease
read at the top of the handler. The version CAS on the append still rejects a
racing write.

Also initialise body when the request carries none. The block path wrote
block_reason into a nil map.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011xsXyr5J1RACo71YeKG3Pu
This commit is contained in:
2026-08-27 18:22:53 +04:00
parent dea56e4bcd
commit efd0a5e3cd
2 changed files with 70 additions and 0 deletions
+13
View File
@@ -422,11 +422,24 @@ func (s Server) action(w http.ResponseWriter, r *http.Request, id, action string
http.Error(w, "unknown action", 404)
return
}
if body == nil {
body = map[string]any{}
}
if action == "block" {
// A browser-created block is an explicit operator decision. Preserve
// that fact even if its prose happens to contain a system keyword.
body["block_reason"] = string(domain.BlockReasonOperator)
}
// The store fences release/block/complete on a leased task against the
// live lease (validateTransition, the "TaskBlocked" case). That fence
// exists to stop a *stale* writer, not the operator, and the browser never
// had a way to satisfy it: all three actions returned 409 on exactly the
// tasks the UI offered them for. Carry the lease we just read. The version
// CAS on the append still rejects a racing write.
if t.Lease != nil {
body["harness_id"] = t.Lease.HarnessID
body["lease_epoch"] = t.Lease.Epoch
}
b, _ := json.Marshal(body)
e := domain.Event{ID: domain.NewID(), Type: typ, TaskID: id, Version: t.Version + 1, Payload: b, Surface: string(authz.Web)}
if err := s.Store.Append(e); err != nil {