)]}'
{"/PATCHSET_LEVEL":[{"author":{"_account_id":9303,"name":"Abhishek Kekane","email":"akekane@redhat.com","username":"abhishekkekane"},"change_message_id":"a1ef5db2c7afcf31c2ca17ca1ce0234173b2c89f","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":1,"id":"e23f0fc1_41d79dc6","updated":"2026-05-25 13:23:56.000000000","message":"recheck unrelated failure in web download test","commit_id":"bc9f83271c091607d49e0b89baae3e3bce1dc92c"},{"author":{"_account_id":8122,"name":"Cyril Roelandt","email":"cyril@redhat.com","username":"cyril.roelandt.enovance"},"change_message_id":"08c73d76756133521d61db32b6e5dbb17a6e8ccc","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":2,"id":"41d5623d_faa1f7b7","updated":"2026-06-15 16:25:42.000000000","message":"A few questions from my first pass at the code.","commit_id":"1dbeacaa8105801da73d24a5065f33b390931b77"},{"author":{"_account_id":9303,"name":"Abhishek Kekane","email":"akekane@redhat.com","username":"abhishekkekane"},"change_message_id":"5061567cd8570dc0e399fffec0ef0c827cccbb79","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":2,"id":"649a667c_64ae407b","updated":"2026-05-28 07:21:38.000000000","message":"Manual testing reference - https://etherpad.opendev.org/p/glance-parallel-import-manual-testing","commit_id":"1dbeacaa8105801da73d24a5065f33b390931b77"},{"author":{"_account_id":9303,"name":"Abhishek Kekane","email":"akekane@redhat.com","username":"abhishekkekane"},"change_message_id":"c15577de6ceffdd01bd53d015543acc7f09e285d","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":2,"id":"9cb709b2_a9e80163","updated":"2026-06-16 05:42:47.000000000","message":"Thank you Cyril and Pranali for reviews, I have made some changes as per suggestions.","commit_id":"1dbeacaa8105801da73d24a5065f33b390931b77"},{"author":{"_account_id":19138,"name":"Pranali Deore","email":"pdeore@redhat.com","username":"PranaliD"},"change_message_id":"389d53fd47d973618736b63dab35e7fe822a3e15","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":2,"id":"5a1e5015_d9a8ef17","updated":"2026-06-16 05:02:54.000000000","message":"Thanks Abhishek for the patch and the manual testing guide. I tried testing the scenarios in my local devstack setup(cheap + robust), the parallel path worked as expected. Test coverage looks good.","commit_id":"1dbeacaa8105801da73d24a5065f33b390931b77"},{"author":{"_account_id":8122,"name":"Cyril Roelandt","email":"cyril@redhat.com","username":"cyril.roelandt.enovance"},"change_message_id":"60625f694d81563ab88f1cb66da212a8e6362012","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":3,"id":"8576d665_2c7e6117","updated":"2026-06-26 22:18:30.000000000","message":"Thanks for taking the time to read and address my comments. The changes are minimal since the previous patchset, so I\u0027ll assume Pranali\u0027s +1 still stands and I\u0027ll merge this right away.","commit_id":"e1ce6290a27e4bbc6961b5014a95685931a8bb7c"},{"author":{"_account_id":9303,"name":"Abhishek Kekane","email":"akekane@redhat.com","username":"abhishekkekane"},"change_message_id":"c5b74b55d5107dbb539d8488b5b4e2f673720241","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":3,"id":"8b615d1a_2fc2f9fd","updated":"2026-06-16 11:54:32.000000000","message":"recheck post failure","commit_id":"e1ce6290a27e4bbc6961b5014a95685931a8bb7c"},{"author":{"_account_id":9303,"name":"Abhishek Kekane","email":"akekane@redhat.com","username":"abhishekkekane"},"change_message_id":"25cbdaa9b36315e7ce4076c9e7dd864b4461a8bc","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":3,"id":"c7c4d46e_b2353749","updated":"2026-06-16 16:58:09.000000000","message":"recheck timeout","commit_id":"e1ce6290a27e4bbc6961b5014a95685931a8bb7c"},{"author":{"_account_id":19138,"name":"Pranali Deore","email":"pdeore@redhat.com","username":"PranaliD"},"change_message_id":"866f6bd2f3459a95f5d031718890e2b324ce9555","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":4,"id":"d50dc4cc_9b9ad0d5","updated":"2026-07-02 10:39:19.000000000","message":"Re ran the scenarios again on my local setup. Works as expected. Thanks again! LGTM.","commit_id":"c2b00ba09aea981a305c185fdcab34788e7b36e0"}],"doc/source/admin/interoperable-image-import.rst":[{"author":{"_account_id":8122,"name":"Cyril Roelandt","email":"cyril@redhat.com","username":"cyril.roelandt.enovance"},"change_message_id":"08c73d76756133521d61db32b6e5dbb17a6e8ccc","unresolved":true,"context_lines":[{"line_number":406,"context_line":"store). After each store upload, Glance compares that store\u0027s reported size,"},{"line_number":407,"context_line":"checksum, and ``os_hash_value`` to image metadata when those fields are set;"},{"line_number":408,"context_line":"those values come from ``add_with_multihash`` for that write and are not"},{"line_number":409,"context_line":"precomputed on staging in the parallel path."},{"line_number":410,"context_line":""},{"line_number":411,"context_line":"Failure and recovery"},{"line_number":412,"context_line":"~~~~~~~~~~~~~~~~~~~~"}],"source_content_type":"text/x-rst","patch_set":2,"id":"786b9ba1_4edbb768","line":409,"range":{"start_line":409,"start_character":39,"end_line":409,"end_character":43},"updated":"2026-06-15 16:25:42.000000000","message":"I found this whole paragraph a bit difficult to understand.\n\n\"each parallel worker re-reads staging and runs them again\". What does each worker read and run? The validation data and the validation process?","commit_id":"1dbeacaa8105801da73d24a5065f33b390931b77"},{"author":{"_account_id":9303,"name":"Abhishek Kekane","email":"akekane@redhat.com","username":"abhishekkekane"},"change_message_id":"c15577de6ceffdd01bd53d015543acc7f09e285d","unresolved":false,"context_lines":[{"line_number":406,"context_line":"store). After each store upload, Glance compares that store\u0027s reported size,"},{"line_number":407,"context_line":"checksum, and ``os_hash_value`` to image metadata when those fields are set;"},{"line_number":408,"context_line":"those values come from ``add_with_multihash`` for that write and are not"},{"line_number":409,"context_line":"precomputed on staging in the parallel path."},{"line_number":410,"context_line":""},{"line_number":411,"context_line":"Failure and recovery"},{"line_number":412,"context_line":"~~~~~~~~~~~~~~~~~~~~"}],"source_content_type":"text/x-rst","patch_set":2,"id":"3a43f447_e15c3801","line":409,"range":{"start_line":409,"start_character":39,"end_line":409,"end_character":43},"in_reply_to":"786b9ba1_4edbb768","updated":"2026-06-16 05:42:47.000000000","message":"Done, each worker re-opens the staged file and re-runs the size/format checks (not signature; that runs once). Simplified the paragraph accordingly.","commit_id":"1dbeacaa8105801da73d24a5065f33b390931b77"}],"glance/async_/flows/api_image_import.py":[{"author":{"_account_id":8122,"name":"Cyril Roelandt","email":"cyril@redhat.com","username":"cyril.roelandt.enovance"},"change_message_id":"08c73d76756133521d61db32b6e5dbb17a6e8ccc","unresolved":true,"context_lines":[{"line_number":85,"context_line":"Maximum number of target stores to import to at the same time for one"},{"line_number":86,"context_line":"image import task."},{"line_number":87,"context_line":""},{"line_number":88,"context_line":"When this value is 1, store imports run sequentially as today. When it is"},{"line_number":89,"context_line":"greater than 1 and more than one store is requested, Glance uses a bounded"},{"line_number":90,"context_line":"worker pool and per-store work queue for copying the data from staging to"},{"line_number":91,"context_line":"target stores (glance-direct, web-download, glance-download) with multi-backend"}],"source_content_type":"text/x-python","patch_set":2,"id":"317891d7_40499282","line":88,"range":{"start_line":88,"start_character":56,"end_line":88,"end_character":61},"updated":"2026-06-15 16:25:42.000000000","message":"In this context, \"today\" means \"before this patch\", so it will be harder to understand once we merge. Please remove \"as today\".","commit_id":"1dbeacaa8105801da73d24a5065f33b390931b77"},{"author":{"_account_id":9303,"name":"Abhishek Kekane","email":"akekane@redhat.com","username":"abhishekkekane"},"change_message_id":"c15577de6ceffdd01bd53d015543acc7f09e285d","unresolved":false,"context_lines":[{"line_number":85,"context_line":"Maximum number of target stores to import to at the same time for one"},{"line_number":86,"context_line":"image import task."},{"line_number":87,"context_line":""},{"line_number":88,"context_line":"When this value is 1, store imports run sequentially as today. When it is"},{"line_number":89,"context_line":"greater than 1 and more than one store is requested, Glance uses a bounded"},{"line_number":90,"context_line":"worker pool and per-store work queue for copying the data from staging to"},{"line_number":91,"context_line":"target stores (glance-direct, web-download, glance-download) with multi-backend"}],"source_content_type":"text/x-python","patch_set":2,"id":"394b2645_45f265cb","line":88,"range":{"start_line":88,"start_character":56,"end_line":88,"end_character":61},"in_reply_to":"317891d7_40499282","updated":"2026-06-16 05:42:47.000000000","message":"Acknowledged","commit_id":"1dbeacaa8105801da73d24a5065f33b390931b77"},{"author":{"_account_id":8122,"name":"Cyril Roelandt","email":"cyril@redhat.com","username":"cyril.roelandt.enovance"},"change_message_id":"08c73d76756133521d61db32b6e5dbb17a6e8ccc","unresolved":true,"context_lines":[{"line_number":978,"context_line":"        LOG.debug(\"Skipping plugins on \u0027copy-image\u0027 job.\")"},{"line_number":979,"context_line":""},{"line_number":980,"context_line":"    # NOTE(abhishekk): Use parallel import to store if enabled and more than"},{"line_number":981,"context_line":"    # one store is requested."},{"line_number":982,"context_line":"    if parallel_import.should_use_parallel_store_import(import_method, stores):"},{"line_number":983,"context_line":"        parallel_import.add_parallel_store_import_tasks("},{"line_number":984,"context_line":"            flow, task_id, task_type, task_repo, action_wrapper, file_uri,"}],"source_content_type":"text/x-python","patch_set":2,"id":"259667ab_ce7b4870","line":981,"range":{"start_line":981,"start_character":6,"end_line":981,"end_character":9},"updated":"2026-06-15 16:25:42.000000000","message":"So we have two different code paths here. Could we use a single code path, and treat sequential imports as a \"parallel import with 1 worker\"? This would probably make maintainance easier and the code simpler.\n\nThe main issue preventing us from implementing this would be the way this works with regards to the database (and the special pending://parallel-import/), I guess.","commit_id":"1dbeacaa8105801da73d24a5065f33b390931b77"},{"author":{"_account_id":9303,"name":"Abhishek Kekane","email":"akekane@redhat.com","username":"abhishekkekane"},"change_message_id":"c15577de6ceffdd01bd53d015543acc7f09e285d","unresolved":true,"context_lines":[{"line_number":978,"context_line":"        LOG.debug(\"Skipping plugins on \u0027copy-image\u0027 job.\")"},{"line_number":979,"context_line":""},{"line_number":980,"context_line":"    # NOTE(abhishekk): Use parallel import to store if enabled and more than"},{"line_number":981,"context_line":"    # one store is requested."},{"line_number":982,"context_line":"    if parallel_import.should_use_parallel_store_import(import_method, stores):"},{"line_number":983,"context_line":"        parallel_import.add_parallel_store_import_tasks("},{"line_number":984,"context_line":"            flow, task_id, task_type, task_repo, action_wrapper, file_uri,"}],"source_content_type":"text/x-python","patch_set":2,"id":"afa119a7_8bac94b6","line":981,"range":{"start_line":981,"start_character":6,"end_line":981,"end_character":9},"in_reply_to":"259667ab_ce7b4870","updated":"2026-06-16 05:42:47.000000000","message":"Agreed that a single path would be nicer long term. I kept the existing _ImportToStore flow for max_parallel_stores\u003d1 (and single-store imports) so we don’t risk changing well-tested default behavior.\n\nThe parallel path only runs when max_parallel_stores \u003e 1 and multiple stores are requested. It needs extra DB handling (pending://parallel-import/ placeholders, per-store status, worker coordination) that the serial flow doesn’t have. Unifying them would be a larger refactor — happy to do that as a follow-up.","commit_id":"1dbeacaa8105801da73d24a5065f33b390931b77"},{"author":{"_account_id":9303,"name":"Abhishek Kekane","email":"akekane@redhat.com","username":"abhishekkekane"},"change_message_id":"d0eb65e671845bdd10d821ea8529e4728a0f5482","unresolved":false,"context_lines":[{"line_number":978,"context_line":"        LOG.debug(\"Skipping plugins on \u0027copy-image\u0027 job.\")"},{"line_number":979,"context_line":""},{"line_number":980,"context_line":"    # NOTE(abhishekk): Use parallel import to store if enabled and more than"},{"line_number":981,"context_line":"    # one store is requested."},{"line_number":982,"context_line":"    if parallel_import.should_use_parallel_store_import(import_method, stores):"},{"line_number":983,"context_line":"        parallel_import.add_parallel_store_import_tasks("},{"line_number":984,"context_line":"            flow, task_id, task_type, task_repo, action_wrapper, file_uri,"}],"source_content_type":"text/x-python","patch_set":2,"id":"94ab7574_54230b35","line":981,"range":{"start_line":981,"start_character":6,"end_line":981,"end_character":9},"in_reply_to":"844fb243_9377c6e6","updated":"2026-07-02 06:31:13.000000000","message":"Done","commit_id":"1dbeacaa8105801da73d24a5065f33b390931b77"},{"author":{"_account_id":8122,"name":"Cyril Roelandt","email":"cyril@redhat.com","username":"cyril.roelandt.enovance"},"change_message_id":"60625f694d81563ab88f1cb66da212a8e6362012","unresolved":true,"context_lines":[{"line_number":978,"context_line":"        LOG.debug(\"Skipping plugins on \u0027copy-image\u0027 job.\")"},{"line_number":979,"context_line":""},{"line_number":980,"context_line":"    # NOTE(abhishekk): Use parallel import to store if enabled and more than"},{"line_number":981,"context_line":"    # one store is requested."},{"line_number":982,"context_line":"    if parallel_import.should_use_parallel_store_import(import_method, stores):"},{"line_number":983,"context_line":"        parallel_import.add_parallel_store_import_tasks("},{"line_number":984,"context_line":"            flow, task_id, task_type, task_repo, action_wrapper, file_uri,"}],"source_content_type":"text/x-python","patch_set":2,"id":"844fb243_9377c6e6","line":981,"range":{"start_line":981,"start_character":6,"end_line":981,"end_character":9},"in_reply_to":"afa119a7_8bac94b6","updated":"2026-06-26 22:18:30.000000000","message":"Yes, this was just a wild idea. I\u0027m afraid if we refactor, we\u0027ll end up with some weird code to handle the small differences and that won\u0027t be much better.\n\nNot changing well tested code makes sense, so let\u0027s keep this as is.","commit_id":"1dbeacaa8105801da73d24a5065f33b390931b77"}],"glance/async_/flows/parallel_api_image_import.py":[{"author":{"_account_id":8122,"name":"Cyril Roelandt","email":"cyril@redhat.com","username":"cyril.roelandt.enovance"},"change_message_id":"08c73d76756133521d61db32b6e5dbb17a6e8ccc","unresolved":true,"context_lines":[{"line_number":47,"context_line":"LOG \u003d logging.getLogger(__name__)"},{"line_number":48,"context_line":""},{"line_number":49,"context_line":"_SUPPORTED_IMPORT_METHODS \u003d frozenset("},{"line_number":50,"context_line":"    (\u0027glance-direct\u0027, \u0027web-download\u0027, \u0027glance-download\u0027))"},{"line_number":51,"context_line":""},{"line_number":52,"context_line":"# Location metadata: marks in-progress rows owned by this import flow."},{"line_number":53,"context_line":"LOC_META_IMPORT_TAG \u003d \u0027os_glance_parallel_import\u0027"}],"source_content_type":"text/x-python","patch_set":2,"id":"b20c846e_105603b7","line":50,"range":{"start_line":50,"start_character":13,"end_line":50,"end_character":19},"updated":"2026-06-15 16:25:42.000000000","message":"So, \u0027copy-image\u0027 is not supported in parallel imports? I think this should be added to the documentation.\n\nAlso, maybe call this variable _SUPPORTED_PARALLEL_IMPORT_METHODS just to be extra clear.","commit_id":"1dbeacaa8105801da73d24a5065f33b390931b77"},{"author":{"_account_id":9303,"name":"Abhishek Kekane","email":"akekane@redhat.com","username":"abhishekkekane"},"change_message_id":"c15577de6ceffdd01bd53d015543acc7f09e285d","unresolved":false,"context_lines":[{"line_number":47,"context_line":"LOG \u003d logging.getLogger(__name__)"},{"line_number":48,"context_line":""},{"line_number":49,"context_line":"_SUPPORTED_IMPORT_METHODS \u003d frozenset("},{"line_number":50,"context_line":"    (\u0027glance-direct\u0027, \u0027web-download\u0027, \u0027glance-download\u0027))"},{"line_number":51,"context_line":""},{"line_number":52,"context_line":"# Location metadata: marks in-progress rows owned by this import flow."},{"line_number":53,"context_line":"LOC_META_IMPORT_TAG \u003d \u0027os_glance_parallel_import\u0027"}],"source_content_type":"text/x-python","patch_set":2,"id":"3053c804_55257daf","line":50,"range":{"start_line":50,"start_character":13,"end_line":50,"end_character":19},"in_reply_to":"b20c846e_105603b7","updated":"2026-06-16 05:42:47.000000000","message":"Acknowledged","commit_id":"1dbeacaa8105801da73d24a5065f33b390931b77"},{"author":{"_account_id":8122,"name":"Cyril Roelandt","email":"cyril@redhat.com","username":"cyril.roelandt.enovance"},"change_message_id":"08c73d76756133521d61db32b6e5dbb17a6e8ccc","unresolved":true,"context_lines":[{"line_number":91,"context_line":""},{"line_number":92,"context_line":"def _placeholder_location_url(image_id, store):"},{"line_number":93,"context_line":"    \"\"\"Placeholder DB url until the real store location is known.\"\"\""},{"line_number":94,"context_line":"    return \u0027pending://parallel-import/%s/%s\u0027 % (image_id, store or \u0027_\u0027)"},{"line_number":95,"context_line":""},{"line_number":96,"context_line":""},{"line_number":97,"context_line":"def _location_metadata(loc):"}],"source_content_type":"text/x-python","patch_set":2,"id":"84fdf806_2efab923","line":94,"range":{"start_line":94,"start_character":40,"end_line":94,"end_character":41},"updated":"2026-06-15 16:25:42.000000000","message":"Are there cases where \"store\" might be None here? If so I\u0027d kind of like to see this explained in the docstring.","commit_id":"1dbeacaa8105801da73d24a5065f33b390931b77"},{"author":{"_account_id":9303,"name":"Abhishek Kekane","email":"akekane@redhat.com","username":"abhishekkekane"},"change_message_id":"c15577de6ceffdd01bd53d015543acc7f09e285d","unresolved":false,"context_lines":[{"line_number":91,"context_line":""},{"line_number":92,"context_line":"def _placeholder_location_url(image_id, store):"},{"line_number":93,"context_line":"    \"\"\"Placeholder DB url until the real store location is known.\"\"\""},{"line_number":94,"context_line":"    return \u0027pending://parallel-import/%s/%s\u0027 % (image_id, store or \u0027_\u0027)"},{"line_number":95,"context_line":""},{"line_number":96,"context_line":""},{"line_number":97,"context_line":"def _location_metadata(loc):"}],"source_content_type":"text/x-python","patch_set":2,"id":"fd8dad9d_0dfdde8b","line":94,"range":{"start_line":94,"start_character":40,"end_line":94,"end_character":41},"in_reply_to":"84fdf806_2efab923","updated":"2026-06-16 05:42:47.000000000","message":"Acknowledged","commit_id":"1dbeacaa8105801da73d24a5065f33b390931b77"},{"author":{"_account_id":8122,"name":"Cyril Roelandt","email":"cyril@redhat.com","username":"cyril.roelandt.enovance"},"change_message_id":"08c73d76756133521d61db32b6e5dbb17a6e8ccc","unresolved":true,"context_lines":[{"line_number":177,"context_line":""},{"line_number":178,"context_line":""},{"line_number":179,"context_line":"def _prepare_upload_data(data_iter, container_format, disk_format):"},{"line_number":180,"context_line":"    \"\"\"Apply in-flight format inspection for bare images (see set_data).\"\"\""},{"line_number":181,"context_line":"    if container_format \u003d\u003d \u0027bare\u0027:"},{"line_number":182,"context_line":"        LOG.debug(\u0027Enabling in-flight format inspection for %s\u0027, disk_format)"},{"line_number":183,"context_line":"        return format_inspector.InspectWrapper(data_iter)"}],"source_content_type":"text/x-python","patch_set":2,"id":"ebe20bae_a1f9029e","line":180,"range":{"start_line":180,"start_character":13,"end_line":180,"end_character":15},"updated":"2026-06-15 16:25:42.000000000","message":"We have very similar code in glance/location.py:610. When using parallel image import, do we not end up in glance/location.py:610? This would mean we have two paths to get to the format inspector.","commit_id":"1dbeacaa8105801da73d24a5065f33b390931b77"},{"author":{"_account_id":8122,"name":"Cyril Roelandt","email":"cyril@redhat.com","username":"cyril.roelandt.enovance"},"change_message_id":"60625f694d81563ab88f1cb66da212a8e6362012","unresolved":false,"context_lines":[{"line_number":177,"context_line":""},{"line_number":178,"context_line":""},{"line_number":179,"context_line":"def _prepare_upload_data(data_iter, container_format, disk_format):"},{"line_number":180,"context_line":"    \"\"\"Apply in-flight format inspection for bare images (see set_data).\"\"\""},{"line_number":181,"context_line":"    if container_format \u003d\u003d \u0027bare\u0027:"},{"line_number":182,"context_line":"        LOG.debug(\u0027Enabling in-flight format inspection for %s\u0027, disk_format)"},{"line_number":183,"context_line":"        return format_inspector.InspectWrapper(data_iter)"}],"source_content_type":"text/x-python","patch_set":2,"id":"a14abf45_5f3b6c75","line":180,"range":{"start_line":180,"start_character":13,"end_line":180,"end_character":15},"in_reply_to":"7bb66e82_16c8da0f","updated":"2026-06-26 22:18:30.000000000","message":"OK, let\u0027s do this in the future.","commit_id":"1dbeacaa8105801da73d24a5065f33b390931b77"},{"author":{"_account_id":9303,"name":"Abhishek Kekane","email":"akekane@redhat.com","username":"abhishekkekane"},"change_message_id":"c15577de6ceffdd01bd53d015543acc7f09e285d","unresolved":false,"context_lines":[{"line_number":177,"context_line":""},{"line_number":178,"context_line":""},{"line_number":179,"context_line":"def _prepare_upload_data(data_iter, container_format, disk_format):"},{"line_number":180,"context_line":"    \"\"\"Apply in-flight format inspection for bare images (see set_data).\"\"\""},{"line_number":181,"context_line":"    if container_format \u003d\u003d \u0027bare\u0027:"},{"line_number":182,"context_line":"        LOG.debug(\u0027Enabling in-flight format inspection for %s\u0027, disk_format)"},{"line_number":183,"context_line":"        return format_inspector.InspectWrapper(data_iter)"}],"source_content_type":"text/x-python","patch_set":2,"id":"7bb66e82_16c8da0f","line":180,"range":{"start_line":180,"start_character":13,"end_line":180,"end_character":15},"in_reply_to":"ebe20bae_a1f9029e","updated":"2026-06-16 05:42:47.000000000","message":"Correct — parallel import does not call image.set_data(), so it never hits location.py:610. Workers upload via store_api.add_with_multihash() and manage image_locations rows directly, bypassing the normal image data path that serial _ImportToStore uses.\n\n_prepare_upload_data() mirrors the InspectWrapper logic from set_data so bare images get the same in-flight format inspection. Happy to extract a small shared helper in a follow-up if we want to deduplicate it.","commit_id":"1dbeacaa8105801da73d24a5065f33b390931b77"}]}
