hermes: recover structured provider completion records
This commit is contained in:
parent
9cc49dedeb
commit
3231e1af07
@ -22,6 +22,26 @@ PART = re.compile(r"[A-Za-z0-9][A-Za-z0-9_.-]{0,127}\Z")
|
||||
RESULT_FIELDS = frozenset({"status", "summary", "changed_files", "tests_run", "artifacts", "findings", "blockers"})
|
||||
|
||||
|
||||
def _result_candidate(value: Any) -> dict[str, Any] | None:
|
||||
"""Accept a direct result or the one canonical Claude session result field."""
|
||||
if not isinstance(value, dict):
|
||||
return None
|
||||
candidate = value.get("structured_output") if "structured_output" in value else value
|
||||
if not isinstance(candidate, dict) or set(candidate) != RESULT_FIELDS:
|
||||
return None
|
||||
if candidate.get("status") != "completed":
|
||||
return None
|
||||
if not isinstance(candidate.get("summary"), str) or not candidate["summary"].strip():
|
||||
return None
|
||||
if any(
|
||||
not isinstance(candidate.get(name), list)
|
||||
or any(not isinstance(item, str) for item in candidate[name])
|
||||
for name in RESULT_FIELDS - {"status", "summary"}
|
||||
):
|
||||
return None
|
||||
return candidate
|
||||
|
||||
|
||||
def _completed_result(assignment: dict[str, Any]) -> dict[str, Any]:
|
||||
"""Return exactly one completed result JSON object from an ordinal's safe log."""
|
||||
parts = tuple(str(assignment[name]) for name in ("board", "task_id", "run_id"))
|
||||
@ -54,13 +74,10 @@ def _completed_result(assignment: dict[str, Any]) -> dict[str, Any]:
|
||||
value = json.loads(line)
|
||||
except (UnicodeDecodeError, json.JSONDecodeError):
|
||||
continue
|
||||
if not isinstance(value, dict) or set(value) != RESULT_FIELDS or value.get("status") != "completed":
|
||||
candidate = _result_candidate(value)
|
||||
if candidate is None:
|
||||
continue
|
||||
if not isinstance(value.get("summary"), str) or not value["summary"].strip():
|
||||
continue
|
||||
if any(not isinstance(value.get(name), list) or any(not isinstance(item, str) for item in value[name]) for name in RESULT_FIELDS - {"status", "summary"}):
|
||||
continue
|
||||
candidates[canonical_json(value)] = value
|
||||
candidates[canonical_json(candidate)] = candidate
|
||||
if len(candidates) != 1:
|
||||
raise ProtocolError("bootstrap result evidence is absent or ambiguous")
|
||||
return next(iter(candidates.values()))
|
||||
|
||||
@ -444,6 +444,21 @@ def test_mediator_bootstrap_recovers_one_unique_completed_log_result(tmp_path, m
|
||||
assert seen["structured"] == completed and seen["request"]["title"] == completed["summary"]
|
||||
|
||||
|
||||
def test_mediator_bootstrap_recovers_one_unique_nested_session_result(tmp_path, monkeypatch):
|
||||
"""A Claude stream carries the completed result under structured_output."""
|
||||
exact = protocol.sign_envelope(
|
||||
KEY, "assignment", binding(), payload(root_task_id="t_deadbeef", continuation_kind="repair")
|
||||
)
|
||||
log = tmp_path / "session-state" / "metis" / "t_deadbeef" / "42.log"
|
||||
log.parent.mkdir(parents=True)
|
||||
completed = dict(RESULT)
|
||||
event = {"event": "result", "structured_output": completed}
|
||||
log.write_bytes(protocol.canonical_json(event) + b"\n" + protocol.canonical_json(event) + b"\n")
|
||||
monkeypatch.setattr(resume_bootstrap, "ROOT", tmp_path)
|
||||
|
||||
assert resume_bootstrap._completed_result(exact) == completed
|
||||
|
||||
|
||||
def test_mediator_bootstrap_rejects_a_symlinked_log_ancestor(tmp_path, monkeypatch):
|
||||
exact = protocol.sign_envelope(KEY, "assignment", binding(), payload())
|
||||
outside = tmp_path / "outside"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user