fix(ingestion): address the #344 review (history re-check, keys, repair)

- Images without an embedded API graph: the scan now asks recorded
  history before skipping a file with no prompt metadata, and the monitor
  gives a keyless unlinked image one history-only re-check when its run's
  outputs are recorded (a seal notification from the outputs write; at
  most 1000 waiting images, each for an hour).
- Run keys: an image is unkeyable when any positive node has no key, so a
  two-pass graph is never linked through the other pass; links follow
  ComfyUI's is_link exactly.
- A failed save marks the node's run key like an empty editor, so an
  older entry of that key can't stand in for this run.
- A placeholder is the latest entry: the image stays unlinked without
  falling through to history, the same rule as a popped placeholder.
- An index on execution_run_outputs(filename, subfolder, root_key,
  path_style) for the history lookup, added by migration and accepted by
  the output schema check (any other index is still refused).
- Repair tool: unlink removes only the link record and only while the
  image is still linked to the prompt it was judged on; every run prints
  what --apply does.
- Real-host case proves the re-run's PromptManager node was cached.
This commit is contained in:
Vito Sansevero
2026-10-25 14:23:17 -07:00
parent 4ab0119580
commit 417a05a2db
16 changed files with 742 additions and 63 deletions
+21
View File
@@ -119,6 +119,25 @@ def install_output_schema(conn: sqlite3.Connection) -> None:
conn.execute(_table_sql(table))
# The one optional, non-unique read index (#342): output file -> run. Older
# libraries may lack it; migrate_execution_history adds it.
OUTPUT_READ_INDEX = "idx_history_read_execution_run_outputs"
OUTPUT_READ_INDEX_COLUMNS = ("filename", "subfolder", "root_key", "path_style")
def _is_read_index(table: str, index: tuple, keys: tuple) -> bool:
"""Exactly the attribution read index, as the migration creates it."""
return (
table == "execution_run_outputs"
and index[1] == OUTPUT_READ_INDEX
and not index[2]
and index[3] == "c"
and not index[4]
and tuple(key[2] for key in keys) == OUTPUT_READ_INDEX_COLUMNS
and not any(key[3] or key[4] != "BINARY" for key in keys)
)
def output_schema_error(conn: sqlite3.Connection) -> str | None:
"""Reject altered schemas and invalid rows before returning bounded DTOs."""
if conn.execute(
@@ -157,6 +176,8 @@ def output_schema_error(conn: sqlite3.Connection) -> str | None:
keys = tuple(
row for row in conn.execute(f'PRAGMA index_xinfo("{name}")') if row[5]
)
if _is_read_index(table, index, keys):
continue
if (
not index[2]
or index[3] != "pk"