fix(kvb-org-operator): refuse operator commands no shell can run

operator.py add accepted prose, slash commands, aliases and <slot> commands as runnable rows, so the operator got bash blocks that do nothing or fail. A command must now start, after VAR=value words, with a program on PATH, a path to one or a shell builtin, and hold no <slot>; anything else is refused unless the row is a decision. Older rows of that kind stay in the table but get no bash block and are skipped by run. Ordered by the operator on 2026-11-11.

🎯 Fix: 93 %
💥 Break: 4 %
💸 Cost: 100 %
⏱️ Time: 100 %
🛡️ Security: 100 %
🏆 Score: 89
🚦 Gate: ✅ 90/5, cost and time <= 100, security 100 %

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Kevin Veen-BirkenbachandClaude Opus 5.5 committed 2026-11-11 16:12:43 +00:00
1 parent e52db1dca0
commit 47ec0213e1
2 files changed
+50 -2

No files matched your search

+22
View File
@@ -126,6 +126,9 @@ class TestOperator(unittest.TestCase):
return json.loads((self.root / "rasci.json").read_text(encoding="utf-8"))
def test_checks_close_rows_once_their_condition_holds(self):
for name in ("pipx", "git-sign-push"):
(self.bin / name).write_text("#!/bin/sh\n", encoding="utf-8")
(self.bin / name).chmod(0o755)
repo = self._repo("repo", upstream=True)
src, dst = self.base / "src", self.base / "dst"
(src / "sub").mkdir(parents=True)
@@ -227,6 +230,25 @@ class TestOperator(unittest.TestCase):
[HEADER, "|---|---|---|---|---|", "| 1 | - | sudo reboot | - | manual |"],
)
def test_add_refuses_a_command_no_shell_can_run_unless_it_is_a_decision(self):
for command in ("Edit pcs.yml: add the group", "/kvb-skill-cleanup", "deploy -p <your pass file>", "gipsn"):
result = self._run("add", "x", "--dir", str(self.base), "--command", command, "--reason", "r",
"--check", "manual")
self.assertEqual(result.returncode, 2, command)
self.assertIn("record the step with --check decision", result.stderr)
self._add("subshell", self.base, "(umask 077; touch secret)", "r", "manual")
self._add("env", self.base, "GIT_CONFIG_COUNT=1 git status", "r", "manual")
self._add("ask", self.base, "Edit pcs.yml: add the group", "r", "decision")
self.assertEqual(self._open(), ["operator/subshell", "operator/env", "operator/ask"])
def test_an_older_prose_row_stays_out_of_blocks_and_run(self):
result = self._run("set", "operator/prose", "--title", "Rotate the credential", "--workdir", str(self.base),
"--status", "queued", script=RASCI)
self.assertEqual(result.returncode, 0, result.stderr)
self._add("go", self.base, "true", "runnable", "manual")
self.assertEqual(self._out("list")[4:], [*self._block(2, "go", self.base, "true"), *RUNNER])
self.assertNotIn("Rotate", self._feed("y\n").stdout)
def test_bash_block_skips_legacy_rows_without_directory(self):
result = self._run("set", "operator/legacy", "--title", "Rotate leaked credentials", "--status", "queued",
script=RASCI)