diff --git a/catalog_console.py b/catalog_console.py index 0d6f007..0438379 100644 --- a/catalog_console.py +++ b/catalog_console.py @@ -15,10 +15,16 @@ Flow: Load -> Review -> Sign. blob SHA for the rest of this review pass. 2. Review -- a semantic diff (catalog_review.diff_catalogs), one card per changed entry, with risk annotations - (catalog_review.entry_risk_findings) and a live registry - lookup. Every changed entry must be individually - acknowledged (its checkbox ticked) before Sign unlocks. - There is no "acknowledge all" -- see catalog_review.py. + (catalog_review.entry_risk_findings). A registry lookup for + each entry's npm/PyPI package kicks off automatically, one + worker thread per entry, the moment the cards are built -- + it is the one check a reviewer can't do by eye, so it must + never depend on a click. It fails soft (a dead registry + shows "unavailable", never blocks review or Sign) and a + per-card "Re-check" button covers manual retries. Every + changed entry must be individually acknowledged (its + checkbox ticked) before Sign unlocks. There is no + "acknowledge all" -- see catalog_review.py. 3. Sign -- re-fetches the current blob SHA and refuses to sign unless it still matches the pinned SHA from step 1 (TOCTOU fix: catalog_review.can_sign). On success, writes @@ -497,11 +503,11 @@ class EntryCard(QWidget): flabel.setStyleSheet("color: #1565c0;") layout.addWidget(flabel) - self.registry_label = plain_label("Registry lookup: not checked yet.") + self.registry_label = plain_label("Registry lookup: loading...") layout.addWidget(self.registry_label) - check_btn = QPushButton("Check registry") - check_btn.clicked.connect(self._run_registry_lookup) - layout.addWidget(check_btn) + recheck_btn = QPushButton("Re-check") + recheck_btn.clicked.connect(self._run_registry_lookup) + layout.addWidget(recheck_btn) self.blocking = any(f.severity == "blocking" for f in findings) self.checkbox = QCheckBox( @@ -519,13 +525,24 @@ class EntryCard(QWidget): ) layout.addWidget(self.checkbox) + # Registry lookup is the one check a reviewer can't do by eye -- it's + # what catches a typosquatted/hijacked package (it already caught + # firecrawl-mcp in the seed data). It must run automatically as soon + # as the card exists, not wait on a click a tired maintainer might + # skip at 11pm. Off the GUI thread (RegistryLookupWorker is a + # QThread) and fails soft: a dead/slow registry can never gate + # review or signing, it just leaves this entry's lookup showing + # "unavailable". The "Re-check" button above stays for retrying a + # failed/unavailable lookup by hand. + self._run_registry_lookup() + def _run_registry_lookup(self): entry = self.change.new or {} refs = review.extract_package_refs(entry) if not refs: self.registry_label.setText("Registry lookup: no npm/PyPI package in this entry.") return - self.registry_label.setText("Registry lookup: checking...") + self.registry_label.setText("Registry lookup: loading...") self._worker = RegistryLookupWorker(refs, self._all_entry_ids) self._worker.done.connect(self._on_registry_result) self._worker.start()