v1.8.2 — Critical: back-matter promotion UUID collision fix
v1.8.2 — Critical: back-matter promotion crashes on cross-doc UUID collision
Production hotfix. Single critical fix. Unblocks v1.8.0 / v1.8.1
deploys that crashed mid-migration with:
ActiveRecord::RecordInvalid: Validation failed: Uuid has already been taken
Root cause
back_matter_resources.uuid carries a GLOBAL unique index. The v1.8.0
back-matter promotion stored the original OSCAL source uuid directly
as BMR.uuid. Two docs that legitimately reference the same source
uuid (very common across SSP / SAR / SAP / CDEF for shared NIST
800-53 references) crashed the second doc's INSERT. ECS task
exit-1'd at ~50s into the migration, before any health-check grace
period would matter. Loop.
Mid-failure state was even worse: previous failed deploys left
partial BMR rows in the database. Resume-from-partial hit the same
crash because the per-doc dedup check only looked at the local doc
(idempotent per-doc, NOT global).
The v1.8.0 release notes claimed both migrations were "idempotent
on re-run" — only per-doc idempotency was implemented. Test
fixtures used per-doc-unique UUIDs so the spec coverage missed the
cross-doc collision case.
Fix
Match the SAR cross-doc copy pattern already used by
sar_documents_controller#copy_back_matter_into_sar:
- BMR.uuid: SecureRandom.uuid (globally fresh, no collision)
- source uuid preserved in resource_data['source_uuid']
Per-doc dedup check (for idempotent re-runs) now matches BOTH:
- back_matter_resources.uuid = source_uuid
(legacy v1.8.0 imports that succeeded) - back_matter_resources.resource_data->>'source_uuid' = source_uuid
(v1.8.2+ imports)
All three upgrade states recover cleanly:
Fresh v1.8.2 installs promote with fresh uuid +
source_uuid metadata
Already-completed v1.8.0 deploys re-run skips (legacy uuid
dedup hit)
Mid-failed v1.8.1 deploys resume continues from where
it left off; no duplicate
creates against existing
partial rows
Files changed
app/services/concerns/back_matter_promotable.rb
Runtime parser path uses fresh uuid + source_uuid stash. Dual-
key dedup helper covers both legacy and new BMRs.
db/migrate/20260526120000_promote_cdef_back_matter_imports.rb
db/migrate/20260526200000_promote_remaining_back_matter_imports.rb
Existing v1.8.0 migration bodies updated in place. Anyone who
already ran them cleanly is unaffected (schema_migrations
records them done; the rake never re-runs).
app/models/sparc_config.rb
VERSION 1.8.1 -> 1.8.2
Specs
spec/services/concerns/back_matter_promotable_spec.rb (new, 9)
Cross-doc UUID collision regression (the original crash);
fresh-uuid + source_uuid metadata preservation; per-doc
idempotency via both legacy uuid path and new resource_data
path; resume-from-partial-v1.8.1 (the actual production state);
unchanged behavior on v4 skip / nil input / audit emission.
Test result
bundle exec rspec 2541 examples, 0 failures, 2 pending (+9 new)
bin/rubocop 0 offenses
Upgrade notes
- No schema changes. No data destruction.
- Stuck on v1.7.x because v1.8.0/v1.8.1 crashed: deploy v1.8.2
directly. Migration resumes from partial state. - Already on v1.8.0/v1.8.1 with the migration completed
successfully: v1.8.2 is a no-op on existing data. - Newly-imported back-matter going forward will have BMR.uuid
distinct from the OSCAL source UUID. The source is recoverable
from resource_data['source_uuid']. The OSCAL export uses
BMR.uuid as the resource uuid (unchanged behavior).
What's next (v1.8.3)
Architectural fix so this class of bug stops killing deploys
regardless of any future migration bug: deferred data migrations
as a Solid Queue job, container comes up immediately, migration
runs in background with admin status page + structured log
emission. Already designed; v1.8.3 will land as a normal feature
PR after v1.8.2 stabilizes in production.
🤖 Generated with Claude Code