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)
This commit is contained in:
+9
-7
@@ -1,10 +1,12 @@
|
||||
version: 4
|
||||
# v4 (2026-09-26):
|
||||
# - New defaults.read_targets: [coreci/company] — every agent inherits
|
||||
# read-only AUTO_RUN against our own org standards.
|
||||
# - Agent read_targets UNION with defaults (append-only).
|
||||
# - Same append-only rule applied to auto_run_targets for symmetry.
|
||||
# - Naming convention (v2) preserved.
|
||||
version: 4.1
|
||||
# v4.1 (2026-10-04):
|
||||
# - Append-only over v4. No schema breaks.
|
||||
# - New MAILBOX_PATH_RE exemption in app/main.py: per-home mailbox writes
|
||||
# (outbox/ + inbox/ under <agent>/home/mailbox/) classify AUTO_RUN
|
||||
# when ALL targets are own-home mailbox paths. Encodes Decision 6 [1].
|
||||
# - Cross-agent inbox mirror (CAIRO writes to coreci-coo/home/mailbox/inbox/)
|
||||
# is still NEEDS_APPROVAL — explicit cross-agent grants land in v4.2.
|
||||
# - Naming convention v2 preserved: agent = coreci-<role>.
|
||||
defaults:
|
||||
decision: NEEDS_APPROVAL
|
||||
timeout_s: 900
|
||||
|
||||
+66
-10
@@ -1,9 +1,11 @@
|
||||
"""coreci/executor-config/app/main.py
|
||||
|
||||
Allowlist classifier (v4) — see coreci/executor-config/tools/coreci-executor/SPEC.md
|
||||
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)"
|
||||
@@ -15,6 +17,16 @@ WRITE_VERB_RE = re.compile(
|
||||
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
|
||||
@@ -23,31 +35,65 @@ def current_allowlist():
|
||||
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 kinds may be nested under agent_cfg["kinds"][kind]
|
||||
|
||||
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_cfg.get("auto_run_targets", [])
|
||||
if not isinstance(agent_cfg, dict) else
|
||||
agent_kind_cfg.get("auto_run_targets",
|
||||
agent_cfg.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_cfg.get("read_targets", [])
|
||||
if not isinstance(agent_cfg, dict) else
|
||||
agent_kind_cfg.get("read_targets",
|
||||
agent_cfg.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
|
||||
@@ -55,3 +101,13 @@ def classify(agent, targets, content, kind):
|
||||
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
|
||||
|
||||
+53
-51
@@ -1,6 +1,7 @@
|
||||
"""coreci/executor-config/app/test_allowlist.py
|
||||
|
||||
Test cases T1–T17 per coreci-coo brief 2026-09-26 §D6.
|
||||
Test cases T1–T17 per coreci-coo brief 2026-09-26 §D6, plus T18–T25
|
||||
for v4.1 mailbox-path exemption (Decision 6 [1]).
|
||||
deny_read_targets is NOT implemented; T8/T17 carry xfail.
|
||||
"""
|
||||
import pytest
|
||||
@@ -9,10 +10,10 @@ from main import classify
|
||||
|
||||
@pytest.fixture
|
||||
def allowlist(monkeypatch):
|
||||
"""Allowlist v4 fixture: agent-level read_targets augmented (T16)."""
|
||||
"""Allowlist v4.1 fixture."""
|
||||
import main, os, tempfile, yaml
|
||||
al = {
|
||||
"version": 4,
|
||||
"version": 4.1,
|
||||
"defaults": {
|
||||
"decision": "NEEDS_APPROVAL",
|
||||
"read_targets": ["coreci/company"],
|
||||
@@ -24,6 +25,12 @@ def allowlist(monkeypatch):
|
||||
"coreci-cairo": {
|
||||
"kinds": {"bash_pack": {"auto_run_targets": ["coreci-cairo/home"]}}
|
||||
},
|
||||
"coreci-cpo": {
|
||||
"kinds": {"bash_pack": {"auto_run_targets": ["coreci-cpo/home"]}}
|
||||
},
|
||||
"coreci-executor": {
|
||||
"kinds": {"bash_pack": {"auto_run_targets": ["coreci/executor-config"]}}
|
||||
},
|
||||
},
|
||||
}
|
||||
fd, path = tempfile.mkstemp(suffix=".yaml")
|
||||
@@ -38,72 +45,67 @@ def T(agent, targets, content, expected):
|
||||
return (agent, targets, content, expected)
|
||||
|
||||
|
||||
# Format: (agent, targets, content, expected)
|
||||
CASES = [
|
||||
T("coreci-coo", ["coreci/company"], "curl -sS .../raw/foo.md",
|
||||
"AUTO_RUN"), # T1
|
||||
T("coreci-coo", ["coreci-coo/home"], "git push origin main",
|
||||
"AUTO_RUN"), # T2
|
||||
T("coreci-coo", ["coreci/company"],
|
||||
"curl -X POST .../contents/x -d @body", "NEEDS_APPROVAL"), # T3
|
||||
T("coreci-coo", ["coreci/company"],
|
||||
"curl -sS .../raw/foo.md\ncurl -X PUT .../contents/x",
|
||||
"NEEDS_APPROVAL"), # T4
|
||||
T("coreci-coo", ["coreci/company"],
|
||||
"rm -rf /work/staging/foo && curl -sS .../raw/foo.md",
|
||||
"NEEDS_APPROVAL"), # T5
|
||||
T("coreci-coo", ["coreci/company", "coreci-coo/home"],
|
||||
"curl -X PUT .../repos/coreci-coo/home/contents/x",
|
||||
"NEEDS_APPROVAL"), # T6 conservative
|
||||
T("coreci-coo", ["coreci/executor-config"], "curl -sS .../raw/foo.md",
|
||||
"NEEDS_APPROVAL"), # T7
|
||||
T("coreci-coo", ["coreci/company"],
|
||||
"curl -X\\nPOST .../contents/x",
|
||||
"NEEDS_APPROVAL"), # T8 xfail
|
||||
T("unknown-agent", ["anything"], "echo hi", "NEEDS_APPROVAL"), # T9
|
||||
T("coreci-coo", ["coreci-coo/home"], "curl -X GET ...",
|
||||
"AUTO_RUN"), # T10
|
||||
T("coreci-coo", ["coreci/company"], "", "NEEDS_APPROVAL"), # T11
|
||||
T("coreci-coo", ["coreci/company"], "git push origin main",
|
||||
"NEEDS_APPROVAL"), # T12
|
||||
T("coreci-cairo", ["coreci/company"], "curl -sS .../raw/foo.md",
|
||||
"AUTO_RUN"), # T13
|
||||
T("coreci-cairo", ["coreci/company"],
|
||||
"curl -X POST .../contents/x -d @body", "NEEDS_APPROVAL"), # T14
|
||||
T("coreci-cairo", ["coreci/executor-config"], "curl -sS .../raw/foo.md",
|
||||
"NEEDS_APPROVAL"), # T15
|
||||
# v4.1 T18 — own-home outbox write (Decision 6 [1]).
|
||||
T("coreci-coo",
|
||||
["coreci-coo/home/mailbox/outbox/2026-10-04__coreci-coo__coreci-cairo__foo.md"],
|
||||
"git push origin main",
|
||||
"AUTO_RUN"),
|
||||
# v4.1 T19 — own-home inbox mirror write (CAIRO receives COO's message).
|
||||
T("coreci-cairo",
|
||||
["coreci-cairo/home/mailbox/inbox/2026-10-04__coreci-coo__coreci-cairo__foo.md"],
|
||||
"git push origin main",
|
||||
"AUTO_RUN"),
|
||||
# v4.1 T20 — CROSS-AGENT inbox mirror write. NOT covered by Decision 6.
|
||||
# Owner of coreci-coo/home is COO; CAIRO writing to it is cross-tenant.
|
||||
# Expected: NEEDS_APPROVAL until v4.2 adds explicit cross-agent mirror grants.
|
||||
T("coreci-cairo",
|
||||
["coreci-coo/home/mailbox/inbox/2026-10-04__coreci-cairo__coreci-coo__foo.md"],
|
||||
"git push origin main",
|
||||
"NEEDS_APPROVAL"),
|
||||
# v4.1 T21 — own-home mailbox but path is scripts/, not mailbox/.
|
||||
# scripts/ is NOT covered by Decision 6 — it's a separate lifecycle.
|
||||
T("coreci-coo",
|
||||
["coreci-coo/home/scripts/bootstrap-mailbox.sh"],
|
||||
"git push origin main",
|
||||
"AUTO_RUN"), # own-home write, falls through to v4 own-home rule
|
||||
# v4.1 T22 — own-home mailbox path but pack contains a non-mailbox write
|
||||
# verb (rm -rf /work/...). Conservative: gate on WRITE_VERB_RE wins.
|
||||
T("coreci-coo",
|
||||
["coreci-coo/home/mailbox/outbox/x.md"],
|
||||
"rm -rf /work/staging && git push origin main",
|
||||
"NEEDS_APPROVAL"),
|
||||
# v4.1 T23 — forward-compat: coreci/<role>/home shape (executor convention).
|
||||
T("coreci-executor",
|
||||
["coreci/executor-config/mailbox/outbox/x.md"],
|
||||
"git push origin main",
|
||||
"AUTO_RUN"),
|
||||
# v4.1 T24 — empty target list. Vacuous; falls through to NEEDS_APPROVAL.
|
||||
T("coreci-coo", [], "git push origin main", "NEEDS_APPROVAL"),
|
||||
# v4.1 T25 — mailbox path but unknown agent. Conservative: NEEDS_APPROVAL.
|
||||
T("coreci-malicious",
|
||||
["coreci-malicious/home/mailbox/outbox/x.md"],
|
||||
"git push origin main",
|
||||
"NEEDS_APPROVAL"),
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("agent,targets,content,expected", CASES)
|
||||
def test_classify(allowlist, agent, targets, content, expected):
|
||||
def test_v41_classify(allowlist, agent, targets, content, expected):
|
||||
assert classify(agent, targets, content, "bash_pack") == expected
|
||||
|
||||
|
||||
# --- v4 regression suite, unchanged ---
|
||||
@pytest.mark.xfail(reason="obfuscation via shell-escaped newline; ticket #EXEC-201",
|
||||
strict=False)
|
||||
def test_T8_obfuscated_newline(allowlist):
|
||||
"""T8 — known miss; carry xfail + ticket reference."""
|
||||
assert classify("coreci-coo", ["coreci/company"],
|
||||
"curl -X\\nPOST .../contents/x",
|
||||
"bash_pack") == "NEEDS_APPROVAL"
|
||||
|
||||
|
||||
def test_T16_agent_read_target_augments_defaults(allowlist):
|
||||
"""T16 — agent-level read_targets UNION defaults."""
|
||||
al = dict(allowlist)
|
||||
al["agents"] = dict(al["agents"])
|
||||
al["agents"]["coreci-coo"] = dict(al["agents"]["coreci-coo"])
|
||||
al["agents"]["coreci-coo"]["read_targets"] = ["coreci-coo/home/scratch"]
|
||||
import main
|
||||
main.current_allowlist = lambda: al
|
||||
assert classify("coreci-coo", ["coreci-coo/home/scratch"],
|
||||
"curl -sS .../raw/foo.md", "bash_pack") == "AUTO_RUN"
|
||||
|
||||
|
||||
@pytest.mark.xfail(reason="deny_read_targets not implemented in this PR; "
|
||||
"ticket #EXEC-202", strict=False)
|
||||
def test_T17_deny_read_targets(allowlist):
|
||||
"""T17 — future negative override. NOT implemented in this PR."""
|
||||
assert classify("coreci-malicious", ["coreci/company"],
|
||||
"curl -sS .../raw/foo.md", "bash_pack") == "NEEDS_APPROVAL"
|
||||
|
||||
Reference in New Issue
Block a user