mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
fix: read the real commit key (hash) and audit the restore-unblock path
- The auto-pause checkpoint and _extract_first_commit_sha read commit dicts by key 'sha', but persisted commits are keyed 'hash' (CommitRef.hash) — the prior change stopped the crash but silently dropped every ref. Read 'hash' (sha fallback) at both sites; the test now uses the production dict shape so the regression can't hide. - unblock_with_restore set status directly and skipped the audit log; emit the status-transition audit there too, like the other direct-set paths.
This commit is contained in:
@@ -1503,14 +1503,14 @@ class Choreographer:
|
||||
|
||||
@staticmethod
|
||||
def _extract_first_commit_sha(t: Any) -> str | None:
|
||||
"""Read the first commit sha off the task, dict or model alike."""
|
||||
"""Read the first commit hash off the task, dict or model alike."""
|
||||
commits: list[Any] = list(getattr(t, "commits", []) or [])
|
||||
if not commits:
|
||||
return None
|
||||
first = commits[0]
|
||||
if isinstance(first, dict):
|
||||
return first.get("sha")
|
||||
return getattr(first, "sha", None)
|
||||
return first.get("hash") or first.get("sha")
|
||||
return getattr(first, "hash", None) or getattr(first, "sha", None)
|
||||
|
||||
@staticmethod
|
||||
def _already_addressed_criteria(existing_status: list[dict[str, Any]]) -> set[str]:
|
||||
@@ -2710,9 +2710,12 @@ class Choreographer:
|
||||
try:
|
||||
commits = task.commits or []
|
||||
# commits may be hydrated as CommitRef objects or as plain dicts
|
||||
# (JSON column round-trip); tolerate both rather than assuming `.sha`.
|
||||
# (JSON column round-trip); the identifier field is `hash` (a stray
|
||||
# `sha` only ever appears on a gateway return value, never persisted).
|
||||
commit_refs = [
|
||||
c.get("sha") if isinstance(c, dict) else getattr(c, "sha", None)
|
||||
(c.get("hash") or c.get("sha"))
|
||||
if isinstance(c, dict)
|
||||
else (getattr(c, "hash", None) or getattr(c, "sha", None))
|
||||
for c in commits[-3:]
|
||||
]
|
||||
commit_refs = [ref for ref in commit_refs if ref]
|
||||
|
||||
@@ -5643,6 +5643,12 @@ class TaskService(BaseService):
|
||||
except ValueError:
|
||||
return await self.unblock(task_id, agent_role="cell_pm")
|
||||
|
||||
pre_status = (
|
||||
task.status.value
|
||||
if isinstance(task.status, TaskStatus)
|
||||
else str(task.status)
|
||||
)
|
||||
restored_owner = cast("Any", task.pre_block_assignee or task.claimed_by)
|
||||
task.status = restored_status
|
||||
if task.pre_block_assignee:
|
||||
task.assigned_to = cast("Any", task.pre_block_assignee)
|
||||
@@ -5653,6 +5659,16 @@ class TaskService(BaseService):
|
||||
task.blocker_resolver_type = None
|
||||
task.blocker_raised_by = None
|
||||
await self.session.flush()
|
||||
# This restore path sets the status directly (bypassing the strict
|
||||
# transition validator), so emit the audit explicitly — no status
|
||||
# change may skip the audit log.
|
||||
self._emit_status_transition_audit(
|
||||
task,
|
||||
from_status=pre_status,
|
||||
to_status=restored_status.value,
|
||||
agent_role=None,
|
||||
audit_agent_id=restored_owner,
|
||||
)
|
||||
return task
|
||||
|
||||
async def cell_pm_complete(
|
||||
|
||||
Reference in New Issue
Block a user