Skip to content

Commit 3149d4e

Browse files
committed
damage-control: group/pack registry + ownership sweep test (#1141)
Built by dotdev.dev
1 parent 23efdbe commit 3149d4e

2 files changed

Lines changed: 89 additions & 0 deletions

File tree

‎agentwire/safety/groups.py‎

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
"""Rule groups and packs: which bundled rule/tooldef files belong to what (#1141).
2+
3+
Every bundled ``rules/*.yaml`` and ``tooldefs/*.yaml`` belongs to exactly one
4+
entry here. ``kind="group"`` entries are default-on ("os-focused"); a human may
5+
opt one out. ``kernel`` groups can never be opted out. ``kind="pack"`` entries
6+
are vendor/tool sets that stay off until a human enables them.
7+
"""
8+
9+
from __future__ import annotations
10+
11+
from dataclasses import dataclass
12+
from pathlib import Path
13+
from typing import Dict, Tuple
14+
15+
16+
@dataclass(frozen=True)
17+
class Entry:
18+
name: str
19+
kind: str # "group" | "pack"
20+
description: str
21+
rules: Tuple[str, ...] = ()
22+
tooldefs: Tuple[str, ...] = ()
23+
kernel: bool = False
24+
binaries: Tuple[str, ...] = () # vendor CLIs whose presence suggests a pack
25+
26+
27+
_ENTRIES = (
28+
# --- default groups -----------------------------------------------------
29+
Entry("control-plane", "group", "Protected damage-control/agent config paths", ("control-plane.yaml",), kernel=True),
30+
Entry("core", "group", "Filesystem, process and history destruction", ("core.yaml",), kernel=True),
31+
Entry("git", "group", "Destructive git operations + git/gh/uv tooldefs", ("git.yaml",), ("git.yaml", "gh.yaml", "uv.yaml")),
32+
Entry("agentwire", "group", "AgentWire state and session destruction", ("agentwire.yaml",)),
33+
Entry("payloads", "group", "Rules scanning payloads wrapped in other commands", ("payloads.yaml",)),
34+
Entry("remote", "group", "ssh-only remote hazards (reboot, service stop)", ("remote.yaml",)),
35+
Entry("mcp-tools", "group", "Per-tool policy for MCP tool calls", ("mcp-tools.yaml",)),
36+
Entry("outbound", "group", "Outward-facing sends (email, SMS, push)", ("outbound.yaml",)),
37+
# --- packs --------------------------------------------------------------
38+
Entry("aws", "pack", "AWS CLI", ("aws.yaml",), ("aws.yaml",), binaries=("aws",)),
39+
Entry("gcp", "pack", "gcloud", ("gcp.yaml",), ("gcp.yaml",), binaries=("gcloud",)),
40+
Entry("firebase", "pack", "Firebase CLI", ("firebase.yaml",), binaries=("firebase",)),
41+
Entry("cloud-hosting", "pack", "Vercel, Netlify, Cloudflare, Heroku, Fly, DO, Supabase (+ gh repo delete)", ("cloud-hosting.yaml",), binaries=("vercel", "netlify", "wrangler", "heroku", "flyctl", "doctl", "supabase")),
42+
Entry("databases", "pack", "Database CLIs and SQL", ("databases.yaml",), binaries=("psql", "mysql", "mongosh", "redis-cli")),
43+
Entry("containers", "pack", "Docker and Kubernetes", ("containers.yaml",), ("docker.yaml", "kubectl.yaml"), binaries=("docker", "kubectl")),
44+
Entry("infrastructure", "pack", "Terraform, Pulumi, Serverless, SAM", ("infrastructure.yaml",), ("terraform.yaml",), binaries=("terraform", "pulumi", "serverless", "sam")),
45+
Entry("publish", "pack", "Package registry publish + npm", ("publish.yaml",), ("npm.yaml",), binaries=("npm", "twine")),
46+
Entry("gws", "pack", "Google Workspace CLI", ("gws.yaml",), ("gws.yaml",), binaries=("gws",)),
47+
)
48+
49+
REGISTRY: Dict[str, Entry] = {e.name: e for e in _ENTRIES}
50+
51+
52+
def bundled_files(package_dir: Path) -> Dict[str, set]:
53+
"""The ``{"rules": {...}, "tooldefs": {...}}`` file names actually bundled."""
54+
return {
55+
"rules": {p.name for p in (package_dir / "hooks" / "damage-control" / "rules").glob("*.yaml")},
56+
"tooldefs": {p.name for p in (package_dir / "tooldefs").glob("*.yaml")},
57+
}
58+
59+
60+
def unclassified(package_dir: Path) -> Dict[str, list]:
61+
"""Bundled files owned by no entry, or by more than one, per section."""
62+
bundled = bundled_files(package_dir)
63+
out: Dict[str, list] = {}
64+
for section in ("rules", "tooldefs"):
65+
owners: Dict[str, list] = {}
66+
for e in _ENTRIES:
67+
for f in getattr(e, section):
68+
owners.setdefault(f, []).append(e.name)
69+
bad = [f"{f}: owned by {owners.get(f, [])}" for f in sorted(bundled[section] | set(owners))
70+
if f not in bundled[section] or len(owners.get(f, [])) != 1]
71+
if bad:
72+
out[section] = bad
73+
return out

‎tests/unit/test_safety_groups.py‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
"""Every bundled rule/tooldef file belongs to exactly one group or pack (#1141)."""
2+
3+
from pathlib import Path
4+
5+
import agentwire
6+
from agentwire.safety import groups
7+
8+
9+
def test_every_bundled_file_has_exactly_one_owner():
10+
bad = groups.unclassified(Path(agentwire.__file__).parent)
11+
assert not bad, bad
12+
13+
14+
def test_kernel_is_never_a_pack():
15+
bad = [e.name for e in groups.REGISTRY.values() if e.kernel and e.kind != "group"]
16+
assert not bad, bad

0 commit comments

Comments
 (0)