mirror of
https://github.com/norandom/OpenUltraSAST.git
synced 2026-10-16 03:49:26 +00:00
search-with-proof: bootstrap intent, real reason/explore contracts, visible refusals
First live run: the reason step got ~190 tokens and returned {} -> silent
no_evidence. Now: a bootstrap intent reads the candidate and its entry point; the
reason prompt states the goal, family oracles, demo schema and must propose
intents when none are open; empty replies retry once then end as reason_refused;
failure records keep messages.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
87b82564b4
commit
e39a4882b0
@@ -37,3 +37,39 @@ def test_bad_references_and_oversize_are_atomic(tmp_path):
|
||||
with pytest.raises(ValueError):
|
||||
board.commit(writer="host", hints=[dict(id="h", text="x" * 2048, step="human", task_id="operator")])
|
||||
assert board.head == before
|
||||
|
||||
|
||||
def test_snapshot_caps_fact_text_and_preserves_context(tmp_path):
|
||||
import yaml
|
||||
|
||||
board = Board(FileStore(tmp_path), "context", coordinator="host")
|
||||
facts = [
|
||||
dict(id=f"f{i}", text="observation " * 500, evidence_refs=["tool:1"], from_intents=[], step="explore", task_id=f"t{i}")
|
||||
for i in range(10)
|
||||
]
|
||||
intents = [
|
||||
dict(id=f"i{i}", description="inspect", from_facts=[], status=status, claimant=None, step="reason", task_id="r")
|
||||
for i, status in enumerate(("open", "concluded"))
|
||||
]
|
||||
board.commit(
|
||||
writer="host",
|
||||
facts=facts,
|
||||
intents=intents,
|
||||
hints=[
|
||||
dict(
|
||||
id="candidate",
|
||||
step="reason",
|
||||
task_id="manifest",
|
||||
text=json.dumps(dict(family="injection", oracle="sql", file="app.py", function="main")),
|
||||
)
|
||||
],
|
||||
)
|
||||
view = yaml.safe_load(board.snapshot(budget_left={"tasks": 3}))
|
||||
assert view["candidate"]["file"] == "app.py" and view["family"] == "sql"
|
||||
assert view["budget_left"]["tasks"] == 3
|
||||
assert {i["status"] for i in view["intents"]} == {"open", "concluded"}
|
||||
assert all(len(f["text"]) <= 2000 for f in view["facts"])
|
||||
assert sum(len(f["text"]) for f in view["facts"]) <= 12000
|
||||
assert view["facts"][-1]["text"].startswith("observation")
|
||||
assert view["facts"][-1]["text_truncated"]
|
||||
assert board.state["facts"] == facts
|
||||
|
||||
@@ -72,7 +72,7 @@ def test_claim_checkpoint_survives_interrupt_without_free_spend(tmp_path):
|
||||
resumed = Board.resume(b.store, "search-1", checkpoint, coordinator="host")
|
||||
state = Coordinator(resumed, lambda task: {"status": "ok", "cost_usd": 0, "intents": []}).run()
|
||||
assert state["checkpoint"]["spent_usd"] == 0.05
|
||||
assert state["checkpoint"]["tasks"] == 2
|
||||
assert state["checkpoint"]["tasks"] == 3
|
||||
|
||||
|
||||
def test_invalid_fact_fails_closed_and_records_failure(tmp_path):
|
||||
|
||||
@@ -34,7 +34,7 @@ def test_probe_pilot_end_to_end(tmp_path, capsys, side, outcome):
|
||||
records = [json.loads(line) for line in output.read_text().splitlines()]
|
||||
search, summary = records
|
||||
assert search["outcome"] == outcome
|
||||
assert search["model_calls"] == (4 if side == "vulnerable" else 5)
|
||||
assert search["model_calls"] == (3 if side == "vulnerable" else 5)
|
||||
assert search["input_bytes"] > 0
|
||||
assert search["board_bytes"] > 0
|
||||
assert search["spend"]["settled"] == 0
|
||||
@@ -122,10 +122,14 @@ def test_fixed_verification_never_acquires_vulnerable_revision(tmp_path, monkeyp
|
||||
|
||||
def test_worker_failure_is_not_no_evidence(tmp_path):
|
||||
pilot = make_pilot(tmp_path, dry_run=True)
|
||||
pilot.worker.client.responses = iter([{"content": "invalid json"}])
|
||||
from openultrasast.search.probe import materialise
|
||||
|
||||
pilot.sides, _ = materialise(tmp_path / "probe", "path")
|
||||
pilot.worker.client.responses = iter([{"content": "invalid json"}, {"content": "{}"}, {"content": "{}"}])
|
||||
record = pilot.run()
|
||||
assert record["outcome"] == "inconclusive"
|
||||
assert record["failures"] == [{"phase": "reason", "reason": "worker_execution_failed"}]
|
||||
assert record["failures"][0]["phase"] == "explore"
|
||||
assert "ValueError: explore must use tools or finish" in record["failures"][0]["reason"]
|
||||
|
||||
|
||||
def test_shared_budget_reserved_and_settled_on_error(tmp_path):
|
||||
@@ -215,7 +219,7 @@ def test_live_wiring_with_fake_mailbox_no_network(tmp_path, monkeypatch, verify_
|
||||
record = pilot.run()
|
||||
assert record["outcome"] == "demonstrated", record
|
||||
assert record["end_reason"] == "goal_met"
|
||||
assert record["model_calls"] == 4
|
||||
assert record["model_calls"] == 3
|
||||
assert record["executor_tasks"] == 3
|
||||
assert record["verify_tasks"] == (6 if verify_lane == "ax" else 0)
|
||||
assert record["tasks_submitted"] == (9 if verify_lane == "ax" else 3)
|
||||
@@ -320,3 +324,41 @@ def test_refused_shared_reservation_releases_task_allowance(tmp_path):
|
||||
worker.reason("facts: []", task)
|
||||
assert not client.calls
|
||||
assert task.reserved == task.spent == run.reserved == run.spent == 0
|
||||
|
||||
|
||||
@pytest.mark.parametrize("through_worker", [False, True])
|
||||
def test_explore_exception_keeps_sanitized_message(tmp_path, monkeypatch, through_worker):
|
||||
from contextlib import contextmanager
|
||||
|
||||
from openultrasast.search.worker import StubModel
|
||||
|
||||
pilot = make_pilot(tmp_path)
|
||||
monkeypatch.setenv("MODEL_API_KEY", "private-token-1234")
|
||||
|
||||
def fail():
|
||||
raise RuntimeError("mailbox failed: " + "app.py " * 100 + "https://host/private?token=secret private-token-1234: diagnostic tail")
|
||||
|
||||
class Broken(StubModel):
|
||||
def complete_chat_raw(self, **kwargs):
|
||||
fail()
|
||||
|
||||
@contextmanager
|
||||
def executor(*args, **kwargs):
|
||||
if not through_worker:
|
||||
fail()
|
||||
from types import SimpleNamespace
|
||||
|
||||
yield SimpleNamespace(client=None)
|
||||
|
||||
monkeypatch.setattr(pilot, "executor", executor)
|
||||
if through_worker:
|
||||
pilot.worker.client = Broken([])
|
||||
record = pilot.run()
|
||||
failure = record["failures"][0]
|
||||
assert failure["phase"] == "explore"
|
||||
assert failure["reason"].startswith("RuntimeError: ")
|
||||
assert "diagnostic tail" in failure["reason"]
|
||||
assert len(failure["reason"]) <= 300
|
||||
assert "app.py" not in failure["reason"]
|
||||
assert "https://" not in failure["reason"]
|
||||
assert "private-token-1234" not in failure["reason"]
|
||||
|
||||
@@ -229,3 +229,81 @@ def test_inprocess_executor_observes_all_brain_repository_commands(tmp_path):
|
||||
assert worker.tool("run", {"command": ["/bin/cat", "/workspace/sample.txt"]})["stdout"] == "needle"
|
||||
assert [c.name for c in executor.commands] == ["list_files", "read_file", "grep", "run"]
|
||||
assert [c.seq for c in executor.commands] == [1, 2, 3, 4]
|
||||
|
||||
|
||||
def test_empty_reason_retried_with_reminder_then_refused(tmp_path):
|
||||
model = StubModel([reply({}), reply({})])
|
||||
worker = Worker(model, repo=tmp_path, demo=tmp_path / "demo")
|
||||
board = Board(FileStore(tmp_path / "memory"), "refused", coordinator="host")
|
||||
result = Coordinator(board, worker).run()
|
||||
assert result["end_reason"] == "reason_refused"
|
||||
assert len(model.calls) == 2
|
||||
assert "No intents are open" in model.calls[1]["messages"][0]["content"]
|
||||
assert result["checkpoint"]["failures"][-1]["reason"] == "reason_refused"
|
||||
|
||||
|
||||
def test_reason_retry_can_recover(tmp_path):
|
||||
model = StubModel(
|
||||
[
|
||||
reply({}),
|
||||
reply({"intents": [{"description": "inspect entry", "from_facts": []}]}),
|
||||
tool("finish", failure="entry unavailable"),
|
||||
]
|
||||
)
|
||||
worker = Worker(model, repo=tmp_path, demo=tmp_path / "demo")
|
||||
board = Board(FileStore(tmp_path / "memory"), "retry", coordinator="host")
|
||||
state = Coordinator(board, worker, budget=SearchBudget(reason_rounds=1)).run()
|
||||
assert state["intents"][0]["status"] == "concluded"
|
||||
assert state["end_reason"] == "budget_spent"
|
||||
assert len(model.calls) == 3
|
||||
|
||||
|
||||
def test_bootstrap_durable_before_first_model_call(tmp_path):
|
||||
import yaml
|
||||
|
||||
board = Board(FileStore(tmp_path / "memory"), "bootstrap", coordinator="host")
|
||||
board.commit(
|
||||
writer="host",
|
||||
hints=[
|
||||
dict(id="candidate", step="reason", task_id="manifest", text=json.dumps(dict(family="path", file="app.py", function="main")))
|
||||
],
|
||||
)
|
||||
|
||||
class InspectingStub(StubModel):
|
||||
def complete_chat_raw(self, **kwargs):
|
||||
intent = board.state["intents"][0]
|
||||
assert intent["id"] == "bootstrap" and intent["status"] == "claimed"
|
||||
assert board.state["checkpoint"]["pending"]["step"] == "explore"
|
||||
snapshot = yaml.safe_load(kwargs["messages"][1]["content"].split("\nIntent:")[0])
|
||||
assert snapshot["family"] == "path"
|
||||
assert snapshot["candidate"]["function"] == "main"
|
||||
assert snapshot["budget_left"]["tasks"] == 0
|
||||
return super().complete_chat_raw(**kwargs)
|
||||
|
||||
model = InspectingStub([tool("finish", failure="entry unavailable")])
|
||||
worker = Worker(model, repo=tmp_path, demo=tmp_path / "demo")
|
||||
state = Coordinator(board, worker, budget=SearchBudget(tasks=1)).run()
|
||||
assert state["end_reason"] == "budget_spent"
|
||||
assert len(model.calls) == 1
|
||||
prompt = model.calls[0]["messages"][0]["content"]
|
||||
assert "Read the candidate function main in app.py" in prompt
|
||||
|
||||
|
||||
@pytest.mark.parametrize("step", ["reason", "explore"])
|
||||
def test_prompt_contracts_and_demo_example(tmp_path, step):
|
||||
from openultrasast.search.demo import validate_demo
|
||||
from openultrasast.search.worker import DEMO_GUIDE, ORACLE_GUIDE
|
||||
|
||||
model = StubModel([reply({}) if step == "reason" else tool("finish", failure="no input")])
|
||||
Worker(model, repo=tmp_path, demo=tmp_path / "demo")(task(step))
|
||||
prompt = model.calls[0]["messages"][0]["content"]
|
||||
assert DEMO_GUIDE in prompt and ORACLE_GUIDE in prompt
|
||||
for text in ("private nonce file", "secret rows", "marker location", "loopback listener", "alert('ousast-xss')"):
|
||||
assert text in prompt
|
||||
example = DEMO_GUIDE.removeprefix("Demo schema: ").split(". This is a minimal example")[0]
|
||||
assert validate_demo(json.loads(example))
|
||||
if step == "reason":
|
||||
assert "MUST propose 1-3" in prompt
|
||||
assert len(prompt) / 4 < 1000 # rough offline token estimate, not a tokenizer
|
||||
else:
|
||||
assert "file:line" in prompt and "final call for finish" in prompt
|
||||
|
||||
Reference in New Issue
Block a user