feat: Catalog Console -- maintainer-only review + signing tool (#62) #66

Merged
the_og merged 2 commits from feat/62-catalog-console into main 2026-07-12 18:04:54 -04:00
Showing only changes of commit d6fc6845c4 - Show all commits
+26 -9
View File
@@ -15,10 +15,16 @@ Flow: Load -> Review -> Sign.
blob SHA for the rest of this review pass. blob SHA for the rest of this review pass.
2. Review -- a semantic diff (catalog_review.diff_catalogs), one card per 2. Review -- a semantic diff (catalog_review.diff_catalogs), one card per
changed entry, with risk annotations changed entry, with risk annotations
(catalog_review.entry_risk_findings) and a live registry (catalog_review.entry_risk_findings). A registry lookup for
lookup. Every changed entry must be individually each entry's npm/PyPI package kicks off automatically, one
acknowledged (its checkbox ticked) before Sign unlocks. worker thread per entry, the moment the cards are built --
There is no "acknowledge all" -- see catalog_review.py. 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 3. Sign -- re-fetches the current blob SHA and refuses to sign unless
it still matches the pinned SHA from step 1 (TOCTOU fix: it still matches the pinned SHA from step 1 (TOCTOU fix:
catalog_review.can_sign). On success, writes catalog_review.can_sign). On success, writes
@@ -497,11 +503,11 @@ class EntryCard(QWidget):
flabel.setStyleSheet("color: #1565c0;") flabel.setStyleSheet("color: #1565c0;")
layout.addWidget(flabel) 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) layout.addWidget(self.registry_label)
check_btn = QPushButton("Check registry") recheck_btn = QPushButton("Re-check")
check_btn.clicked.connect(self._run_registry_lookup) recheck_btn.clicked.connect(self._run_registry_lookup)
layout.addWidget(check_btn) layout.addWidget(recheck_btn)
self.blocking = any(f.severity == "blocking" for f in findings) self.blocking = any(f.severity == "blocking" for f in findings)
self.checkbox = QCheckBox( self.checkbox = QCheckBox(
@@ -519,13 +525,24 @@ class EntryCard(QWidget):
) )
layout.addWidget(self.checkbox) 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): def _run_registry_lookup(self):
entry = self.change.new or {} entry = self.change.new or {}
refs = review.extract_package_refs(entry) refs = review.extract_package_refs(entry)
if not refs: if not refs:
self.registry_label.setText("Registry lookup: no npm/PyPI package in this entry.") self.registry_label.setText("Registry lookup: no npm/PyPI package in this entry.")
return 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 = RegistryLookupWorker(refs, self._all_entry_ids)
self._worker.done.connect(self._on_registry_result) self._worker.done.connect(self._on_registry_result)
self._worker.start() self._worker.start()