rein-aharness/rein_aharness/profiles.py
tegwick 4ae245a88f
Some checks failed
Governed runtime contract / contract (push) Failing after 27s
Constrain controlled CLI sessions and prove native budget overshoot
Assistant: codex
Assistant-Model: gpt-5.6-luna
Assistant-Session: 01a07ff8-19d0-7820-b4d0-1353833cb7fc
2026-09-09 20:53:25 +02:00

92 lines
3.1 KiB
Python

"""Named tool-profile registry.
Instances declare a profile by name in the instance manifest; the harness
resolves and enforces the allow-list. Instances never enumerate tools.
Unknown profile names refuse to run.
"""
from __future__ import annotations
from dataclasses import dataclass
import re
class UnknownToolProfileError(ValueError):
"""Raised when a manifest references a profile that is not registered."""
# Direct Claude Code permission rules. Indirect Git helpers still require
# the sandbox owner's filesystem, credential and egress boundaries.
_GREEN_COMMIT_TOOLS = (
"Read,Write,Edit,Glob,Grep,"
"Bash(git add:*),Bash(git commit:*),Bash(git status),"
"Bash(git log:*),Bash(git diff:*),Bash(date:*),Bash(ls:*)"
)
# Blue-lane mail triage session: same session tools as green-commit-only.
# Credentialed IMAP scan is a deterministic pre-step outside the session
# (see mailscan.py); the session only reads reports and updates queues.
_BLUE_MAIL_TRIAGE_TOOLS = _GREEN_COMMIT_TOOLS
@dataclass(frozen=True)
class ToolProfile:
"""Named permission rules; controlled runs also restrict the tool inventory."""
name: str
description: str
allowed_tools: str
lane: str # green | blue — advisory; enforcement is the allow-list
@property
def available_tools(self) -> str:
"""Builtin inventory derived from the registered permission rules."""
names = []
for rule in self.allowed_tools.split(","):
match = re.fullmatch(r"([A-Za-z][A-Za-z0-9_]*)(?:\([^\r\n()]+\))?", rule.strip())
if match is None or match[1].startswith("mcp__"):
raise ValueError("controlled tool profile has an invalid builtin rule")
if match[1] not in names:
names.append(match[1])
return ",".join(names)
PROFILES: dict[str, ToolProfile] = {
"green-commit-only": ToolProfile(
name="green-commit-only",
description=(
"Green-lane local commit: read/edit tools plus git add/commit/"
"status/log/diff. No push, no network, no arbitrary shell."
),
allowed_tools=_GREEN_COMMIT_TOOLS,
lane="green",
),
"blue-mail-triage": ToolProfile(
name="blue-mail-triage",
description=(
"Blue-lane mail triage session after a credentialed deterministic "
"scan: same local commit tools as green-commit-only. Credentials "
"and network stay outside the agentic session."
),
allowed_tools=_BLUE_MAIL_TRIAGE_TOOLS,
lane="blue",
),
}
def get_profile(name: str) -> ToolProfile:
"""Resolve a profile by name. Raises UnknownToolProfileError if missing."""
key = (name or "").strip()
if not key:
raise UnknownToolProfileError("tool_profile name is empty")
profile = PROFILES.get(key)
if profile is None:
known = ", ".join(sorted(PROFILES))
raise UnknownToolProfileError(
f"unknown tool_profile '{key}' (known: {known})"
)
return profile
def list_profiles() -> list[ToolProfile]:
return [PROFILES[k] for k in sorted(PROFILES)]