Skip to content

[JCC] - Add 'previousSSN' field to license upload process for correcting incorrect SSN uploads#1726

Open
landonshumway-ia wants to merge 13 commits into
csg-org:mainfrom
InspiringApps:feat/jcc-migrate-records
Open

[JCC] - Add 'previousSSN' field to license upload process for correcting incorrect SSN uploads#1726
landonshumway-ia wants to merge 13 commits into
csg-org:mainfrom
InspiringApps:feat/jcc-migrate-records

Conversation

@landonshumway-ia

@landonshumway-ia landonshumway-ia commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

There have been issues where states have entered the wrong SSN for a license record, but the last 4 of the SSN were still accurate so practitioners have been able to register under a license record and purchase privileges. We need to provide a way for states to correct the SSNs for their particular license records. This ticket involves adding a new optional field to the license upload API, 'previousSSN'. When this value is included with a properly formatted SSN, then the system will look up any existing records for the provider id associated with that SSN. If a matching license record is found, it will move that license record along with any associated privileges and other records over to the new provider id, and delete the old records under the previous ssn to remove the duplicate records.

Requirements List

  • This feature is gated with a feature flag so we can disable it if needed to prevent states from using it.

Testing List

  • yarn test:unit:all should run without errors or warnings
  • yarn serve should run without errors or warnings
  • yarn build should run without errors or warnings
  • For API configuration changes: CDK tests added/updated in backend/compact-connect/tests/unit/test_api.py
  • For API endpoint changes: OpenAPI spec updated to show latest endpoint configuration run compact-connect/bin/download_oas30.py
  • Code review

Closes #1725

Summary by CodeRabbit

  • New Features
    • Added support for SSN correction by accepting a previous SSN during license submission.
    • Implemented automated migration of affected records to the corrected provider and sent re-registration notifications when required.
    • Introduced a feature flag to control SSN-correction migration behavior.
  • Bug Fixes
    • Improved duplicate SSN detection during uploads.
    • Fixed rollback window handling for same-month date ranges with different times of day.
    • Ensured home-jurisdiction change notifications are published whenever the jurisdiction changes.
  • Documentation
    • Updated upload and API documentation to describe the new optional SSN-correction field.

@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 993a5849-7686-48a8-a330-35158657c86b

📥 Commits

Reviewing files that changed from the base of the PR and between 615fbc8 and 2bd5ea7.

📒 Files selected for processing (8)
  • backend/compact-connect/lambdas/nodejs/lib/email/base-email-service.ts
  • backend/compact-connect/lambdas/nodejs/lib/email/email-notification-service.ts
  • backend/compact-connect/lambdas/nodejs/tests/lib/email/base-email-service.test.ts
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py
  • backend/compact-connect/lambdas/python/common/cc_common/feature_flag_enum.py
  • backend/compact-connect/lambdas/python/common/tests/function/test_data_client_ssn_correction.py
  • backend/compact-connect/lambdas/python/provider-data-v1/handlers/provider_users.py
  • backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_provider_users_home_jurisdiction_change.py
💤 Files with no reviewable changes (1)
  • backend/compact-connect/lambdas/python/common/cc_common/feature_flag_enum.py

📝 Walkthrough

Walkthrough

Changes

SSN Correction Migration

Layer / File(s) Summary
Contracts and migration gating
backend/compact-connect/lambdas/python/common/..., backend/compact-connect/lambdas/python/provider-data-v1/handlers/..., backend/compact-connect/stacks/feature_flag_stack/..., backend/compact-connect/stacks/state_api_stack/...
Adds previousSSN and transient previousProviderId, introduces migration flag handling, conditionally strips migration data, and always validates duplicate SSN/license-type pairs.
SSN resolution and ingest routing
backend/compact-connect/lambdas/python/provider-data-v1/handlers/ingest.py, backend/compact-connect/lambdas/python/provider-data-v1/tests/unit/test_handlers/test_ingest.py
Resolves SSNs to provider IDs, publishes transient routing data without raw SSNs, and starts migration before normal ingestion.
Provider record and document migration
backend/compact-connect/lambdas/python/common/cc_common/data_model/..., backend/compact-connect/lambdas/python/provider-data-v1/tests/...
Migrates dependent records with transactional re-keying, partial/full teardown handling, concurrency protection, replay support, and S3 document movement.
Re-registration notification and infrastructure
backend/compact-connect/lambdas/nodejs/..., backend/compact-connect/lambdas/python/common/cc_common/email_service_client.py, backend/compact-connect/stacks/...
Adds the re-registration notification flow and grants ingest access to provider-user Cognito, S3, and notification resources.

Email Logging Protection

Layer / File(s) Summary
Email masking utilities and logging
backend/compact-connect/lambdas/nodejs/lib/email/..., backend/compact-connect/lambdas/nodejs/tests/lib/email/...
Adds email masking helpers and uses masked addresses in notification-service logs.

Rollback Month Boundary Fix

Layer / File(s) Summary
Rollback query boundary handling
backend/compact-connect/lambdas/python/disaster-recovery/handlers/rollback_license_upload.py, backend/compact-connect/lambdas/python/disaster-recovery/tests/function/test_rollback_license_upload.py
Normalizes month boundaries by clearing time components and adds same-month regression coverage.

Home-Jurisdiction Event Behavior

Layer / File(s) Summary
Unconditional jurisdiction event publication
backend/compact-connect/lambdas/python/provider-data-v1/handlers/provider_users.py, backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_provider_users_home_jurisdiction_change.py
Publishes home-jurisdiction change events whenever the jurisdiction changes, without feature-flag gating.

Estimated code review effort: 5 (Critical) | ~90 minutes

Sequence Diagram(s)

sequenceDiagram
  participant StateAPI
  participant preprocess_license_ingest
  participant DataClient
  participant S3
  participant Cognito
  participant EmailService
  StateAPI->>preprocess_license_ingest: submit previousSSN
  preprocess_license_ingest->>DataClient: resolve previousProviderId
  preprocess_license_ingest->>DataClient: migrate provider records
  DataClient->>S3: move provider documents
  DataClient->>Cognito: delete old user
  DataClient->>EmailService: request re-registration notification
Loading

Possibly related PRs

Suggested reviewers: jlkravitz, ChiefStief

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The PR includes unrelated changes to disaster-recovery rollback logic, home-jurisdiction notifications, and email masking beyond #1725. Split unrelated fixes into separate PRs so this one stays focused on SSN-correction migration.
Docstring Coverage ⚠️ Warning Docstring coverage is 43.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main change: adding previousSSN support for correcting SSN uploads.
Description check ✅ Passed The description covers the feature, feature-flag requirement, and issue closure, but the template's Description List section is missing.
Linked Issues check ✅ Passed The changes implement previousSSN migration, related record moves, old-record deletion, and feature-flag gating required by #1725.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@backend/compact-connect/lambdas/nodejs/lib/email/email-notification-service.ts`:
- Line 440: Avoid logging recipient email addresses in the ssn correction
reregistration notification flow. Update the logger call in the
email-notification service method that sends this notification so it keeps only
safe metadata about the compact/practitioner and removes the
recipients/specificEmails payload. If you need traceability, log a non-PII
indicator such as recipient count or delivery type instead, and apply the same
sanitizing pattern used by other notification methods in this file.

In
`@backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py`:
- Around line 3154-3175: The new-provider record creation path in
_build_new_provider_record_if_absent is race-prone because the existence check
and the later Put are not atomic. Update the write path used for the provider
summary record so the Put includes a condition like attribute_not_exists(pk),
matching the other conditional writes in this flow, and keep the existing
get_provider_top_level_record / ProviderRecordUtility.populate_provider_record
logic for the pre-check and record construction.
- Around line 2931-2932: `full_teardown` is being decided from a stale provider
snapshot, so add a commit-time concurrency guard in the teardown path. Update
`update_provider_license_and_schema_data` to revalidate the old provider’s
license set against the current database state immediately before deleting the
provider record, not just via the existing `dateOfUpdate` fence. Use the same
teardown/delete flow that currently handles `old_provider_records`,
`old_provider_license_records`, and the provider deletion block to ensure a
concurrent ingest cannot add a license and leave it orphaned.
- Around line 2887-2891: The SSN-correction migration currently infers
teardown/retry state from whether `old_provider_data` still exists, which breaks
replay after a later DynamoDB batch failure past the 100-item limit. Update the
migration flow in `data_client.py` to persist an explicit cleanup/teardown
marker (or equivalent state) when the old provider record is deleted, and have
the replay path use that marker instead of `old_provider_data is None`. Adjust
the logic around the `old_provider_registered_email` / Cognito delete /
re-registration email branch so it still runs correctly on retry even when the
old provider record has already been removed.

In `@backend/compact-connect/lambdas/python/provider-data-v1/handlers/ingest.py`:
- Around line 373-478: The teardown work in _perform_ssn_correction_migration is
coupled too tightly to migrate_provider_for_ssn_correction, so once
migration_performed becomes false on a retry,
_move_provider_documents_to_new_keyspace and
_delete_old_cognito_user_and_send_reregistration_email never run. Add a separate
persisted teardown state (for example in the existing migration result or a new
tracking record) so the S3 move and Cognito/email steps can resume independently
after a crash. Update the retry path in _perform_ssn_correction_migration to
check that teardown state rather than relying only on migration_performed, and
keep _move_s3_object and _delete_old_cognito_user_and_send_reregistration_email
idempotent.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e728dc78-699b-468b-8b55-c9d6593ab8fb

📥 Commits

Reviewing files that changed from the base of the PR and between a8102ac and 615fbc8.

📒 Files selected for processing (27)
  • backend/compact-connect/docs/README.md
  • backend/compact-connect/lambdas/nodejs/email-notification-service/lambda.ts
  • backend/compact-connect/lambdas/nodejs/lib/email/email-notification-service.ts
  • backend/compact-connect/lambdas/nodejs/tests/email-notification-service.test.ts
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/provider_record_util.py
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/common.py
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/license/api.py
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/schema/license/ingest.py
  • backend/compact-connect/lambdas/python/common/cc_common/email_service_client.py
  • backend/compact-connect/lambdas/python/common/cc_common/feature_flag_enum.py
  • backend/compact-connect/lambdas/python/common/cc_common/utils.py
  • backend/compact-connect/lambdas/python/common/tests/unit/test_data_model/test_schema/test_license.py
  • backend/compact-connect/lambdas/python/common/tests/unit/test_email_service_client.py
  • backend/compact-connect/lambdas/python/disaster-recovery/handlers/rollback_license_upload.py
  • backend/compact-connect/lambdas/python/disaster-recovery/tests/function/test_rollback_license_upload.py
  • backend/compact-connect/lambdas/python/provider-data-v1/handlers/bulk_upload.py
  • backend/compact-connect/lambdas/python/provider-data-v1/handlers/ingest.py
  • backend/compact-connect/lambdas/python/provider-data-v1/handlers/licenses.py
  • backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_bulk_upload.py
  • backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_ingest.py
  • backend/compact-connect/lambdas/python/provider-data-v1/tests/function/test_handlers/test_licenses.py
  • backend/compact-connect/lambdas/python/provider-data-v1/tests/unit/test_handlers/test_ingest.py
  • backend/compact-connect/pipeline/backend_stage.py
  • backend/compact-connect/stacks/feature_flag_stack/__init__.py
  • backend/compact-connect/stacks/ingest_stack.py
  • backend/compact-connect/stacks/state_api_stack/v1_api/api_model.py

Comment thread backend/compact-connect/lambdas/nodejs/lib/email/email-notification-service.ts Outdated
Comment on lines +3154 to +3175
def _build_new_provider_record_if_absent(
self,
*,
compact: str,
new_provider_id: str,
rekeyed_target_license: LicenseData,
rekeyed_privileges: list[PrivilegeData],
) -> ProviderData | None:
"""
Build a top-level provider record for the new provider from the migrated license/privileges, or return
None if the new provider already has one (a pre-existing record is never modified).
"""
try:
self.get_provider_top_level_record(compact=compact, provider_id=new_provider_id)
return None
except CCNotFoundException:
return ProviderRecordUtility.populate_provider_record(
current_provider_record=None,
license_record=rekeyed_target_license.to_dict(),
privilege_records=[privilege_data.to_dict() for privilege_data in rekeyed_privileges],
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Consider guarding the new-provider-record put with attribute_not_exists(pk).

The absence check (get_provider_top_level_record) and the later Put aren't atomic. Two concurrent migrations both targeting the same new provider id could both observe "absent" and race to write, with the second silently overwriting the first's provider summary. Adding ConditionExpression: 'attribute_not_exists(pk)' to the put (mirroring the conditioned writes used elsewhere in this method) would close this gap cheaply.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py`
around lines 3154 - 3175, The new-provider record creation path in
_build_new_provider_record_if_absent is race-prone because the existence check
and the later Put are not atomic. Update the write path used for the provider
summary record so the Put includes a condition like attribute_not_exists(pk),
matching the other conditional writes in this flow, and keep the existing
get_provider_top_level_record / ProviderRecordUtility.populate_provider_record
logic for the pre-check and record construction.

Comment on lines +373 to +478


def _perform_ssn_correction_migration(
*,
compact: str,
previous_provider_id: str,
new_provider_id: str,
jurisdiction: str,
license_type: str,
new_ssn_last_four: str,
):
"""
Orchestrate the migration of a practitioner's records after a state corrected the SSN on a license upload.

The DynamoDB migration runs first; on a full teardown the S3 document move, Cognito user deletion, and
re-registration email follow, each idempotent so an SQS retry of a partially-completed migration converges.
A concurrency conflict inside the migration raises, letting SQS redeliver the message after the visibility
timeout.
"""
logger.info('Performing SSN correction migration', previous_provider_id=previous_provider_id)

result = config.data_client.migrate_provider_for_ssn_correction(
compact=compact,
previous_provider_id=previous_provider_id,
new_provider_id=new_provider_id,
jurisdiction=jurisdiction,
license_type=license_type,
new_ssn_last_four=new_ssn_last_four,
)
if not result.migration_performed:
logger.info('No records to migrate for previous provider id; proceeding with normal ingest')
return

if result.full_teardown:
_move_provider_documents_to_new_keyspace(
compact=compact,
previous_provider_id=previous_provider_id,
new_provider_id=new_provider_id,
)
if result.old_provider_registered_email is not None:
_delete_old_cognito_user_and_send_reregistration_email(
compact=compact,
old_registered_email=result.old_provider_registered_email,
)


def _move_provider_documents_to_new_keyspace(*, compact: str, previous_provider_id: str, new_provider_id: str):
"""Move every object under the old provider id's S3 keyspace to the new provider id's.

Rather than relying on any single record type's tracked document keys, this lists everything under the
old provider's keyspace prefix (`compact/{compact}/provider/{provider_id}/`) and moves it, changing only
the provider id segment of each key. This picks up every document type a provider might have uploaded,
including ones this migration logic has no other knowledge of.

Runs after the DynamoDB migration (whose migrated militaryAffiliation records already reference the new
keys). Best-effort per object: a copy/delete failure is logged and skipped rather than failing the
migration, and re-running against an already-moved key is a no-op (the source object is simply absent).
"""
old_prefix = f'compact/{compact}/provider/{previous_provider_id}/'
new_prefix = f'compact/{compact}/provider/{new_provider_id}/'

paginator = config.s3_client.get_paginator('list_objects_v2')
for page in paginator.paginate(Bucket=config.provider_user_bucket_name, Prefix=old_prefix):
for s3_object in page.get('Contents', []):
old_key = s3_object['Key']
new_key = new_prefix + old_key[len(old_prefix) :]
_move_s3_object(old_key=old_key, new_key=new_key)


def _move_s3_object(*, old_key: str, new_key: str):
try:
config.s3_client.copy_object(
Bucket=config.provider_user_bucket_name,
CopySource={'Bucket': config.provider_user_bucket_name, 'Key': old_key},
Key=new_key,
)
config.s3_client.delete_object(Bucket=config.provider_user_bucket_name, Key=old_key)
except ClientError as e:
logger.error(
'Failed to move provider document to the new keyspace',
old_key=old_key,
new_key=new_key,
error=str(e),
)


def _delete_old_cognito_user_and_send_reregistration_email(*, compact: str, old_registered_email: str):
"""Delete the old provider's Cognito user and email the practitioner to re-register.

The email is only sent when a user was actually deleted in this run, so an SQS retry of a
partially-completed migration does not send a duplicate notification.
"""
try:
config.cognito_client.admin_delete_user(
UserPoolId=config.provider_user_pool_id,
Username=old_registered_email,
)
except config.cognito_client.exceptions.UserNotFoundException:
logger.info('Old Cognito user not found (already deleted); skipping re-registration email')
return

logger.info('Deleted old Cognito user after SSN correction; sending re-registration email')
config.email_service_client.send_provider_ssn_correction_reregistration_email(
compact=compact,
provider_email=old_registered_email,
)

@coderabbitai coderabbitai Bot Jul 9, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the relevant functions and surrounding code.
git ls-files 'backend/compact-connect/lambdas/python/provider-data-v1/handlers/ingest.py'
ast-grep outline backend/compact-connect/lambdas/python/provider-data-v1/handlers/ingest.py --view expanded

# Show the relevant sections with line numbers.
sed -n '1,220p' backend/compact-connect/lambdas/python/provider-data-v1/handlers/ingest.py | cat -n
sed -n '220,520p' backend/compact-connect/lambdas/python/provider-data-v1/handlers/ingest.py | cat -n

Repository: csg-org/CompactConnect

Length of output: 27642


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Focus on the migration implementation and its caller.
rg -n "migrate_provider_for_ssn_correction|_perform_ssn_correction_migration|_move_provider_documents_to_new_keyspace|_delete_old_cognito_user_and_send_reregistration_email|migration_performed|full_teardown" backend/compact-connect/lambdas/python/provider-data-v1/handlers/ingest.py backend/compact-connect/lambdas/python/provider-data-v1 -S

# Inspect the data client implementation that decides whether migration runs.
rg -n "def migrate_provider_for_ssn_correction|migration_performed|full_teardown|old_provider_registered_email" backend/compact-connect/lambdas/python/provider-data-v1 -S

Repository: csg-org/CompactConnect

Length of output: 5444


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find the data client implementation and any related status markers.
fd -a '.*' backend/compact-connect/lambdas/python/provider-data-v1 | sed -n '1,200p'
rg -n "class .*DataClient|def migrate_provider_for_ssn_correction|migration_performed|full_teardown|old_provider_registered_email|teardown pending|ssn correction" backend/compact-connect/lambdas/python/provider-data-v1 -S

Repository: csg-org/CompactConnect

Length of output: 8361


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
p = Path('backend/compact-connect/lambdas/python/provider-data-v1/handlers/ingest.py')
text = p.read_text()
for needle in ['def _perform_ssn_correction_migration', 'def _move_provider_documents_to_new_keyspace', 'def _move_s3_object', 'def _delete_old_cognito_user_and_send_reregistration_email']:
    idx = text.find(needle)
    print(f'\n=== {needle} @ {idx} ===')
    if idx != -1:
        start = max(0, text.rfind('\n', 0, idx-300))
        end = min(len(text), idx+2200)
        print(text[start:end])
PY

Repository: csg-org/CompactConnect

Length of output: 8647


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the implementation of the migrate_provider_for_ssn_correction return contract.
rg -n "migrate_provider_for_ssn_correction" backend -S

Repository: csg-org/CompactConnect

Length of output: 440


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show the exact implementation boundaries and any explicit retry/status handling.
python3 - <<'PY'
from pathlib import Path
p = Path('backend/compact-connect/lambdas/python/provider-data-v1/handlers/ingest.py')
lines = p.read_text().splitlines()
targets = [
    '_perform_ssn_correction_migration',
    '_move_provider_documents_to_new_keyspace',
    '_move_s3_object',
    '_delete_old_cognito_user_and_send_reregistration_email',
]
for i, line in enumerate(lines, 1):
    if any(line.startswith(f'def {t}') for t in targets):
        start = max(1, i-15)
        end = min(len(lines), i+120)
        print(f'\n### {line.strip()} (lines {start}-{end})')
        for j in range(start, end + 1):
            print(f'{j:4d}: {lines[j-1]}')
PY

Repository: csg-org/CompactConnect

Length of output: 15034


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
p = Path('backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py')
text = p.read_text().splitlines()
for i, line in enumerate(text, 1):
    if 'def migrate_provider_for_ssn_correction' in line:
        start = max(1, i - 40)
        end = min(len(text), i + 220)
        for j in range(start, end + 1):
            print(f'{j:4d}: {text[j-1]}')
        break
PY

Repository: csg-org/CompactConnect

Length of output: 15714


Keep teardown retryable after the DB migration.

migrate_provider_for_ssn_correction returns migration_performed=False once the corrected license is gone from the old provider, so a redelivered message never reaches the S3 move or Cognito/email branch in ingest.py. A crash after the DB transaction can therefore leave old documents or the old Cognito user behind permanently. Persist a separate teardown state so those side effects can resume independently.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/compact-connect/lambdas/python/provider-data-v1/handlers/ingest.py`
around lines 373 - 478, The teardown work in _perform_ssn_correction_migration
is coupled too tightly to migrate_provider_for_ssn_correction, so once
migration_performed becomes false on a retry,
_move_provider_documents_to_new_keyspace and
_delete_old_cognito_user_and_send_reregistration_email never run. Add a separate
persisted teardown state (for example in the existing migration result or a new
tracking record) so the S3 move and Cognito/email steps can resume independently
after a crash. Update the retry path in _perform_ssn_correction_migration to
check that teardown state rather than relying only on migration_performed, and
keep _move_s3_object and _delete_old_cognito_user_and_send_reregistration_email
idempotent.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have broken the transactions out into three phases in the case of a large number of items that need to be migrated. Please review and confirm if this address the issue.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, something went wrong! Please try again later. 🐰 💔

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

JCC - Migrate practitioner records from one SSN to another using 'previousSSN' field

1 participant