rein-aharness/rein_aharness/taskspec.py
tegwick c63caf5568
Some checks are pending
Governed runtime contract / contract (push) Waiting to run
feat: import governed sandbox commits and enforce native CLI limits
Assistant: codex
Assistant-Model: gpt-5.6-luna
Assistant-Session: 01a07ff8-19d0-7820-b4d0-1353833cb7fc
2026-09-09 14:08:10 +02:00

69 lines
2.5 KiB
Python

"""Task specification the worker consumes.
MVP source is a JSON file matching what the activity-core issue-core sink
emits per rule action (task_template/description/target_repo/labels), plus
executor-specific fields. A future source polls issue-core directly.
"""
from __future__ import annotations
import json
from dataclasses import dataclass, field
from pathlib import Path
from rein_aharness.native_limits import validate_limits
from rein_aharness.repository_grant import RepositoryGrant, RepositoryGrantError
class TaskSpecError(ValueError):
pass
@dataclass
class TaskSpec:
title: str
description: str
target_repo: Path
agent: str = "coach"
labels: list[str] = field(default_factory=list)
hub_task_id: str | None = None
completion_event_type: str = "executor_run"
timeout_seconds: int = 900
repository_grant: RepositoryGrant | None = None
max_budget_usd: float | None = None
max_turns: int | None = None
def __post_init__(self) -> None:
try:
validate_limits(self.max_budget_usd, self.max_turns)
except ValueError as exc:
raise TaskSpecError(str(exc)) from exc
@classmethod
def from_file(cls, path: str | Path) -> "TaskSpec":
raw = json.loads(Path(path).read_text())
missing = {"title", "description", "target_repo"} - set(raw)
if missing:
raise TaskSpecError(f"task spec missing field(s): {', '.join(sorted(missing))}")
target = Path(raw["target_repo"]).expanduser()
if not (target / ".git").is_dir():
raise TaskSpecError(f"target_repo is not a git repository: {target}")
grant = None
if "repository_grant" in raw:
try:
grant = RepositoryGrant.from_mapping(raw["repository_grant"])
except RepositoryGrantError as exc:
raise TaskSpecError(f"invalid repository_grant: {exc}") from exc
return cls(
title=str(raw["title"]),
description=str(raw["description"]),
target_repo=target,
agent=str(raw.get("agent", "coach")),
labels=[str(label) for label in raw.get("labels", [])],
hub_task_id=raw.get("hub_task_id"),
completion_event_type=str(raw.get("completion_event_type", "executor_run")),
timeout_seconds=int(raw.get("timeout_seconds", 900)),
repository_grant=grant,
max_budget_usd=raw.get("max_budget_usd"),
max_turns=raw.get("max_turns"),
)