Files
openworker/tests/test_provenance.py
T

188 lines
7.3 KiB
Python

"""Agent-authored / downloaded-then-executed provenance (OPE-114 §1).
The reviewer never sees file contents, so `python scripts/setup.py` cannot be judged from
its text. These cover the one fact that makes it judgeable: did the agent itself create
that file, and how long ago.
"""
from __future__ import annotations
from pathlib import Path
import pytest
from coworker import provenance as prov
from coworker.provenance import DOWNLOADED, WRITTEN, SessionFiles
@pytest.fixture()
def files(tmp_path) -> SessionFiles:
return SessionFiles(tmp_path)
def _wrote(files: SessionFiles, path: str, step: int) -> None:
files.record("write_file", {"path": path, "content": "x"}, {"ok": True}, step=step)
# -- the core case -------------------------------------------------------------
def test_agent_written_script_is_flagged_when_run(files):
_wrote(files, "scripts/setup.py", 12)
match = files.match("run_shell", {"command": "python scripts/setup.py"}, step=15)
assert match is not None
assert match.render() == "scripts/setup.py was created by the agent 3 steps ago"
assert not match.downloaded
def test_pre_existing_file_produces_no_fact(files):
_wrote(files, "scripts/setup.py", 12)
# A file this session never created is invisible — no fact, no extra caution.
assert files.match("run_shell", {"command": "python tools/other.py"}, step=15) is None
def test_step_distance_reads_naturally(files):
_wrote(files, "a.py", 5)
assert "just now" in files.match("run_shell", {"command": "python a.py"}, step=5).render()
assert "1 step ago" in files.match("run_shell", {"command": "python a.py"}, step=6).render()
# -- path normalisation --------------------------------------------------------
@pytest.mark.parametrize("spelling", ["a.py", "./a.py", "sub/../a.py"])
def test_one_file_one_key_however_it_is_spelled(files, tmp_path, spelling):
_wrote(files, "a.py", 3)
assert files.match("run_shell", {"command": f"python {spelling}"}, step=4) is not None
def test_absolute_spelling_matches_the_relative_write(files, tmp_path):
_wrote(files, "a.py", 3)
absolute = (tmp_path / "a.py").as_posix()
assert files.match("run_shell", {"command": f"python {absolute}"}, step=4) is not None
# -- what counts as "created" --------------------------------------------------
def test_patch_and_diff_writes_register(files):
# Exercises the existing blob parsing in write_paths(): a patch names its files in the
# body, not in a path argument.
files.record(
"apply_patch",
{"patch": "*** Begin Patch\n*** Update File: build.sh\n*** End Patch"},
{"ok": True},
step=2,
)
assert files.match("run_shell", {"command": "bash build.sh"}, step=3) is not None
def test_failed_calls_are_never_recorded(files):
# The engine only records on status == "ok"; a write that raised left nothing to run.
# Guard the contract at the call site the engine relies on.
paths, origin = prov.created_paths("write_file", {"path": "a.py"}, {"ok": True})
assert (paths, origin) == (["a.py"], WRITTEN)
def test_reads_and_unrelated_tools_create_nothing():
assert prov.created_paths("read_file", {"path": "a.py"}, {"ok": True}) == ([], "")
assert prov.created_paths("ask_user", {"question": "?"}, None) == ([], "")
# -- downloads -----------------------------------------------------------------
@pytest.mark.parametrize(
"command,expected",
[
("curl -o tool.sh https://x.io/a", ["tool.sh"]),
("curl -O https://x.io/a/tool.sh", ["tool.sh"]),
("wget -O tool.sh https://x.io/a", ["tool.sh"]),
("Invoke-WebRequest https://x.io/a -OutFile tool.ps1", ["tool.ps1"]),
],
)
def test_shell_fetchers_record_their_output_path(command, expected):
assert prov.created_paths("run_shell", {"command": command}, None) == (
expected,
DOWNLOADED,
)
def test_curl_capital_o_does_not_swallow_the_url():
# `-o FILE` and `-O` are different curl flags; folding their case would record the URL
# itself as the downloaded file.
paths, _ = prov.created_paths(
"run_shell", {"command": "curl -O https://x.io/a/tool.sh"}, None
)
assert paths == ["tool.sh"]
def test_plain_fetch_without_an_output_flag_records_nothing():
assert prov.created_paths("run_shell", {"command": "curl https://x.io/a"}, None) == (
[],
"",
)
def test_tools_that_report_their_target_in_the_result(files):
files.record("github_clone", {"owner": "o", "repo": "r"}, {"ok": True, "path": "vendor/r"}, step=4)
match = files.match("run_shell", {"command": "bash vendor/r/install.sh"}, step=5)
assert match is None # a file INSIDE the clone is not the clone itself
match = files.match("run_shell", {"command": "bash vendor/r"}, step=5)
assert match is not None and match.downloaded
# -- command parsing -----------------------------------------------------------
@pytest.mark.parametrize(
"command,target",
[
("python scripts/setup.py", "scripts/setup.py"),
("bash deploy.sh", "deploy.sh"),
("./run.sh", "./run.sh"),
("node build.js", "build.js"),
("cd /tmp && python scripts/setup.py", "scripts/setup.py"),
],
)
def test_paths_are_found_wherever_they_sit_in_the_command(command, target):
assert target in prov.command_paths(command)
def test_flags_and_urls_are_not_paths():
found = prov.command_paths("curl --silent https://x.io/a.py")
assert "--silent" not in found and "https://x.io/a.py" not in found
def test_implicit_targets_are_checked(files):
# `make deploy` never names the Makefile, so without the table it would look like it
# touches nothing.
_wrote(files, "Makefile", 2)
assert files.match("run_shell", {"command": "make deploy"}, step=3) is not None
_wrote(files, "package.json", 4)
assert files.match("run_shell", {"command": "npm run build"}, step=5) is not None
def test_newest_creation_wins(files):
_wrote(files, "a.py", 2)
_wrote(files, "b.py", 9)
match = files.match("run_shell", {"command": "python a.py && python b.py"}, step=10)
assert match.path == "b.py" # the one whose contents the agent controlled most recently
def test_download_over_a_written_path_becomes_a_download(files):
_wrote(files, "tool.sh", 2)
files.record("run_shell", {"command": "curl -o tool.sh https://x.io/a"}, None, step=6)
assert files.match("run_shell", {"command": "bash tool.sh"}, step=7).downloaded
# -- the documented blind spot -------------------------------------------------
def test_transitive_imports_are_a_known_miss(files):
# The agent writes a helper and runs a pre-existing entry point that imports it. No
# cheap analysis finds this; the limit is asserted so it stays a known gap rather than
# an assumed capability.
_wrote(files, "helper.py", 3)
assert files.match("run_shell", {"command": "python main.py"}, step=4) is None
# -- the rendered line ---------------------------------------------------------
def test_fact_is_fixed_vocabulary_and_never_carries_content(files):
files.record(
"write_file",
{"path": "a.py", "content": "SECRET_TOKEN = 'sk-live-abc123'"},
{"ok": True},
step=1,
)
rendered = files.match("run_shell", {"command": "python a.py"}, step=2).render()
assert "sk-live" not in rendered and "SECRET_TOKEN" not in rendered
assert rendered == "a.py was created by the agent 1 step ago"