diff --git a/app/assets/services/ingest.py b/app/assets/services/ingest.py index 6a9034845..888e0b28d 100644 --- a/app/assets/services/ingest.py +++ b/app/assets/services/ingest.py @@ -199,9 +199,10 @@ def ingest_existing_file( """Register an existing on-disk file as an asset stub. If a reference already exists for this path, updates mtime_ns and job_id. - When the file's mtime or size differ from what was recorded the file has - been rewritten, so its hash, size and enrichment state are discarded and - the enricher re-hashes it. An unchanged file keeps the hash it already has. + A file whose mtime and size still match what was recorded keeps its hash, + size and enrichment state. If the file was rewritten, or there is no + recorded hash left to keep, that state is discarded and the enricher + re-hashes it. For brand-new paths, inserts a stub record (hash=NULL) for immediate UX visibility. @@ -219,10 +220,16 @@ def ingest_existing_file( existing_ref = get_reference_by_file_path(session, locator) if existing_ref is not None: asset = existing_ref.asset - unchanged = asset is not None and verify_file_unchanged( - mtime_db=existing_ref.mtime_ns, - size_db=asset.size_bytes, - stat_result=stat_result, + # mtime + size is the subsystem-wide staleness test (see + # verify_file_unchanged); strengthening it is not a local decision. + unchanged = ( + asset is not None + and asset.hash is not None + and verify_file_unchanged( + mtime_db=existing_ref.mtime_ns, + size_db=asset.size_bytes, + stat_result=stat_result, + ) ) now = get_utc_now() diff --git a/tests-unit/assets_test/services/test_ingest.py b/tests-unit/assets_test/services/test_ingest.py index 4ee45a2ed..2ae887cd6 100644 --- a/tests-unit/assets_test/services/test_ingest.py +++ b/tests-unit/assets_test/services/test_ingest.py @@ -730,6 +730,31 @@ class TestIngestExistingFileContentState: assert ref.is_missing is False assert asset.hash == "blake3:seeded" + def test_restored_reference_is_re_enriched_when_its_asset_lost_its_hash( + self, mock_create_session, output_root: Path, session: Session + ): + content = b"image data" + first = _write_output(output_root, "ComfyUI_00001_.png", content) + second = _write_output(output_root, "ComfyUI_00002_.png", content) + asset, first_ref = _seed_hashed_reference(session, first) + _, second_ref = _seed_hashed_reference(session, second, asset=asset) + second_ref.deleted_at = get_utc_now() + session.commit() + + _rewrite_with_newer_mtime(first, b"other data") + assert ingest_existing_file(str(first)) is True + session.expire_all() + assert asset.hash is None + + assert ingest_existing_file(str(second)) is True + + session.expire_all() + assert second_ref.deleted_at is None + assert ( + second_ref.enrichment_level == ENRICHMENT_STUB + ), "a reference that lost its hash must be re-enriched, not left at HASHED" + assert second_ref.asset_id != asset.id + def test_soft_deleted_reference_with_rewritten_file_is_restored_and_reset( self, mock_create_session, output_root: Path, session: Session ):