fix: arbitrate slots on the strongest profile proof

This commit is contained in:
Abdessamad Derraz committed 2026-10-06 04:32:19 +02:00
1 parent a8415c33c3
commit 16f4c071fe
2 files changed
+46 -4

No files matched your search

+26 -4
View File
@@ -319,6 +319,15 @@ def pack_overrides(
return overrides return overrides
def _strongest(claims: list[Claim]) -> Claim:
"""The profile claim with the strongest proof; the first emulator breaks ties.
yaps2 pins its GameIndex.yaml by sha1 while armsx2 and lrps2 only match a
path: alphabetical order served armsx2's copy to all three.
"""
return max(claims, key=lambda claim: (_claim_rank(claim), -claims.index(claim)))
def arbitrate(conflict: Conflict, mode: str, addressee: str = "platform") -> Decision: def arbitrate(conflict: Conflict, mode: str, addressee: str = "platform") -> Decision:
"""Decide a contested destination for the pack being built. """Decide a contested destination for the pack being built.
@@ -333,7 +342,7 @@ def arbitrate(conflict: Conflict, mode: str, addressee: str = "platform") -> Dec
addressee decides; the loss is reported rather than absorbed, because the addressee decides; the loss is reported rather than absorbed, because the
cause is an upstream declaration that needs fixing at its source. cause is an upstream declaration that needs fixing at its source.
""" """
profile = conflict.profile_claims[0] profile = _strongest(conflict.profile_claims)
if addressee == "emulator": if addressee == "emulator":
return Decision(conflict, profile, ADDRESSEE) return Decision(conflict, profile, ADDRESSEE)
if not nativemode.reads_file_contents(mode): if not nativemode.reads_file_contents(mode):
@@ -345,11 +354,24 @@ def format_decision(decision: Decision) -> str:
"""One line naming the contested slot, the winner and the ground for it.""" """One line naming the contested slot, the winner and the ground for it."""
conflict = decision.conflict conflict = decision.conflict
if decision.serves_both: if decision.serves_both:
return ( winner = decision.winner.local_path
f"{conflict.destination}: serve {decision.winner.local_path} " served = sorted({
f"({', '.join(conflict.emulators) or 'profile'}); the frontend only " c.emulator for c in conflict.profile_claims
if c.emulator and c.local_path == winner
})
others = sorted({
c.emulator for c in conflict.profile_claims
if c.emulator and c.local_path != winner
})
line = (
f"{conflict.destination}: serve {winner} "
f"({', '.join(served) or 'profile'}); the frontend only "
"checks the path, so both are satisfied" "checks the path, so both are satisfied"
) )
if others:
# One path holds one file: an emulator pinning another is not served.
line += f"; {', '.join(others)} expected another file"
return line
if decision.reason == ADDRESSEE: if decision.reason == ADDRESSEE:
return ( return (
f"{conflict.destination}: serve {decision.winner.local_path}, " f"{conflict.destination}: serve {decision.winner.local_path}, "
+20
View File
@@ -555,5 +555,25 @@ class ManifestFollowsTheArbitration(unittest.TestCase):
self.assertEqual(by_dest[dest]["repo_path"], path) self.assertEqual(by_dest[dest]["repo_path"], path)
class StrongestProfileClaimWins(unittest.TestCase):
"""yaps2 pins GameIndex.yaml by sha1; armsx2 matched a path only."""
def test_hash_proof_beats_path_proof(self):
from slots import Claim, Conflict, arbitrate, format_decision
def claim(emu, path, status):
return Claim("profile", "pcsx2/resources/GameIndex.yaml", "GameIndex.yaml",
emulator=emu, local_path=path, status=status)
conflict = Conflict(
"pcsx2/resources/GameIndex.yaml",
Claim("platform", "pcsx2/resources/GameIndex.yaml", "GameIndex.yaml",
local_path="p", status="path_exact"),
[claim("armsx2", "a", "path_exact"), claim("yaps2", "y", "sha1_exact")],
)
decision = arbitrate(conflict, "existence")
self.assertEqual(decision.winner.emulator, "yaps2")
self.assertIn("armsx2 expected another file", format_decision(decision))
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()