Skip to content

feat(attributes): add data migration utilities#454

Draft
constantinius wants to merge 1 commit into
mainfrom
constantinius/feat/attributes/data-migration-utilities
Draft

feat(attributes): add data migration utilities#454
constantinius wants to merge 1 commit into
mainfrom
constantinius/feat/attributes/data-migration-utilities

Conversation

@constantinius

@constantinius constantinius commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Summary

  • add shared data migration APIs for deprecated attributes across JS, Python, and Rust
  • expose migration metadata in generated attribute metadata
  • use open-ended string migration identifiers instead of enumerating ids in schemas or generated types
  • document how SDKs and Relay should use the migration helpers and how contributors add future data migrations

Contributes to TET-2369.

Testing

  • yarn test

Add shared data migration APIs for deprecated attributes across the JavaScript, Python, and Rust packages. The utilities expose single-value and attribute-map migration helpers, generated metadata support, and documentation for adding future migrations without hard-coding migration ids in schemas or generated types.
@github-actions

Copy link
Copy Markdown

Semver Impact of This PR

🟡 Minor (new features)

📋 Changelog Preview

This is how your changes will appear in the changelog.
Entries from this PR are highlighted with a left border (blockquote style).


New Features ✨

  • (attributes) Add data migration utilities by constantinius in #454

🤖 This preview updates automatically when you update the PR.


for migration in ATTRIBUTE_MIGRATIONS:
if len(migration.sources) == 1 and migration.sources[0] == attribute_name:
return migration.migrate({attribute_name: value}) or value

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Bug: The migrate_attribute_value function incorrectly handles falsy return values (like 0, False, or "") from migrations, causing it to discard the migrated value and return the original one.
Severity: HIGH

Suggested Fix

Replace the or value logic with an explicit check for None. For example: migrated_value = migration.migrate({attribute_name: value}); return migrated_value if migrated_value is not None else value;. This ensures that only a None return is treated as a failed migration.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.

Location: python/src/sentry_conventions/migrations.py#L73

Potential issue: The function `migrate_attribute_value` uses the `or` operator to
provide a fallback value. This is incorrect because valid migrated attribute values can
be "falsy" in Python (e.g., `0`, `False`, `""`, `[]`). When a migration correctly
returns a falsy value, the `or` operator will incorrectly discard it and return the
original, un-migrated value instead. This leads to silent data corruption, as the
migration appears to fail when it has actually succeeded. This behavior is inconsistent
with the JavaScript and Rust implementations, which correctly handle this case.

Did we get this right? 👍 / 👎 to inform future reviews.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 3 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit a768c83. Configure here.


for migration in ATTRIBUTE_MIGRATIONS:
if len(migration.sources) == 1 and migration.sources[0] == attribute_name:
return migration.migrate({attribute_name: value}) or value

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Python falsy migration results dropped

Medium Severity

In migrate_attribute_value, the migrated result is combined with or value, so legitimate values such as 0, False, or "" are treated as failure and the original deprecated value is returned instead of the replacement.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit a768c83. Configure here.

Comment thread rust/src/migrations.rs
if migration.remove_sources {
for source in migration.sources {
attributes.remove(*source);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Rust ignores deprecation normalize status

High Severity

Rust migrate_deprecated_attributes removes sources only when each migration’s static remove_sources flag is set, not from generated deprecation normalize/backfill metadata. Docs and CONTRIBUTING describe metadata-driven behavior like JavaScript and Python, so flipping _status in JSON will not update Rust (including Relay) unless code is edited separately.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit a768c83. Configure here.

if (migration.sources.some((source) => ATTRIBUTE_METADATA[source]?.deprecation?.status === 'normalize')) {
for (const source of migration.sources) {
delete attributes[source];
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Normalize drops sources without replacement

Medium Severity

When any migration source has deprecation status normalize, migrateDeprecatedAttributes deletes all configured source keys even if the migration did not produce a replacement value and the replacement key is still absent from the map.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit a768c83. Configure here.

@linear-code

linear-code Bot commented Jun 26, 2026

Copy link
Copy Markdown

TET-2369

@loewenheim

Copy link
Copy Markdown
Contributor

Hi, we're going to discuss in the ingest team how this API would fit into Relay. In the meantime, a question: is the migration machinery intended to eventually replace the existing backfill/normalize logic?

@constantinius

Copy link
Copy Markdown
Contributor Author

Thank you

is the migration machinery intended to eventually replace the existing backfill/normalize logic?

No, I would say it complements it. The migrations are only used when we change the data layout. But we should discuss how it works together: e.g backfill + migration keeps the old value on the old field, and sets the new value on the new field.

@cleptric cleptric marked this pull request as draft June 29, 2026 12:20

@loewenheim loewenheim 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.

This needs some further offline discussion.

@alexander-alderman-webb

Copy link
Copy Markdown
Contributor

My knee-jerk reaction to this: why don't we design schemas that are forwards compatible instead?

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.

3 participants