feat: Catalog Console -- maintainer-only review + signing tool (#62) #66
+26
-9
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user