agent_harness -> rein_aharness (package + all imports), CLI command agent-harness -> rein-aharness, Docker image tag, k8s namespace/labels/ names, Makefile targets, deploy script env var/paths. In-repo identity strings (hub event source, metrics harness field, default assignee, argparse prog name, commit author identity) updated to match. Historical documents left untouched on purpose: docs/adr/ADR-001-agent-harness-architecture.md, docs/architecture.md (dated v0.1 snapshot), workplans/HARNESS-WP-0001 (completed under the old name), and the SSH host alias "forgejo-agent-harness" (external ~/.ssh/config entry, not owned here). Verified: 47/47 tests pass, CLI runs correctly from a fresh venv, `make image` builds and the resulting container runs correctly. deploy/README.md gained an explicit rename cutover checklist for what this session cannot safely do unattended -- moving the host-side secrets dir and checkout on railiance01, and not deleting the old k8s namespace until the new one is confirmed working. The actual live cutover (running that checklist against the real Railiance deployment) is not attempted here -- real production surgery on binky-control's live automation, needs the operator present. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
78 lines
2.4 KiB
Python
78 lines
2.4 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
|
|
|
|
|
|
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)]
|