feat(runtime): consume governed Activity Core closes

Assistant: codex
Assistant-Model: gpt-5.6-sol
Assistant-Session: 01a06ba0-10aa-7ea0-b20a-4f3fac39efe9
This commit is contained in:
tegwick 2026-09-04 19:54:07 +02:00
parent 0e6d795aa4
commit d00ffcb402
22 changed files with 1218 additions and 148 deletions

View file

@ -25,6 +25,7 @@ from typing import Any
import httpx
from rein_aharness.intake import resolve_target_repo
from rein_aharness.repository_grant import RepositoryGrant, RepositoryGrantError
from rein_aharness.taskspec import TaskSpec, TaskSpecError
DEFAULT_ACTIVITY_CORE_URL = "http://127.0.0.1:8010"
@ -41,10 +42,12 @@ class OpsRunError(RuntimeError):
*,
action: str | None = None,
status_code: int | None = None,
code: str | None = None,
) -> None:
super().__init__(message)
self.action = action
self.status_code = status_code
self.code = code
@property
def lease_rejected(self) -> bool:
@ -73,11 +76,25 @@ class OpsRun:
approach_hint: str | None = None
harness_profile_ref: str | None = None
execution_refs: dict[str, Any] = field(default_factory=dict)
repository_grant: RepositoryGrant | None = None
result: dict[str, Any] = field(default_factory=dict)
close_disposition: str | None = None
close_intent_digest: str | None = None
raw: dict[str, Any] = field(default_factory=dict)
@classmethod
def from_api(cls, data: dict[str, Any]) -> "OpsRun":
grant = None
if data.get("repository_grant") is not None:
try:
grant = RepositoryGrant.from_mapping(data["repository_grant"])
except RepositoryGrantError as exc:
run_id = str(data.get("id") or "unknown")[:200]
raise OpsRunError(
f"ops_run {run_id} has an invalid repository_grant",
action="decode",
code="invalid_repository_grant",
) from exc
return cls(
id=str(data.get("id") or ""),
activity_definition_id=str(data.get("activity_definition_id") or ""),
@ -97,7 +114,14 @@ class OpsRun:
approach_hint=data.get("approach_hint"),
harness_profile_ref=data.get("harness_profile_ref"),
execution_refs=dict(data.get("execution_refs") or {}),
repository_grant=grant,
result=dict(data.get("result") or {}),
close_disposition=_bounded_optional_string(
data.get("close_disposition"), 80
),
close_intent_digest=_bounded_optional_string(
data.get("close_intent_digest"), 128
),
raw=data,
)
@ -279,6 +303,7 @@ class ActivityCoreOpsClient:
f"{action} ops_run {run_id} failed: HTTP {exc.response.status_code}",
action=action,
status_code=exc.response.status_code,
code=_response_error_code(exc.response),
) from exc
except httpx.HTTPError as exc:
raise OpsRunError(
@ -313,6 +338,7 @@ def ops_run_to_taskspec(
hub_task_id=run.id,
completion_event_type=completion_event_type,
timeout_seconds=timeout_seconds,
repository_grant=run.repository_grant,
)
@ -325,3 +351,19 @@ def resolve_ops_target(run: OpsRun, config: OpsRunConfig | None = None) -> Path:
repo_map=cfg.repo_map,
repo_roots=cfg.repo_roots,
)
def _response_error_code(response: httpx.Response) -> str | None:
try:
payload = response.json()
except (ValueError, TypeError):
return None
detail = payload.get("detail") if isinstance(payload, dict) else None
code = detail.get("code") if isinstance(detail, dict) else None
return _bounded_optional_string(code, 80)
def _bounded_optional_string(value: Any, limit: int) -> str | None:
if not isinstance(value, str) or not value:
return None
return value[:limit]