From e3709703638c2b01c865255d01c67f4389233bf3 Mon Sep 17 00:00:00 2001 From: Abdessamad Derraz <3028866+Abdess@users.noreply.github.com> Date: Tue, 27 Oct 2026 21:16:54 +0100 Subject: [PATCH] feat: write a finding again on new evidence A finding line is written again when the records behind it change, so an open finding cites the oracle runs rather than a frame comparison. A contradicted validation shows the checks the runs prove, content and size, beside the profile's list. --- scripts/campaign.py | 3 ++- scripts/campaign_rules.py | 6 ++++-- tests/test_campaign.py | 10 ++++++++++ 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/scripts/campaign.py b/scripts/campaign.py index c8cbd69da..5648cb23d 100644 --- a/scripts/campaign.py +++ b/scripts/campaign.py @@ -1413,7 +1413,8 @@ class Campaign: key = self.finding_key(emulator, content, entry, verdict) previous = self.latest_findings().get(key) if previous and previous["status"] == status and previous["rule"] == verdict.rule \ - and previous["run_shows"] == verdict.value and previous["profile_says"] == says: + and previous["run_shows"] == verdict.value and previous["profile_says"] == says \ + and previous["evidence"] == list(verdict.evidence): return line = mr.seal_line({ "schema_version": 1, "found_at": now_iso(), "key": key, "emulator": emulator, diff --git a/scripts/campaign_rules.py b/scripts/campaign_rules.py index dc21258c3..89726a3d1 100644 --- a/scripts/campaign_rules.py +++ b/scripts/campaign_rules.py @@ -214,12 +214,14 @@ def rule_validation(zeros: Run | None, half: Run | None, none: Run, present: Run if size is False and "size" in declared: problems.append(f"the first half of {name} is run, the profile declares a size check") statement = f"the core {' and '.join(shown)} of {name}" + # What the runs show, for a finding beside what the profile says. + checks = {"content": content, "size": size} if problems: - return Verdict("validation_contradicted", "validation", said, + return Verdict("validation_contradicted", "validation", checks, f"{statement}: {'; '.join(problems)}", evidence) if missing_size: if not editable: - return Verdict("validation_contradicted", "validation", said, + return Verdict("validation_contradicted", "validation", checks, f"{statement}: the per-build validation has no size check", evidence) value = [*(entry.get("validation") or []), "size"] return Verdict("validation_size_added", "validation", value, diff --git a/tests/test_campaign.py b/tests/test_campaign.py index 071200d8b..e32d5f422 100644 --- a/tests/test_campaign.py +++ b/tests/test_campaign.py @@ -217,6 +217,7 @@ class RuleTable(unittest.TestCase): for zeros, half, entry in cases: verdict = rules.rule_validation(zeros, half, NONE_BOOTS, PRESENT, entry) self.assertEqual((verdict.rule, verdict.apply), ("validation_contradicted", False), entry) + self.assertEqual(set(verdict.value), {"content", "size"}) self.assertFalse(rules.agrees(entry, verdict)) self.assertIsNone(rules.rule_validation(run(rules.UNKNOWN), None, NONE_BOOTS, PRESENT, {"name": "b"})) @@ -866,6 +867,15 @@ class Findings(Workspace): self.c.settle("fake", self.content, self.core, 0, self.entry(), [later]) self.assertEqual([line["status"] for line in self.lines()], ["open", "resolved"]) + def test_new_evidence_writes_the_finding_again(self) -> None: + verdict = rules.Verdict("unsettled", "hle_fallback", None, "x", (self.RID,)) + self.c.settle("fake", self.content, self.core, 0, self.entry(), [verdict]) + self.c.settle("fake", self.content, self.core, 0, self.entry(), [verdict]) + later = rules.Verdict("unsettled", "hle_fallback", None, "x", ("2026-10-28-000000000000",)) + self.c.settle("fake", self.content, self.core, 0, self.entry(), [later]) + self.assertEqual([line["evidence"] for line in self.lines()], + [[self.RID], ["2026-10-28-000000000000"]]) + def test_never_opened_is_flagged(self) -> None: verdict = rules.rule_never_opened(self.entry(1), [self.RID], "Fake Game (World)") self.c.settle("fake", self.content, self.core, 1, self.entry(1), [verdict])