"""Review workflow tests (T03): promote/reject/discuss + idempotent re-review.""" import os import sys sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from session_memory.curate.catalog import Catalog # noqa: E402 from session_memory.curate.review import ( # noqa: E402 APPROVE, DISCUSS, REJECT, ReviewLog, candidate_to_pattern, review, ) from session_memory.curate.schema import SolutionPattern # noqa: E402 def _candidate(key="success:clean_pass:outcome", freq=18, flavors=("claude", "grok")): return { "key": key, "polarity": key.split(":")[0], "signal_type": key.split(":")[1], "locus": key.split(":")[2], "title": "cross-flavor success: clean pass", "frequency": freq, "flavors": list(flavors), "repos": ["agentic-resources"], "sessions": [f"s{i}" for i in range(freq)], "cross_flavor": len(flavors) > 1, "cost_impact": 12.5, } def _decider(action, rationale="because"): return lambda cand: (action, rationale) def test_approve_promotes_to_catalog(tmp_path): cat = Catalog(str(tmp_path / "catalog")) log = ReviewLog(str(tmp_path / "reviews.jsonl")) res = review([_candidate()], _decider(APPROVE), cat, log) assert len(res.approved) == 1 p = cat.load(SolutionPattern.make_id("success:clean_pass:outcome")) assert p is not None assert p.scope.flavors == ["claude", "grok"] assert set(p.rendering_hints) == {"claude", "grok"} assert p.provenance.evidence["frequency"] == 18 def test_reject_records_no_catalog_write(tmp_path): cat = Catalog(str(tmp_path / "catalog")) log = ReviewLog(str(tmp_path / "reviews.jsonl")) res = review([_candidate()], _decider(REJECT), cat, log) assert res.rejected == ["success:clean_pass:outcome"] assert cat.list() == [] def test_discuss_defers_and_is_not_final(tmp_path): cat = Catalog(str(tmp_path / "catalog")) log = ReviewLog(str(tmp_path / "reviews.jsonl")) res = review([_candidate()], _decider(DISCUSS), cat, log) assert res.deferred == ["success:clean_pass:outcome"] # not recorded as final -> a later pass re-surfaces it res2 = review([_candidate()], _decider(APPROVE), cat, log) assert len(res2.approved) == 1 def test_prior_reject_remembered_same_evidence(tmp_path): cat = Catalog(str(tmp_path / "catalog")) log_path = str(tmp_path / "reviews.jsonl") review([_candidate()], _decider(REJECT), cat, ReviewLog(log_path)) # fresh log instance (reloads from disk) + same evidence -> skipped res = review([_candidate()], _decider(APPROVE), cat, ReviewLog(log_path)) assert res.skipped == ["success:clean_pass:outcome"] assert cat.list() == [] def test_changed_evidence_resurfaces(tmp_path): cat = Catalog(str(tmp_path / "catalog")) log_path = str(tmp_path / "reviews.jsonl") review([_candidate(freq=18)], _decider(REJECT), cat, ReviewLog(log_path)) # more evidence now -> not skipped, gets re-reviewed res = review([_candidate(freq=40)], _decider(APPROVE), cat, ReviewLog(log_path)) assert len(res.approved) == 1 def test_candidate_to_pattern_defaults(): p = candidate_to_pattern(_candidate(flavors=("claude",))) assert p.status == "provisional" assert p.rendering_hints["claude"]["target"] == "CLAUDE.md" assert p.polarity == "success"