ES /docs

Database import retries exhausted: migration id(1843) - ActiveRecord::RecordNotFound: Couldn't find

Validation: 824fa57f-da39-4076-8304-1a67aece327b

Verdict: APPROVED#

Completeness#

Plan Item Status Evidence
migrate_panos 진입점에서 abandoned 원본 pano 를 non-destructive 필터링하여 filtered_pano_data 로 하위 호출에 전달 PASS app/operations/migration_import_operation.rb: hunk @@ -311 shows filtered_pano_data = filter_out_abandoned_panos(pano_data, capture_org_id) inserted before the transaction, and all subsequent calls (migrate_model('pano', ...), migrate_model('pano_resource', ...), migrate_model('mask', ...), migrate_resource_object, pano_data.dig('pano', ...) for tile check, migrate_tile_object, migrate_mask_object, set_pano_states_done) now pass filtered_pano_data
걸러진 pano 수를 Cupix::Logger.info 로 기록 (class/method 메타 포함) PASS app/operations/migration_import_operation.rb: hunk @@ -346 (new method filter_out_abandoned_panos) contains Cupix::Logger.info("Migration - migrate_panos: skipping abandoned panos (capture: #{capture_org_id}, count: #{abandoned_ids.size}, ids: #{abandoned_ids})", class: self.class.name, method: __method__)
migrate_tile_object (line 708) .find(new_id)unscoped.find(new_id) PASS Hunk @@ -705,7 shows model_with_tile = model_name.camelize.constantize.unscoped.find(new_id) replacing scoped find
migrate_mask_object (line 755) .find(new_id)unscoped.find(new_id) PASS Hunk @@ -752,7 shows new_pano = Pano.unscoped.find(new_id)
set_pano_states_done (line 793) .find(new_id)unscoped.find(new_id) PASS Hunk @@ -790,7 shows pano = Pano.unscoped.find(new_id)

Acceptance Criteria#

Criterion Status Evidence
migrate_panos filters pano_data['pano'] where data['state'] == 'abandoned' before downstream calls PASS filter_out_abandoned_panos iterates original_panos.each_with_object([]) and pushes ids where entry.dig('data', 'state') == 'abandoned'; result assigned to filtered['pano'] = original_panos.reject {...}; called in migrate_panos before transaction
Info log includes filtered count and original ids with class/method meta PASS Cupix::Logger.info("... count: #{abandoned_ids.size}, ids: #{abandoned_ids}", class: self.class.name, method: __method__) in new helper
All .find(new_id) under migration_import_operation.rb are unscoped.find(new_id) (three sites) PASS Three hunks (lines ~708/755/793 in old numbering) each replace scoped .find(new_id) with .unscoped.find(new_id); no other .find(new_id) sites appear in diff — diff만으로 나머지 부재 확인 불가하나 계획된 3지점 모두 커버됨
Original pano_data argument is not mutated PASS Helper uses pano_data.dup and reassigns filtered['pano'] = original_panos.reject {...} (returns new hash from reject), no in-place delete/[]= on caller-owned hash. Early returns on blank/empty simply return original reference without modification.
Existing behavior preserved for non-abandoned panos PASS Helper returns pano_data unchanged when original_panos.blank? or abandoned_ids.empty?; otherwise only abandoned entries are rejected — the non-abandoned entries preserve identity in the new hash
bundle exec rubocop app/operations/migration_import_operation.rb passes UNVERIFIABLE diff만으로 검증 불가 — 런타임 확인 필요
bundle exec srb tc app/operations/migration_import_operation.rb passes UNVERIFIABLE diff만으로 검증 불가 — 런타임 확인 필요

Issues Found#

차단 이슈 없음.

Observations#

  • The helper filter_out_abandoned_panos performs a shallow pano_data.dup; only the 'pano' key is replaced with a new hash. Other top-level keys (e.g., 'pano_resource', 'mask', 'tile') still reference the original nested hashes. This is fine for the current call sites (they only read), but any future mutation of nested sections would still leak to the caller. Non-blocking.
  • The helper's early-return path (return pano_data if abandoned_ids.empty?) returns the caller's original hash without logging — consistent with the intent of "log only when filtering occurs", which matches the plan wording ("걸러진 pano 수").
  • abandoned_ids collects string keys (pano_id from hash iteration) and the log emits them directly; suitable for observability, matches plan wording ("원본 id").
  • Rubocop/Sorbet acceptance criteria cannot be validated from the diff alone.