From 8a9d6919d91c4afedfd2cfde57c53c258cec910c Mon Sep 17 00:00:00 2001 From: Abdessamad Derraz <3028866+Abdess@users.noreply.github.com> Date: Sun, 18 Oct 2026 02:49:24 +0200 Subject: [PATCH] feat: record the pin a prose reread confirmed A prose citation whose text lived under several pins stayed in read again until the text changed, so fixnes, zesarux and emucorex, exact at their pins, would have refused every later bump. prose_written_at names the pin a reader confirmed; it settles the readings and never overrides a history with one candidate. --- emulators/emucorex.yml | 1 + emulators/fixnes.yml | 1 + emulators/zesarux.yml | 1 + schemas/emulator.schema.json | 5 +++++ scripts/profile_sync.py | 11 ++++++++++- tests/test_profile_sync.py | 33 +++++++++++++++++++++++++++++++++ wiki/profiling.md | 1 + 7 files changed, 52 insertions(+), 1 deletion(-) diff --git a/emulators/emucorex.yml b/emulators/emucorex.yml index 04bb9abc..b5012bfb 100644 --- a/emulators/emucorex.yml +++ b/emulators/emucorex.yml @@ -6,6 +6,7 @@ upstream: "https://github.com/PCSX2/pcsx2" author: "Oleksandr (sashkinbro)" profiled_date: "2026-10-04" source_commit: "9884c9f9a0c5d5df2f0c0c064ec41029a4528d72" +prose_written_at: "9884c9f9a0c5d5df2f0c0c064ec41029a4528d72" upstream_commit: "c10a5c9ad951c517dc48b722b5b84eb9bf7fdb42" core_version: "0.4.7" display_name: "Sony - PlayStation 2 (EmuCoreX)" diff --git a/emulators/fixnes.yml b/emulators/fixnes.yml index 1cf6804f..b9f61fe7 100644 --- a/emulators/fixnes.yml +++ b/emulators/fixnes.yml @@ -5,6 +5,7 @@ source: "https://github.com/libretro/fixNES" upstream: "https://github.com/FIX94/fixNES" profiled_date: "2026-03-23" source_commit: "72af6c6fd9d3b6c23d334ba7385b6a09225f2276" +prose_written_at: "72af6c6fd9d3b6c23d334ba7385b6a09225f2276" core_version: "Alpha v1.3.1" display_name: "Nintendo - NES / Famicom (fixNES)" cores: diff --git a/emulators/zesarux.yml b/emulators/zesarux.yml index ef0031e6..710e0088 100644 --- a/emulators/zesarux.yml +++ b/emulators/zesarux.yml @@ -4,6 +4,7 @@ core_classification: other source: "https://github.com/chernandezba/zesarux" upstream: "https://github.com/chernandezba/zesarux" source_commit: "8e1e8f3fcb5a8e677984df7aeaff30667682ab56" +prose_written_at: "8e1e8f3fcb5a8e677984df7aeaff30667682ab56" profiled_date: "2026-09-17" core_version: "13.1-SN" display_name: "Sinclair - ZX Spectrum and clones (ZEsarUX)" diff --git a/schemas/emulator.schema.json b/schemas/emulator.schema.json index 26a45828..f908a46b 100644 --- a/schemas/emulator.schema.json +++ b/schemas/emulator.schema.json @@ -800,6 +800,11 @@ "type": "string", "description": "Branch the port was read from, when it is not the default one." }, + "prose_written_at": { + "type": "string", + "pattern": "^[0-9a-f]{40}$", + "description": "The pin a reader confirmed every prose citation against. When the profile's history shows several pins under one text, profile_sync takes this one as the revision the text describes; it never overrides a single candidate." + }, "pin_frozen": { "type": "string", "description": "Why source_commit is held on purpose rather than followed. The profile is then judged against its own revision, like one pinned to a superseded tag: comparing it to HEAD says nothing and recaling would repoint its refs at code the build it documents never had." diff --git a/scripts/profile_sync.py b/scripts/profile_sync.py index ee663dad..a7e2e998 100644 --- a/scripts/profile_sync.py +++ b/scripts/profile_sync.py @@ -3016,14 +3016,23 @@ def _writing_pairs( Each reading lists (repo, writing pin, current pin) per declared repository. A repository whose text lived under several pins gives one reading per pin; the caller settles them when they all agree. + + `prose_written_at` names the pin a reader confirmed the prose against. + Among several candidate pins it is the answer: fixnes's doOpenFDSBIOS + citation lived under four pins, exact at the last, and with nothing to + record that reading the bump stayed refused for good. It never + overrides a history with a single candidate. """ options: list[list] = [] ambiguous: list[str] = [] + attested = str(document.get("prose_written_at") or "") for pin_field, mode, repo in repos: current = _pin_of(document, f"{pin_field}_commit", mode) if not current: continue written = _writing_pins(revisions, intro_sha, f"{pin_field}_commit", mode) + if attested in written: + written = [attested] if len(written) > 1: ambiguous.append(f"{pin_field}_commit {' -> '.join(written)}") # The current pin among the candidates reads as "no move" (None) for @@ -3336,7 +3345,7 @@ def _stale_prose_blocks(name: str, path: Path, args: argparse.Namespace) -> bool if stale: print( f"{name}: {len(stale)} prose citation(s) written under an older pin; " - "run --realign-prose or read them before the pin moves", + "run --realign-prose, or read them and set prose_written_at to the pin they match", file=sys.stderr, ) return bool(stale) diff --git a/tests/test_profile_sync.py b/tests/test_profile_sync.py index 8ea6016b..13123f31 100644 --- a/tests/test_profile_sync.py +++ b/tests/test_profile_sync.py @@ -2379,6 +2379,39 @@ class TestWriteDryRun(unittest.TestCase): self.assertEqual(readings, [[(repo, "pin1", "pin2")], []]) self.assertTrue(ambiguous) + def test_a_confirmed_pin_settles_the_readings(self): + """fixnes: doOpenFDSBIOS lived under four pins and was exact at the + last; with nothing to record that reading, the bump was refused for + good. prose_written_at names the pin and the readings collapse.""" + revisions = [ + ("c2", {"source_commit": "pin2", "notes": "x"}), + ("c1", {"source_commit": "pin1", "notes": "x"}), + ] + repo = object() + readings, _ambiguous = profile_sync._writing_pairs( + {"source_commit": "pin2", "prose_written_at": "pin2"}, + [("source", "", repo)], revisions, "c1", + ) + self.assertEqual(readings, [[]]) + later, _ = profile_sync._writing_pairs( + {"source_commit": "pin3", "prose_written_at": "pin2"}, + [("source", "", repo)], revisions, "c1", + ) + self.assertEqual(later, [[(repo, "pin2", "pin3")]]) + + def test_a_confirmed_pin_outside_the_history_changes_nothing(self): + revisions = [ + ("c2", {"source_commit": "pin2", "notes": "x"}), + ("c1", {"source_commit": "pin1", "notes": "x"}), + ] + repo = object() + readings, ambiguous = profile_sync._writing_pairs( + {"source_commit": "pin2", "prose_written_at": "elsewhere"}, + [("source", "", repo)], revisions, "c1", + ) + self.assertEqual(readings, [[(repo, "pin1", "pin2")], []]) + self.assertTrue(ambiguous) + def test_a_pin_keyed_by_build_mode_is_read_for_its_mode(self): """eka2l1 pins a dict per build: read as a string, it gave no repository and its prose was never judged.""" diff --git a/wiki/profiling.md b/wiki/profiling.md index 0eeda1f6..d3fd67b9 100644 --- a/wiki/profiling.md +++ b/wiki/profiling.md @@ -355,6 +355,7 @@ which CI validates every profile against. | `known_hash_adler32` | expected Adler-32 hash (Dolphin's DSP ROMs); pair with `adler32_byteswap` when the code hashes 16-bit byte-swapped data | | `adler32_pairs_with`, `adler32_pairs` | the file hashed together with this one and the combinations the code accepts, this file's value first, in table order: Dolphin warns on a DSP ROM pair outside its `known_roms` even when each file passes alone | | `pin_frozen` | why `source_commit` is held on purpose rather than followed. The profile is then judged against its own revision, like one pinned to a superseded tag | +| `prose_written_at` | the pin a reader confirmed the prose citations against. When the profile's history shows a text living under several pins, profile_sync takes this one as the revision the text describes, so a bump is no longer refused for a citation already reread. It never overrides a history with a single candidate | | `upstream_gone` | why the declared upstream can no longer be reached, when that is settled rather than transient. Records the death instead of leaving the profile failing every pass; profile_sync reports it as declared, and reports the contradiction if the forge answers again | | `source_mirror` | a repository carrying the same tree, consulted after `source` and `upstream`. What keeps a profile checkable once its own forge stops answering | | `validation` | checks the code performs: `size`, `crc32`, `md5`, `sha1`, `adler32`, `signature`, `crypto`. Can be a list or dict `{core: [...], upstream: [...]}` for divergent checks |