Some checks failed
Governed runtime contract / contract (push) Failing after 27s
Assistant: codex Assistant-Model: gpt-5.6-luna Assistant-Session: 01a07ff8-19d0-7820-b4d0-1353833cb7fc
92 lines
3.1 KiB
Python
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)]
|