"""Which cgroups a machine-side killer may destroy. Fail-closed.

Membership by resolved cgroup path, never by process name — name matching is
what killed the user's own sessions. Shared by pids-guard (the watchdog) and
pids-rescue (the recovery path) so the two can never drift apart.
"""
import os
import re

UID = os.getuid()

# Slices whose scopes are agent-owned and disposable. app.slice is deliberately
# absent: it holds the terminal scopes carrying the user's own sessions.
KILLABLE_SLICES = ("agent.slice", "agent-seat.slice", "build.slice", "unsafe.slice")

# Scope names the agent launchers produce: confine.sh, the seat entrypoint, and
# systemd-run's own generated names.
KILLABLE_SCOPE_RE = re.compile(
    r"^(?:confine-(?:agent|build)-\d+-\d+"
    r"|agent-seat-[A-Za-z0-9._-]+"
    r"|unsafe-[A-Za-z0-9._-]+-\d+-\d+"
    r"|run-[a-z][0-9a-f]+(?:-i\d+)?)\.scope$"
)

# A match on any component of the chain refuses the kill: killing an ancestor
# kills the leaf, and a descendant IS the leaf.
HUMAN_COMPONENT_RE = re.compile(
    r"^(?:human\.slice|app\.slice|init\.scope|session-\d+\.scope|vte-spawn-.*\.scope)$"
)


def is_killable(rel, uid=None):
    """True only for an agent-owned scope, given a cgroup-root-relative path.

    Every other input — a foreign path, an unknown slice, a human component
    anywhere in the chain, a slice rather than a scope, or a scope name no
    agent launcher produces — is False.
    """
    if uid is None:
        uid = UID
    if not isinstance(rel, str):
        return False
    prefix = f"/user.slice/user-{uid}.slice/user@{uid}.service/"
    if not rel.startswith(prefix):
        return False
    parts = [p for p in rel[len(prefix):].split("/") if p]
    if len(parts) < 2 or parts[0] not in KILLABLE_SLICES:
        return False
    if any(HUMAN_COMPONENT_RE.match(p) for p in parts):
        return False
    return bool(KILLABLE_SCOPE_RE.match(parts[-1]))


def is_killable_slice(rel, uid=None):
    """True for one of KILLABLE_SLICES itself, given a cgroup-root-relative path.

    A slice is never a kill target — killing it kills every sibling agent. It is
    a saturation *signal*: when the aggregate crosses the threshold the guard
    kills the largest scope inside it instead.
    """
    if uid is None:
        uid = UID
    if not isinstance(rel, str):
        return False
    prefix = f"/user.slice/user-{uid}.slice/user@{uid}.service/"
    if not rel.startswith(prefix):
        return False
    parts = [p for p in rel[len(prefix):].split("/") if p]
    return len(parts) == 1 and parts[0] in KILLABLE_SLICES


def same_chain(a, b):
    return a == b or a.startswith(b + "/") or b.startswith(a + "/")
