"""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 class UnknownToolProfileError(ValueError): """Raised when a manifest references a profile that is not registered.""" # Claude Code --allowedTools strings. No push, no network, no arbitrary shell. _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: """A named hard allow-list for agentic sessions.""" name: str description: str allowed_tools: str lane: str # green | blue — advisory; enforcement is the allow-list 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)]