Files
coreci-cairo 331809f014 executor-config: bump to v4.1; add MAILBOX_PATH_RE + Decision 6 exemption
Per CAIRO task brief 2026-09-26 [1] (D1-D6), Decision 6 mailbox
writes are ALWAYS AUTO_RUN against the sender's own home.
v4 did not implement this; v4.1 adds:

* MAILBOX_PATH_RE recognises <home-repo>/mailbox/(outbox|inbox)/<path>
  for both coreci-<role>/home (standard) and coreci/<role>/home
  (forward-compat).
* classify() extended with own-home gate: if every target is an
  own-home mailbox path and no non-mailbox write verb is present,
  classify AUTO_RUN.
* T18-T25 added; T8/T17 xfail preserved.
* deny_read_targets NOT implemented (per brief).

[1] CAIRO task brief - consolidated D1-D6
    (per-home mailbox + read_targets parser)
2026-10-04 17:58:12 +00:00

114 lines
4.1 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""coreci/executor-config/app/main.py
Allowlist classifier (v4.1) — see coreci/executor-config/tools/coreci-executor/SPEC.md §11.
"""
import re
# v4 — pre-hoc classifier gate. Any pack containing a write verb against
# read-only targets is NEEDS_APPROVAL.
WRITE_VERB_RE = re.compile(
r"(?i)\b("
r"curl\s+[^|;]*\s-X\s+(POST|PUT|PATCH|DELETE)"
r"|curl\s+[^|;]*-(d|--data|--data-binary|--data-raw|urlencode)\s"
r"|\bgit\s+push\b"
r"|\bgit\s+commit\b(?![^|;\n]*--amend\b)"
r"|\bgh\s+(pr|issue|release|repo)\s+(create|edit|close|merge|delete)\b"
r"|\b(rm|mv|cp)\s+-[^\n]*\s+/\w"
r")"
)
# v4.1 — mailbox-path exemption. Recognises the per-home mailbox envelope
# from coreci/company/standards/communications.md §"Mailbox" (decisions 1–9).
# Path shape: <home-repo>/mailbox/(outbox|inbox)/<file-or-dir>
# where <home-repo> = coreci-<agent>/home or coreci/<agent>/home.
# Does NOT exempt paths in scripts/ or anywhere outside mailbox/.
MAILBOX_PATH_RE = re.compile(
r"^(?:coreci[-/][\w-]+/home|"
r"coreci[-/]\w+)/mailbox/(?:outbox|inbox)(?:/[^\s]*)?$"
)
def current_allowlist():
import os, yaml
with open(os.environ.get(
"ALLOWLIST_PATH", "allowlist.yaml"), "r") as f:
return yaml.safe_load(f)
def _is_own_home(agent, target):
"""Return True if `target` is <agent>'s own home repo.
Naming convention v2: agent field = Gitea username = allowlist key.
Format: coreci-<role>. Two accepted shapes for the home repo path:
coreci-<role>/home (standard)
coreci/<role>/home (forward-compatible)
"""
role = agent.replace("coreci-", "", 1)
return target in {f"coreci-{role}/home", f"coreci/{role}/home"}
def _mailbox_only_targets(targets):
"""Return True iff every target is an own-home mailbox path."""
for t in targets:
if not MAILBOX_PATH_RE.match(t):
return False
return True
def classify(agent, targets, content, kind):
al = current_allowlist()
defaults = al["defaults"]
agent_cfg = al["agents"].get(agent)
if agent_cfg is None:
return defaults["decision"]
agent_kind_cfg = (
agent_cfg.get("kinds", {}).get(kind, {})
if isinstance(agent_cfg, dict) else {}
)
auto_run_targets = (
set(defaults.get("auto_run_targets", []))
| set(agent_kind_cfg.get(
"auto_run_targets",
agent_cfg.get("auto_run_targets", [])))
)
read_targets = (
set(defaults.get("read_targets", []))
| set(agent_kind_cfg.get(
"read_targets",
agent_cfg.get("read_targets", [])))
)
# v4.1 — Decision 6 mailbox-path exemption [1].
# Mailbox writes are ALWAYS AUTO_RUN when:
# (a) every target is the sender's own home mailbox path, AND
# (b) every target is on the sender's own home (not a cross-agent mirror
# to someone else's <n>/home/mailbox/), AND
# (c) the content does not contain any non-mailbox write verb.
# Cross-agent mailbox INBOX mirror (CAIRO writes to coreci-coo/home/mailbox/inbox/)
# is NOT covered by this exemption — that needs explicit cross-agent grant.
# That's the gap Decision 6 left; COO needs to add mailbox mirror grants in v4.2.
if (_mailbox_only_targets(targets)
and all(_is_own_home(agent, _home_part(t)) for t in targets)
and not WRITE_VERB_RE.search(content)):
return "AUTO_RUN"
# v4 — pre-existing classifier, unchanged.
if all(t in auto_run_targets for t in targets):
return "AUTO_RUN"
allowed_for_read = auto_run_targets | read_targets
if all(t in allowed_for_read for t in targets):
if not WRITE_VERB_RE.search(content):
return "AUTO_RUN"
return defaults["decision"]
def _home_part(target):
"""Return the <home-repo> portion of a mailbox target.
Example: 'coreci-coo/home/mailbox/outbox/x.md' -> 'coreci-coo/home'
Used to gate the Decision 6 exemption to own-home only.
"""
parts = target.split("/mailbox/", 1)
return parts[0] if parts else target