feat(review): offer inspection-profile choices for untraceable batches

Qwen image-edit batches run several KSamplers, each with its own
TextEncodeQwenImageEdit prompt, all saved under one prefix. The scan's
default profile knows neither the encoder nor the string nodes, so every
candidate was unresolved and the files left Needs review as "could not be
traced", although the inspection profile resolves every text.

When the default profile is ambiguous without a choice and nothing links
the file, the scan now resolves it with the inspection profile and, with at
least two choosable candidates, queues the item from that evidence. The
evidence records profile "inspection" and the semantics table version
(SHA-256 of its canonical JSON); the item's resolver_version folds the
profile, RESOLVER_VERSION and the table version, so a table change is
handled like a resolver change (I2). Apply re-resolves the capture with the
recorded profile, re-checks the chosen candidate hash and links through the
compatible-only ingestion. The inspection profile never links on its own,
ingestion keeps the default profile, and the live monitor never queues.
This commit is contained in:
Vito Sansevero
2026-10-23 15:18:15 -07:00
parent b4c7a15045
commit 341757ea8e
7 changed files with 717 additions and 38 deletions
+28 -5
View File
@@ -45,7 +45,7 @@ try:
parse_page_json,
)
from ...utils.prompt_pair_resolver import resolve_prompt_pair
from ...utils.review_capture import reviewable
from ...utils.review_capture import inspection_offers_choice, reviewable
except ImportError:
from database.operations import PromptDatabase
from database.pair_ingestion import ingest_resolved_pair
@@ -56,7 +56,7 @@ except ImportError:
parse_page_json,
)
from utils.prompt_pair_resolver import resolve_prompt_pair
from utils.review_capture import reviewable
from utils.review_capture import inspection_offers_choice, reviewable
try:
@@ -252,6 +252,22 @@ def _file_size(path):
return None
def _untraced_outcome(media_file, canonical, ambiguous, untraced):
"""The status of an unlinked file the default profile found ambiguous
without a choice: ``"ambiguous"`` when the inspection profile offers one
(the file moves from ``untraced`` to ``ambiguous``), else ``"unresolved"``.
Asked only when ``ambiguous`` is a list (the scan, which records review
items), so other callers never pay for the heavier profile.
"""
if ambiguous is None or not inspection_offers_choice(canonical):
return "unresolved"
if untraced is not None and media_file in untraced:
untraced.remove(media_file)
ambiguous.append(media_file)
return "ambiguous"
def _with_file_size(canonical, media_file):
"""Record the file's size in ``file_info`` unless the metadata carries one.
@@ -1714,7 +1730,10 @@ class AdminRoutesMixin:
``"ambiguous"`` (nothing linked it) is a Needs review case. An
ambiguous result with nothing to choose (``reviewable`` is False: fewer
than two candidates carry a prompt) that nothing links returns
``"unresolved"`` instead, and ``media_file`` goes to ``untraced``.
``"unresolved"`` instead, and ``media_file`` goes to ``untraced``,
unless the inspection profile offers a choice (only asked when
``ambiguous`` is a list, so only by the scan): then it is
``"ambiguous"`` and goes to ``ambiguous``. That profile never links.
"""
if not metadata:
return None
@@ -1756,9 +1775,13 @@ class AdminRoutesMixin:
# Graphs without a recognized sampler keep the legacy extractor below.
prompt_text = self._graph_prompt_text(metadata)
if not prompt_text:
return "unresolved" if unchoosable else resolution["state"]
if unchoosable:
return _untraced_outcome(media_file, canonical, ambiguous, untraced)
return resolution["state"]
elif resolution["state"] == "ambiguous":
return "unresolved" if unchoosable else "ambiguous"
if unchoosable:
return _untraced_outcome(media_file, canonical, ambiguous, untraced)
return "ambiguous"
elif resolution["state"] == "unresolved" and not isinstance(
canonical.get("parameters"), str
):