Просмотр исходного кода

ref(hybrid-cloud): Alter actor circular ref kludge (#54474)

An update to the work started in
https://github.com/getsentry/sentry/pull/46925. Because we no longer
reference actors from the `User` model, this is safe to remove.
Alex Zaslavsky 1 год назад
Родитель
Сommit
0f20e14105

+ 53 - 0
bin/generate-model-dependency-fixtures

@@ -0,0 +1,53 @@
+#!/usr/bin/env python
+from __future__ import annotations
+
+from sentry.runner import configure
+
+configure()
+
+import click
+
+from sentry.backup.dependencies import DependenciesJSONEncoder, dependencies, sorted_dependencies
+from sentry.testutils.factories import get_fixture_path  # noqa
+
+encoder = DependenciesJSONEncoder(
+    sort_keys=True,
+    ensure_ascii=True,
+    check_circular=True,
+    allow_nan=True,
+    indent=2,
+    encoding="utf-8",
+)
+
+
+@click.command()
+def main():
+    """Used for generating fixtures for the model dependency map."""
+
+    click.echo("\nThis script can take up to 30 seconds, please be patient...\n")
+
+    # Do all of the calculation before any file writes, so that we don't end up with partial
+    # overwrites.
+    detailed = dependencies()
+    flat = {k: v.flatten() for k, v in detailed.items()}
+    sorted = sorted_dependencies()
+
+    det_path = get_fixture_path("backup", "model_dependencies", "detailed.json")
+    with open(det_path, "w+") as fixture:
+        fixture.write(encoder.encode(detailed))
+
+    flat_path = get_fixture_path("backup", "model_dependencies", "flat.json")
+    with open(flat_path, "w+") as fixture:
+        fixture.write(encoder.encode(flat))
+
+    det_path = get_fixture_path("backup", "model_dependencies", "sorted.json")
+    with open(det_path, "w+") as fixture:
+        fixture.write(encoder.encode(sorted))
+
+    click.echo(
+        f"\nSuccess! The dependency mapping fixtures at {[det_path, flat_path]} were updated.\n"
+    )
+
+
+if __name__ == "__main__":
+    main()

+ 2579 - 0
fixtures/backup/model_dependencies/detailed.json

@@ -0,0 +1,2579 @@
+{
+  "nodestore.Node": {
+    "model": "nodestore.Node",
+    "relations": {},
+    "silos": [
+      "REGION"
+    ]
+  },
+  "replays.ReplayRecordingSegment": {
+    "model": "replays.ReplayRecordingSegment",
+    "relations": {},
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.Activity": {
+    "model": "sentry.Activity",
+    "relations": {
+      "group": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Group"
+      },
+      "project": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Project"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.Actor": {
+    "model": "sentry.Actor",
+    "relations": {},
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.AlertRule": {
+    "model": "sentry.AlertRule",
+    "relations": {
+      "excluded_projects": {
+        "kind": "ManyToManyField",
+        "model": "sentry.Project"
+      },
+      "organization": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Organization"
+      },
+      "owner": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Actor"
+      },
+      "snuba_query": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.SnubaQuery"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.AlertRuleActivity": {
+    "model": "sentry.AlertRuleActivity",
+    "relations": {
+      "alert_rule": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.AlertRule"
+      },
+      "previous_alert_rule": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.AlertRule"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.AlertRuleExcludedProjects": {
+    "model": "sentry.AlertRuleExcludedProjects",
+    "relations": {
+      "alert_rule": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.AlertRule"
+      },
+      "project": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Project"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.AlertRuleTrigger": {
+    "model": "sentry.AlertRuleTrigger",
+    "relations": {
+      "alert_rule": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.AlertRule"
+      },
+      "triggered_incidents": {
+        "kind": "ManyToManyField",
+        "model": "sentry.Incident"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.AlertRuleTriggerAction": {
+    "model": "sentry.AlertRuleTriggerAction",
+    "relations": {
+      "alert_rule_trigger": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.AlertRuleTrigger"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.AlertRuleTriggerExclusion": {
+    "model": "sentry.AlertRuleTriggerExclusion",
+    "relations": {
+      "alert_rule_trigger": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.AlertRuleTrigger"
+      },
+      "query_subscription": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.QuerySubscription"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.ApiApplication": {
+    "model": "sentry.ApiApplication",
+    "relations": {
+      "owner": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.User"
+      }
+    },
+    "silos": [
+      "CONTROL"
+    ]
+  },
+  "sentry.ApiAuthorization": {
+    "model": "sentry.ApiAuthorization",
+    "relations": {
+      "application": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.ApiApplication"
+      },
+      "user": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.User"
+      }
+    },
+    "silos": [
+      "CONTROL"
+    ]
+  },
+  "sentry.ApiGrant": {
+    "model": "sentry.ApiGrant",
+    "relations": {
+      "application": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.ApiApplication"
+      },
+      "user": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.User"
+      }
+    },
+    "silos": [
+      "CONTROL"
+    ]
+  },
+  "sentry.ApiKey": {
+    "model": "sentry.ApiKey",
+    "relations": {},
+    "silos": [
+      "CONTROL"
+    ]
+  },
+  "sentry.ApiToken": {
+    "model": "sentry.ApiToken",
+    "relations": {
+      "application": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.ApiApplication"
+      },
+      "user": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.User"
+      }
+    },
+    "silos": [
+      "CONTROL"
+    ]
+  },
+  "sentry.AppConnectBuild": {
+    "model": "sentry.AppConnectBuild",
+    "relations": {
+      "project": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Project"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.ArtifactBundle": {
+    "model": "sentry.ArtifactBundle",
+    "relations": {
+      "file": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.File"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.ArtifactBundleFlatFileIndex": {
+    "model": "sentry.ArtifactBundleFlatFileIndex",
+    "relations": {
+      "flat_file_index": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.File"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.ArtifactBundleIndex": {
+    "model": "sentry.ArtifactBundleIndex",
+    "relations": {
+      "artifact_bundle": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.ArtifactBundle"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.AssistantActivity": {
+    "model": "sentry.AssistantActivity",
+    "relations": {
+      "user": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.User"
+      }
+    },
+    "silos": [
+      "CONTROL"
+    ]
+  },
+  "sentry.AuditLogEntry": {
+    "model": "sentry.AuditLogEntry",
+    "relations": {
+      "actor": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.User"
+      },
+      "actor_key": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.ApiKey"
+      },
+      "target_user": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.User"
+      }
+    },
+    "silos": [
+      "CONTROL"
+    ]
+  },
+  "sentry.AuthIdentity": {
+    "model": "sentry.AuthIdentity",
+    "relations": {
+      "auth_provider": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.AuthProvider"
+      },
+      "user": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.User"
+      }
+    },
+    "silos": [
+      "CONTROL"
+    ]
+  },
+  "sentry.AuthProvider": {
+    "model": "sentry.AuthProvider",
+    "relations": {},
+    "silos": [
+      "CONTROL"
+    ]
+  },
+  "sentry.AuthProviderDefaultTeams": {
+    "model": "sentry.AuthProviderDefaultTeams",
+    "relations": {},
+    "silos": [
+      "CONTROL"
+    ]
+  },
+  "sentry.Authenticator": {
+    "model": "sentry.Authenticator",
+    "relations": {
+      "user": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.User"
+      }
+    },
+    "silos": [
+      "CONTROL"
+    ]
+  },
+  "sentry.Broadcast": {
+    "model": "sentry.Broadcast",
+    "relations": {},
+    "silos": [
+      "CONTROL"
+    ]
+  },
+  "sentry.BroadcastSeen": {
+    "model": "sentry.BroadcastSeen",
+    "relations": {
+      "broadcast": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Broadcast"
+      },
+      "user": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.User"
+      }
+    },
+    "silos": [
+      "CONTROL"
+    ]
+  },
+  "sentry.Commit": {
+    "model": "sentry.Commit",
+    "relations": {
+      "author": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.CommitAuthor"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.CommitAuthor": {
+    "model": "sentry.CommitAuthor",
+    "relations": {},
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.CommitFileChange": {
+    "model": "sentry.CommitFileChange",
+    "relations": {
+      "commit": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Commit"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.ControlFile": {
+    "model": "sentry.ControlFile",
+    "relations": {
+      "blobs": {
+        "kind": "ManyToManyField",
+        "model": "sentry.ControlFileBlob"
+      }
+    },
+    "silos": [
+      "CONTROL"
+    ]
+  },
+  "sentry.ControlFileBlob": {
+    "model": "sentry.ControlFileBlob",
+    "relations": {},
+    "silos": [
+      "CONTROL"
+    ]
+  },
+  "sentry.ControlFileBlobIndex": {
+    "model": "sentry.ControlFileBlobIndex",
+    "relations": {
+      "blob": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.ControlFileBlob"
+      },
+      "file": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.ControlFile"
+      }
+    },
+    "silos": [
+      "CONTROL"
+    ]
+  },
+  "sentry.ControlFileBlobOwner": {
+    "model": "sentry.ControlFileBlobOwner",
+    "relations": {
+      "blob": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.ControlFileBlob"
+      }
+    },
+    "silos": [
+      "CONTROL"
+    ]
+  },
+  "sentry.ControlOption": {
+    "model": "sentry.ControlOption",
+    "relations": {},
+    "silos": [
+      "CONTROL"
+    ]
+  },
+  "sentry.ControlOutbox": {
+    "model": "sentry.ControlOutbox",
+    "relations": {},
+    "silos": [
+      "CONTROL"
+    ]
+  },
+  "sentry.ControlTombstone": {
+    "model": "sentry.ControlTombstone",
+    "relations": {},
+    "silos": [
+      "CONTROL"
+    ]
+  },
+  "sentry.Counter": {
+    "model": "sentry.Counter",
+    "relations": {
+      "project": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Project"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.Dashboard": {
+    "model": "sentry.Dashboard",
+    "relations": {
+      "organization": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Organization"
+      },
+      "projects": {
+        "kind": "ManyToManyField",
+        "model": "sentry.Project"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.DashboardProject": {
+    "model": "sentry.DashboardProject",
+    "relations": {
+      "dashboard": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Dashboard"
+      },
+      "project": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Project"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.DashboardTombstone": {
+    "model": "sentry.DashboardTombstone",
+    "relations": {
+      "organization": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Organization"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.DashboardWidget": {
+    "model": "sentry.DashboardWidget",
+    "relations": {
+      "dashboard": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Dashboard"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.DashboardWidgetQuery": {
+    "model": "sentry.DashboardWidgetQuery",
+    "relations": {
+      "widget": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.DashboardWidget"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.DebugIdArtifactBundle": {
+    "model": "sentry.DebugIdArtifactBundle",
+    "relations": {
+      "artifact_bundle": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.ArtifactBundle"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.DeletedOrganization": {
+    "model": "sentry.DeletedOrganization",
+    "relations": {},
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.DeletedProject": {
+    "model": "sentry.DeletedProject",
+    "relations": {},
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.DeletedTeam": {
+    "model": "sentry.DeletedTeam",
+    "relations": {},
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.Deploy": {
+    "model": "sentry.Deploy",
+    "relations": {
+      "release": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Release"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.DiscoverSavedQuery": {
+    "model": "sentry.DiscoverSavedQuery",
+    "relations": {
+      "organization": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Organization"
+      },
+      "projects": {
+        "kind": "ManyToManyField",
+        "model": "sentry.Project"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.DiscoverSavedQueryProject": {
+    "model": "sentry.DiscoverSavedQueryProject",
+    "relations": {
+      "discover_saved_query": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.DiscoverSavedQuery"
+      },
+      "project": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Project"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.Distribution": {
+    "model": "sentry.Distribution",
+    "relations": {
+      "release": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Release"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.DocIntegration": {
+    "model": "sentry.DocIntegration",
+    "relations": {},
+    "silos": [
+      "CONTROL"
+    ]
+  },
+  "sentry.DocIntegrationAvatar": {
+    "model": "sentry.DocIntegrationAvatar",
+    "relations": {
+      "doc_integration": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.DocIntegration"
+      }
+    },
+    "silos": [
+      "CONTROL"
+    ]
+  },
+  "sentry.Email": {
+    "model": "sentry.Email",
+    "relations": {},
+    "silos": [
+      "CONTROL"
+    ]
+  },
+  "sentry.Environment": {
+    "model": "sentry.Environment",
+    "relations": {
+      "projects": {
+        "kind": "ManyToManyField",
+        "model": "sentry.Project"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.EnvironmentProject": {
+    "model": "sentry.EnvironmentProject",
+    "relations": {
+      "environment": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Environment"
+      },
+      "project": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Project"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.EventAttachment": {
+    "model": "sentry.EventAttachment",
+    "relations": {},
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.EventProcessingIssue": {
+    "model": "sentry.EventProcessingIssue",
+    "relations": {
+      "processing_issue": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.ProcessingIssue"
+      },
+      "raw_event": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.RawEvent"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.EventUser": {
+    "model": "sentry.EventUser",
+    "relations": {},
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.ExportedData": {
+    "model": "sentry.ExportedData",
+    "relations": {
+      "organization": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Organization"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.ExportedDataBlob": {
+    "model": "sentry.ExportedDataBlob",
+    "relations": {
+      "data_export": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.ExportedData"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.ExternalActor": {
+    "model": "sentry.ExternalActor",
+    "relations": {
+      "actor": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Actor"
+      },
+      "organization": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Organization"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.ExternalIssue": {
+    "model": "sentry.ExternalIssue",
+    "relations": {
+      "organization": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Organization"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.FeatureAdoption": {
+    "model": "sentry.FeatureAdoption",
+    "relations": {
+      "organization": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Organization"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.File": {
+    "model": "sentry.File",
+    "relations": {
+      "blob": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.FileBlob"
+      },
+      "blobs": {
+        "kind": "ManyToManyField",
+        "model": "sentry.FileBlob"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.FileBlob": {
+    "model": "sentry.FileBlob",
+    "relations": {},
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.FileBlobIndex": {
+    "model": "sentry.FileBlobIndex",
+    "relations": {
+      "blob": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.FileBlob"
+      },
+      "file": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.File"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.FileBlobOwner": {
+    "model": "sentry.FileBlobOwner",
+    "relations": {
+      "blob": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.FileBlob"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.FlatFileIndexState": {
+    "model": "sentry.FlatFileIndexState",
+    "relations": {
+      "artifact_bundle": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.ArtifactBundle"
+      },
+      "flat_file_index": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.ArtifactBundleFlatFileIndex"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.Group": {
+    "model": "sentry.Group",
+    "relations": {
+      "first_release": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Release"
+      },
+      "project": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Project"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.GroupAssignee": {
+    "model": "sentry.GroupAssignee",
+    "relations": {
+      "group": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Group"
+      },
+      "project": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Project"
+      },
+      "team": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Team"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.GroupBookmark": {
+    "model": "sentry.GroupBookmark",
+    "relations": {
+      "group": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Group"
+      },
+      "project": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Project"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.GroupCommitResolution": {
+    "model": "sentry.GroupCommitResolution",
+    "relations": {},
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.GroupEmailThread": {
+    "model": "sentry.GroupEmailThread",
+    "relations": {
+      "group": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Group"
+      },
+      "project": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Project"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.GroupEnvironment": {
+    "model": "sentry.GroupEnvironment",
+    "relations": {
+      "environment": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Environment"
+      },
+      "first_release": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Release"
+      },
+      "group": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Group"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.GroupHash": {
+    "model": "sentry.GroupHash",
+    "relations": {
+      "group": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Group"
+      },
+      "project": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Project"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.GroupHistory": {
+    "model": "sentry.GroupHistory",
+    "relations": {
+      "actor": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Actor"
+      },
+      "group": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Group"
+      },
+      "organization": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Organization"
+      },
+      "project": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Project"
+      },
+      "release": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Release"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.GroupInbox": {
+    "model": "sentry.GroupInbox",
+    "relations": {
+      "group": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Group"
+      },
+      "organization": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Organization"
+      },
+      "project": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Project"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.GroupLink": {
+    "model": "sentry.GroupLink",
+    "relations": {
+      "group": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Group"
+      },
+      "project": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Project"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.GroupMeta": {
+    "model": "sentry.GroupMeta",
+    "relations": {
+      "group": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Group"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.GroupOwner": {
+    "model": "sentry.GroupOwner",
+    "relations": {
+      "group": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Group"
+      },
+      "organization": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Organization"
+      },
+      "project": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Project"
+      },
+      "team": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Team"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.GroupRedirect": {
+    "model": "sentry.GroupRedirect",
+    "relations": {},
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.GroupRelease": {
+    "model": "sentry.GroupRelease",
+    "relations": {},
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.GroupResolution": {
+    "model": "sentry.GroupResolution",
+    "relations": {
+      "group": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Group"
+      },
+      "release": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Release"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.GroupRuleStatus": {
+    "model": "sentry.GroupRuleStatus",
+    "relations": {
+      "group": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Group"
+      },
+      "project": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Project"
+      },
+      "rule": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Rule"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.GroupSeen": {
+    "model": "sentry.GroupSeen",
+    "relations": {
+      "group": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Group"
+      },
+      "project": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Project"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.GroupShare": {
+    "model": "sentry.GroupShare",
+    "relations": {
+      "group": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Group"
+      },
+      "project": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Project"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.GroupSnooze": {
+    "model": "sentry.GroupSnooze",
+    "relations": {
+      "group": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Group"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.GroupSubscription": {
+    "model": "sentry.GroupSubscription",
+    "relations": {
+      "group": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Group"
+      },
+      "project": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Project"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.GroupTombstone": {
+    "model": "sentry.GroupTombstone",
+    "relations": {
+      "project": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Project"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.Identity": {
+    "model": "sentry.Identity",
+    "relations": {
+      "idp": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.IdentityProvider"
+      },
+      "user": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.User"
+      }
+    },
+    "silos": [
+      "CONTROL"
+    ]
+  },
+  "sentry.IdentityProvider": {
+    "model": "sentry.IdentityProvider",
+    "relations": {},
+    "silos": [
+      "CONTROL"
+    ]
+  },
+  "sentry.Incident": {
+    "model": "sentry.Incident",
+    "relations": {
+      "alert_rule": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.AlertRule"
+      },
+      "organization": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Organization"
+      },
+      "projects": {
+        "kind": "ManyToManyField",
+        "model": "sentry.Project"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.IncidentActivity": {
+    "model": "sentry.IncidentActivity",
+    "relations": {
+      "incident": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Incident"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.IncidentProject": {
+    "model": "sentry.IncidentProject",
+    "relations": {
+      "incident": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Incident"
+      },
+      "project": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Project"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.IncidentSeen": {
+    "model": "sentry.IncidentSeen",
+    "relations": {
+      "incident": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Incident"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.IncidentSnapshot": {
+    "model": "sentry.IncidentSnapshot",
+    "relations": {
+      "event_stats_snapshot": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.TimeSeriesSnapshot"
+      },
+      "incident": {
+        "kind": "OneToOneCascadeDeletes",
+        "model": "sentry.Incident"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.IncidentSubscription": {
+    "model": "sentry.IncidentSubscription",
+    "relations": {
+      "incident": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Incident"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.IncidentTrigger": {
+    "model": "sentry.IncidentTrigger",
+    "relations": {
+      "alert_rule_trigger": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.AlertRuleTrigger"
+      },
+      "incident": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Incident"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.Integration": {
+    "model": "sentry.Integration",
+    "relations": {},
+    "silos": [
+      "CONTROL"
+    ]
+  },
+  "sentry.IntegrationExternalProject": {
+    "model": "sentry.IntegrationExternalProject",
+    "relations": {},
+    "silos": [
+      "CONTROL"
+    ]
+  },
+  "sentry.IntegrationFeature": {
+    "model": "sentry.IntegrationFeature",
+    "relations": {},
+    "silos": [
+      "CONTROL"
+    ]
+  },
+  "sentry.LatestAppConnectBuildsCheck": {
+    "model": "sentry.LatestAppConnectBuildsCheck",
+    "relations": {
+      "project": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Project"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.LatestRepoReleaseEnvironment": {
+    "model": "sentry.LatestRepoReleaseEnvironment",
+    "relations": {},
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.LostPasswordHash": {
+    "model": "sentry.LostPasswordHash",
+    "relations": {
+      "user": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.User"
+      }
+    },
+    "silos": [
+      "CONTROL"
+    ]
+  },
+  "sentry.MetricsKeyIndexer": {
+    "model": "sentry.MetricsKeyIndexer",
+    "relations": {},
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.Monitor": {
+    "model": "sentry.Monitor",
+    "relations": {},
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.MonitorCheckIn": {
+    "model": "sentry.MonitorCheckIn",
+    "relations": {
+      "location": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.MonitorLocation"
+      },
+      "monitor": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Monitor"
+      },
+      "monitor_environment": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.MonitorEnvironment"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.MonitorEnvironment": {
+    "model": "sentry.MonitorEnvironment",
+    "relations": {
+      "environment": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Environment"
+      },
+      "monitor": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Monitor"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.MonitorLocation": {
+    "model": "sentry.MonitorLocation",
+    "relations": {},
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.NotificationAction": {
+    "model": "sentry.NotificationAction",
+    "relations": {
+      "organization": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Organization"
+      },
+      "projects": {
+        "kind": "ManyToManyField",
+        "model": "sentry.Project"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.NotificationActionProject": {
+    "model": "sentry.NotificationActionProject",
+    "relations": {
+      "action": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.NotificationAction"
+      },
+      "project": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Project"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.NotificationSetting": {
+    "model": "sentry.NotificationSetting",
+    "relations": {
+      "user": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.User"
+      }
+    },
+    "silos": [
+      "CONTROL"
+    ]
+  },
+  "sentry.Option": {
+    "model": "sentry.Option",
+    "relations": {},
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.OrgAuthToken": {
+    "model": "sentry.OrgAuthToken",
+    "relations": {
+      "created_by": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.User"
+      }
+    },
+    "silos": [
+      "CONTROL"
+    ]
+  },
+  "sentry.Organization": {
+    "model": "sentry.Organization",
+    "relations": {},
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.OrganizationAccessRequest": {
+    "model": "sentry.OrganizationAccessRequest",
+    "relations": {
+      "member": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.OrganizationMember"
+      },
+      "team": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Team"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.OrganizationAvatar": {
+    "model": "sentry.OrganizationAvatar",
+    "relations": {
+      "organization": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Organization"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.OrganizationIntegration": {
+    "model": "sentry.OrganizationIntegration",
+    "relations": {
+      "integration": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Integration"
+      }
+    },
+    "silos": [
+      "CONTROL"
+    ]
+  },
+  "sentry.OrganizationMapping": {
+    "model": "sentry.OrganizationMapping",
+    "relations": {},
+    "silos": [
+      "CONTROL"
+    ]
+  },
+  "sentry.OrganizationMember": {
+    "model": "sentry.OrganizationMember",
+    "relations": {
+      "organization": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Organization"
+      },
+      "teams": {
+        "kind": "ManyToManyField",
+        "model": "sentry.Team"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.OrganizationMemberMapping": {
+    "model": "sentry.OrganizationMemberMapping",
+    "relations": {
+      "inviter": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.User"
+      },
+      "user": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.User"
+      }
+    },
+    "silos": [
+      "CONTROL"
+    ]
+  },
+  "sentry.OrganizationMemberTeam": {
+    "model": "sentry.OrganizationMemberTeam",
+    "relations": {
+      "organizationmember": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.OrganizationMember"
+      },
+      "team": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Team"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.OrganizationOnboardingTask": {
+    "model": "sentry.OrganizationOnboardingTask",
+    "relations": {
+      "organization": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Organization"
+      },
+      "project": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Project"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.OrganizationOption": {
+    "model": "sentry.OrganizationOption",
+    "relations": {
+      "organization": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Organization"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.PagerDutyService": {
+    "model": "sentry.PagerDutyService",
+    "relations": {
+      "organization_integration": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.OrganizationIntegration"
+      }
+    },
+    "silos": [
+      "CONTROL"
+    ]
+  },
+  "sentry.PendingIncidentSnapshot": {
+    "model": "sentry.PendingIncidentSnapshot",
+    "relations": {
+      "incident": {
+        "kind": "OneToOneCascadeDeletes",
+        "model": "sentry.Incident"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.PerfStringIndexer": {
+    "model": "sentry.PerfStringIndexer",
+    "relations": {},
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.PlatformExternalIssue": {
+    "model": "sentry.PlatformExternalIssue",
+    "relations": {
+      "group": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Group"
+      },
+      "project": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Project"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.ProcessingIssue": {
+    "model": "sentry.ProcessingIssue",
+    "relations": {
+      "project": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Project"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.ProguardArtifactRelease": {
+    "model": "sentry.ProguardArtifactRelease",
+    "relations": {
+      "project_debug_file": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.ProjectDebugFile"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.Project": {
+    "model": "sentry.Project",
+    "relations": {
+      "organization": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Organization"
+      },
+      "teams": {
+        "kind": "ManyToManyField",
+        "model": "sentry.Team"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.ProjectArtifactBundle": {
+    "model": "sentry.ProjectArtifactBundle",
+    "relations": {
+      "artifact_bundle": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.ArtifactBundle"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.ProjectAvatar": {
+    "model": "sentry.ProjectAvatar",
+    "relations": {
+      "project": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Project"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.ProjectBookmark": {
+    "model": "sentry.ProjectBookmark",
+    "relations": {
+      "project": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Project"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.ProjectCodeOwners": {
+    "model": "sentry.ProjectCodeOwners",
+    "relations": {
+      "project": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Project"
+      },
+      "repository_project_path_config": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.RepositoryProjectPathConfig"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.ProjectDebugFile": {
+    "model": "sentry.ProjectDebugFile",
+    "relations": {
+      "file": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.File"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.ProjectIntegration": {
+    "model": "sentry.ProjectIntegration",
+    "relations": {
+      "project": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Project"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.ProjectKey": {
+    "model": "sentry.ProjectKey",
+    "relations": {
+      "project": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Project"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.ProjectOption": {
+    "model": "sentry.ProjectOption",
+    "relations": {
+      "project": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Project"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.ProjectOwnership": {
+    "model": "sentry.ProjectOwnership",
+    "relations": {
+      "project": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Project"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.ProjectPlatform": {
+    "model": "sentry.ProjectPlatform",
+    "relations": {},
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.ProjectRedirect": {
+    "model": "sentry.ProjectRedirect",
+    "relations": {
+      "organization": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Organization"
+      },
+      "project": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Project"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.ProjectTeam": {
+    "model": "sentry.ProjectTeam",
+    "relations": {
+      "project": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Project"
+      },
+      "team": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Team"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.ProjectTransactionThreshold": {
+    "model": "sentry.ProjectTransactionThreshold",
+    "relations": {
+      "organization": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Organization"
+      },
+      "project": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Project"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.ProjectTransactionThresholdOverride": {
+    "model": "sentry.ProjectTransactionThresholdOverride",
+    "relations": {
+      "organization": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Organization"
+      },
+      "project": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Project"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.PromptsActivity": {
+    "model": "sentry.PromptsActivity",
+    "relations": {},
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.PullRequest": {
+    "model": "sentry.PullRequest",
+    "relations": {
+      "author": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.CommitAuthor"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.PullRequestComment": {
+    "model": "sentry.PullRequestComment",
+    "relations": {
+      "pull_request": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.PullRequest"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.PullRequestCommit": {
+    "model": "sentry.PullRequestCommit",
+    "relations": {
+      "commit": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Commit"
+      },
+      "pull_request": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.PullRequest"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.QuerySubscription": {
+    "model": "sentry.QuerySubscription",
+    "relations": {
+      "project": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Project"
+      },
+      "snuba_query": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.SnubaQuery"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.RawEvent": {
+    "model": "sentry.RawEvent",
+    "relations": {
+      "project": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Project"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.RecentSearch": {
+    "model": "sentry.RecentSearch",
+    "relations": {
+      "organization": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Organization"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.RegionOutbox": {
+    "model": "sentry.RegionOutbox",
+    "relations": {},
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.RegionScheduledDeletion": {
+    "model": "sentry.RegionScheduledDeletion",
+    "relations": {},
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.RegionTombstone": {
+    "model": "sentry.RegionTombstone",
+    "relations": {},
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.Relay": {
+    "model": "sentry.Relay",
+    "relations": {},
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.RelayUsage": {
+    "model": "sentry.RelayUsage",
+    "relations": {},
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.Release": {
+    "model": "sentry.Release",
+    "relations": {
+      "organization": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Organization"
+      },
+      "projects": {
+        "kind": "ManyToManyField",
+        "model": "sentry.Project"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.ReleaseActivity": {
+    "model": "sentry.ReleaseActivity",
+    "relations": {
+      "release": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Release"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.ReleaseArtifactBundle": {
+    "model": "sentry.ReleaseArtifactBundle",
+    "relations": {
+      "artifact_bundle": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.ArtifactBundle"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.ReleaseCommit": {
+    "model": "sentry.ReleaseCommit",
+    "relations": {
+      "commit": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Commit"
+      },
+      "release": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Release"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.ReleaseEnvironment": {
+    "model": "sentry.ReleaseEnvironment",
+    "relations": {
+      "environment": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Environment"
+      },
+      "organization": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Organization"
+      },
+      "release": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Release"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.ReleaseFile": {
+    "model": "sentry.ReleaseFile",
+    "relations": {
+      "file": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.File"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.ReleaseHeadCommit": {
+    "model": "sentry.ReleaseHeadCommit",
+    "relations": {
+      "commit": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Commit"
+      },
+      "release": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Release"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.ReleaseProject": {
+    "model": "sentry.ReleaseProject",
+    "relations": {
+      "project": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Project"
+      },
+      "release": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Release"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.ReleaseProjectEnvironment": {
+    "model": "sentry.ReleaseProjectEnvironment",
+    "relations": {
+      "environment": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Environment"
+      },
+      "project": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Project"
+      },
+      "release": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Release"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.Repository": {
+    "model": "sentry.Repository",
+    "relations": {},
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.RepositoryProjectPathConfig": {
+    "model": "sentry.RepositoryProjectPathConfig",
+    "relations": {
+      "project": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Project"
+      },
+      "repository": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Repository"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.ReprocessingReport": {
+    "model": "sentry.ReprocessingReport",
+    "relations": {
+      "project": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Project"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.Rule": {
+    "model": "sentry.Rule",
+    "relations": {
+      "owner": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Actor"
+      },
+      "project": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Project"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.RuleActivity": {
+    "model": "sentry.RuleActivity",
+    "relations": {
+      "rule": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Rule"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.RuleFireHistory": {
+    "model": "sentry.RuleFireHistory",
+    "relations": {
+      "group": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Group"
+      },
+      "project": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Project"
+      },
+      "rule": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Rule"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.RuleSnooze": {
+    "model": "sentry.RuleSnooze",
+    "relations": {
+      "alert_rule": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.AlertRule"
+      },
+      "rule": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Rule"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.SavedSearch": {
+    "model": "sentry.SavedSearch",
+    "relations": {
+      "organization": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Organization"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.ScheduledDeletion": {
+    "model": "sentry.ScheduledDeletion",
+    "relations": {},
+    "silos": [
+      "CONTROL"
+    ]
+  },
+  "sentry.SentryApp": {
+    "model": "sentry.SentryApp",
+    "relations": {
+      "application": {
+        "kind": "DefaultOneToOneField",
+        "model": "sentry.ApiApplication"
+      },
+      "creator_user": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.User"
+      },
+      "proxy_user": {
+        "kind": "DefaultOneToOneField",
+        "model": "sentry.User"
+      }
+    },
+    "silos": [
+      "CONTROL"
+    ]
+  },
+  "sentry.SentryAppAvatar": {
+    "model": "sentry.SentryAppAvatar",
+    "relations": {
+      "sentry_app": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.SentryApp"
+      }
+    },
+    "silos": [
+      "CONTROL"
+    ]
+  },
+  "sentry.SentryAppComponent": {
+    "model": "sentry.SentryAppComponent",
+    "relations": {
+      "sentry_app": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.SentryApp"
+      }
+    },
+    "silos": [
+      "CONTROL"
+    ]
+  },
+  "sentry.SentryAppInstallation": {
+    "model": "sentry.SentryAppInstallation",
+    "relations": {
+      "api_grant": {
+        "kind": "DefaultOneToOneField",
+        "model": "sentry.ApiGrant"
+      },
+      "api_token": {
+        "kind": "DefaultOneToOneField",
+        "model": "sentry.ApiToken"
+      },
+      "sentry_app": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.SentryApp"
+      }
+    },
+    "silos": [
+      "CONTROL"
+    ]
+  },
+  "sentry.SentryAppInstallationForProvider": {
+    "model": "sentry.SentryAppInstallationForProvider",
+    "relations": {
+      "sentry_app_installation": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.SentryAppInstallation"
+      }
+    },
+    "silos": [
+      "CONTROL"
+    ]
+  },
+  "sentry.SentryAppInstallationToken": {
+    "model": "sentry.SentryAppInstallationToken",
+    "relations": {
+      "api_token": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.ApiToken"
+      },
+      "sentry_app_installation": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.SentryAppInstallation"
+      }
+    },
+    "silos": [
+      "CONTROL"
+    ]
+  },
+  "sentry.SentryFunction": {
+    "model": "sentry.SentryFunction",
+    "relations": {
+      "organization": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Organization"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.ServiceHook": {
+    "model": "sentry.ServiceHook",
+    "relations": {},
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.ServiceHookProject": {
+    "model": "sentry.ServiceHookProject",
+    "relations": {
+      "service_hook": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.ServiceHook"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.SnubaQuery": {
+    "model": "sentry.SnubaQuery",
+    "relations": {
+      "environment": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Environment"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.SnubaQueryEventType": {
+    "model": "sentry.SnubaQueryEventType",
+    "relations": {
+      "snuba_query": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.SnubaQuery"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.StringIndexer": {
+    "model": "sentry.StringIndexer",
+    "relations": {},
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.Team": {
+    "model": "sentry.Team",
+    "relations": {
+      "actor": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Actor"
+      },
+      "organization": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Organization"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.TeamAvatar": {
+    "model": "sentry.TeamAvatar",
+    "relations": {
+      "team": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Team"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.TeamKeyTransaction": {
+    "model": "sentry.TeamKeyTransaction",
+    "relations": {
+      "organization": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.Organization"
+      },
+      "project_team": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.ProjectTeam"
+      }
+    },
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.TimeSeriesSnapshot": {
+    "model": "sentry.TimeSeriesSnapshot",
+    "relations": {},
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.User": {
+    "model": "sentry.User",
+    "relations": {},
+    "silos": [
+      "CONTROL"
+    ]
+  },
+  "sentry.UserAvatar": {
+    "model": "sentry.UserAvatar",
+    "relations": {
+      "user": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.User"
+      }
+    },
+    "silos": [
+      "CONTROL"
+    ]
+  },
+  "sentry.UserEmail": {
+    "model": "sentry.UserEmail",
+    "relations": {
+      "user": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.User"
+      }
+    },
+    "silos": [
+      "CONTROL"
+    ]
+  },
+  "sentry.UserIP": {
+    "model": "sentry.UserIP",
+    "relations": {
+      "user": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.User"
+      }
+    },
+    "silos": [
+      "CONTROL"
+    ]
+  },
+  "sentry.UserOption": {
+    "model": "sentry.UserOption",
+    "relations": {
+      "user": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.User"
+      }
+    },
+    "silos": [
+      "CONTROL"
+    ]
+  },
+  "sentry.UserPermission": {
+    "model": "sentry.UserPermission",
+    "relations": {
+      "user": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.User"
+      }
+    },
+    "silos": [
+      "CONTROL"
+    ]
+  },
+  "sentry.UserReport": {
+    "model": "sentry.UserReport",
+    "relations": {},
+    "silos": [
+      "REGION"
+    ]
+  },
+  "sentry.UserRole": {
+    "model": "sentry.UserRole",
+    "relations": {
+      "users": {
+        "kind": "ManyToManyField",
+        "model": "sentry.User"
+      }
+    },
+    "silos": [
+      "CONTROL"
+    ]
+  },
+  "sentry.UserRoleUser": {
+    "model": "sentry.UserRoleUser",
+    "relations": {
+      "role": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.UserRole"
+      },
+      "user": {
+        "kind": "FlexibleForeignKey",
+        "model": "sentry.User"
+      }
+    },
+    "silos": [
+      "CONTROL"
+    ]
+  },
+  "sessions.Session": {
+    "model": "sessions.Session",
+    "relations": {},
+    "silos": [
+      "MONOLITH"
+    ]
+  },
+  "sites.Site": {
+    "model": "sites.Site",
+    "relations": {},
+    "silos": [
+      "MONOLITH"
+    ]
+  },
+  "social_auth.UserSocialAuth": {
+    "model": "social_auth.UserSocialAuth",
+    "relations": {
+      "user": {
+        "kind": "DefaultForeignKey",
+        "model": "sentry.User"
+      }
+    },
+    "silos": [
+      "CONTROL"
+    ]
+  }
+}

+ 603 - 0
fixtures/backup/model_dependencies/flat.json

@@ -0,0 +1,603 @@
+{
+  "nodestore.Node": [],
+  "replays.ReplayRecordingSegment": [],
+  "sentry.Activity": [
+    "sentry.Group",
+    "sentry.Project"
+  ],
+  "sentry.Actor": [],
+  "sentry.AlertRule": [
+    "sentry.Actor",
+    "sentry.Organization",
+    "sentry.Project",
+    "sentry.SnubaQuery"
+  ],
+  "sentry.AlertRuleActivity": [
+    "sentry.AlertRule"
+  ],
+  "sentry.AlertRuleExcludedProjects": [
+    "sentry.AlertRule",
+    "sentry.Project"
+  ],
+  "sentry.AlertRuleTrigger": [
+    "sentry.AlertRule",
+    "sentry.Incident"
+  ],
+  "sentry.AlertRuleTriggerAction": [
+    "sentry.AlertRuleTrigger"
+  ],
+  "sentry.AlertRuleTriggerExclusion": [
+    "sentry.AlertRuleTrigger",
+    "sentry.QuerySubscription"
+  ],
+  "sentry.ApiApplication": [
+    "sentry.User"
+  ],
+  "sentry.ApiAuthorization": [
+    "sentry.ApiApplication",
+    "sentry.User"
+  ],
+  "sentry.ApiGrant": [
+    "sentry.ApiApplication",
+    "sentry.User"
+  ],
+  "sentry.ApiKey": [],
+  "sentry.ApiToken": [
+    "sentry.ApiApplication",
+    "sentry.User"
+  ],
+  "sentry.AppConnectBuild": [
+    "sentry.Project"
+  ],
+  "sentry.ArtifactBundle": [
+    "sentry.File"
+  ],
+  "sentry.ArtifactBundleFlatFileIndex": [
+    "sentry.File"
+  ],
+  "sentry.ArtifactBundleIndex": [
+    "sentry.ArtifactBundle"
+  ],
+  "sentry.AssistantActivity": [
+    "sentry.User"
+  ],
+  "sentry.AuditLogEntry": [
+    "sentry.ApiKey",
+    "sentry.User"
+  ],
+  "sentry.AuthIdentity": [
+    "sentry.AuthProvider",
+    "sentry.User"
+  ],
+  "sentry.AuthProvider": [],
+  "sentry.AuthProviderDefaultTeams": [],
+  "sentry.Authenticator": [
+    "sentry.User"
+  ],
+  "sentry.Broadcast": [],
+  "sentry.BroadcastSeen": [
+    "sentry.Broadcast",
+    "sentry.User"
+  ],
+  "sentry.Commit": [
+    "sentry.CommitAuthor"
+  ],
+  "sentry.CommitAuthor": [],
+  "sentry.CommitFileChange": [
+    "sentry.Commit"
+  ],
+  "sentry.ControlFile": [
+    "sentry.ControlFileBlob"
+  ],
+  "sentry.ControlFileBlob": [],
+  "sentry.ControlFileBlobIndex": [
+    "sentry.ControlFile",
+    "sentry.ControlFileBlob"
+  ],
+  "sentry.ControlFileBlobOwner": [
+    "sentry.ControlFileBlob"
+  ],
+  "sentry.ControlOption": [],
+  "sentry.ControlOutbox": [],
+  "sentry.ControlTombstone": [],
+  "sentry.Counter": [
+    "sentry.Project"
+  ],
+  "sentry.Dashboard": [
+    "sentry.Organization",
+    "sentry.Project"
+  ],
+  "sentry.DashboardProject": [
+    "sentry.Dashboard",
+    "sentry.Project"
+  ],
+  "sentry.DashboardTombstone": [
+    "sentry.Organization"
+  ],
+  "sentry.DashboardWidget": [
+    "sentry.Dashboard"
+  ],
+  "sentry.DashboardWidgetQuery": [
+    "sentry.DashboardWidget"
+  ],
+  "sentry.DebugIdArtifactBundle": [
+    "sentry.ArtifactBundle"
+  ],
+  "sentry.DeletedOrganization": [],
+  "sentry.DeletedProject": [],
+  "sentry.DeletedTeam": [],
+  "sentry.Deploy": [
+    "sentry.Release"
+  ],
+  "sentry.DiscoverSavedQuery": [
+    "sentry.Organization",
+    "sentry.Project"
+  ],
+  "sentry.DiscoverSavedQueryProject": [
+    "sentry.DiscoverSavedQuery",
+    "sentry.Project"
+  ],
+  "sentry.Distribution": [
+    "sentry.Release"
+  ],
+  "sentry.DocIntegration": [],
+  "sentry.DocIntegrationAvatar": [
+    "sentry.DocIntegration"
+  ],
+  "sentry.Email": [],
+  "sentry.Environment": [
+    "sentry.Project"
+  ],
+  "sentry.EnvironmentProject": [
+    "sentry.Environment",
+    "sentry.Project"
+  ],
+  "sentry.EventAttachment": [],
+  "sentry.EventProcessingIssue": [
+    "sentry.ProcessingIssue",
+    "sentry.RawEvent"
+  ],
+  "sentry.EventUser": [],
+  "sentry.ExportedData": [
+    "sentry.Organization"
+  ],
+  "sentry.ExportedDataBlob": [
+    "sentry.ExportedData"
+  ],
+  "sentry.ExternalActor": [
+    "sentry.Actor",
+    "sentry.Organization"
+  ],
+  "sentry.ExternalIssue": [
+    "sentry.Organization"
+  ],
+  "sentry.FeatureAdoption": [
+    "sentry.Organization"
+  ],
+  "sentry.File": [
+    "sentry.FileBlob"
+  ],
+  "sentry.FileBlob": [],
+  "sentry.FileBlobIndex": [
+    "sentry.File",
+    "sentry.FileBlob"
+  ],
+  "sentry.FileBlobOwner": [
+    "sentry.FileBlob"
+  ],
+  "sentry.FlatFileIndexState": [
+    "sentry.ArtifactBundle",
+    "sentry.ArtifactBundleFlatFileIndex"
+  ],
+  "sentry.Group": [
+    "sentry.Project",
+    "sentry.Release"
+  ],
+  "sentry.GroupAssignee": [
+    "sentry.Group",
+    "sentry.Project",
+    "sentry.Team"
+  ],
+  "sentry.GroupBookmark": [
+    "sentry.Group",
+    "sentry.Project"
+  ],
+  "sentry.GroupCommitResolution": [],
+  "sentry.GroupEmailThread": [
+    "sentry.Group",
+    "sentry.Project"
+  ],
+  "sentry.GroupEnvironment": [
+    "sentry.Environment",
+    "sentry.Group",
+    "sentry.Release"
+  ],
+  "sentry.GroupHash": [
+    "sentry.Group",
+    "sentry.Project"
+  ],
+  "sentry.GroupHistory": [
+    "sentry.Actor",
+    "sentry.Group",
+    "sentry.Organization",
+    "sentry.Project",
+    "sentry.Release"
+  ],
+  "sentry.GroupInbox": [
+    "sentry.Group",
+    "sentry.Organization",
+    "sentry.Project"
+  ],
+  "sentry.GroupLink": [
+    "sentry.Group",
+    "sentry.Project"
+  ],
+  "sentry.GroupMeta": [
+    "sentry.Group"
+  ],
+  "sentry.GroupOwner": [
+    "sentry.Group",
+    "sentry.Organization",
+    "sentry.Project",
+    "sentry.Team"
+  ],
+  "sentry.GroupRedirect": [],
+  "sentry.GroupRelease": [],
+  "sentry.GroupResolution": [
+    "sentry.Group",
+    "sentry.Release"
+  ],
+  "sentry.GroupRuleStatus": [
+    "sentry.Group",
+    "sentry.Project",
+    "sentry.Rule"
+  ],
+  "sentry.GroupSeen": [
+    "sentry.Group",
+    "sentry.Project"
+  ],
+  "sentry.GroupShare": [
+    "sentry.Group",
+    "sentry.Project"
+  ],
+  "sentry.GroupSnooze": [
+    "sentry.Group"
+  ],
+  "sentry.GroupSubscription": [
+    "sentry.Group",
+    "sentry.Project"
+  ],
+  "sentry.GroupTombstone": [
+    "sentry.Project"
+  ],
+  "sentry.Identity": [
+    "sentry.IdentityProvider",
+    "sentry.User"
+  ],
+  "sentry.IdentityProvider": [],
+  "sentry.Incident": [
+    "sentry.AlertRule",
+    "sentry.Organization",
+    "sentry.Project"
+  ],
+  "sentry.IncidentActivity": [
+    "sentry.Incident"
+  ],
+  "sentry.IncidentProject": [
+    "sentry.Incident",
+    "sentry.Project"
+  ],
+  "sentry.IncidentSeen": [
+    "sentry.Incident"
+  ],
+  "sentry.IncidentSnapshot": [
+    "sentry.Incident",
+    "sentry.TimeSeriesSnapshot"
+  ],
+  "sentry.IncidentSubscription": [
+    "sentry.Incident"
+  ],
+  "sentry.IncidentTrigger": [
+    "sentry.AlertRuleTrigger",
+    "sentry.Incident"
+  ],
+  "sentry.Integration": [],
+  "sentry.IntegrationExternalProject": [],
+  "sentry.IntegrationFeature": [],
+  "sentry.LatestAppConnectBuildsCheck": [
+    "sentry.Project"
+  ],
+  "sentry.LatestRepoReleaseEnvironment": [],
+  "sentry.LostPasswordHash": [
+    "sentry.User"
+  ],
+  "sentry.MetricsKeyIndexer": [],
+  "sentry.Monitor": [],
+  "sentry.MonitorCheckIn": [
+    "sentry.Monitor",
+    "sentry.MonitorEnvironment",
+    "sentry.MonitorLocation"
+  ],
+  "sentry.MonitorEnvironment": [
+    "sentry.Environment",
+    "sentry.Monitor"
+  ],
+  "sentry.MonitorLocation": [],
+  "sentry.NotificationAction": [
+    "sentry.Organization",
+    "sentry.Project"
+  ],
+  "sentry.NotificationActionProject": [
+    "sentry.NotificationAction",
+    "sentry.Project"
+  ],
+  "sentry.NotificationSetting": [
+    "sentry.User"
+  ],
+  "sentry.Option": [],
+  "sentry.OrgAuthToken": [
+    "sentry.User"
+  ],
+  "sentry.Organization": [],
+  "sentry.OrganizationAccessRequest": [
+    "sentry.OrganizationMember",
+    "sentry.Team"
+  ],
+  "sentry.OrganizationAvatar": [
+    "sentry.Organization"
+  ],
+  "sentry.OrganizationIntegration": [
+    "sentry.Integration"
+  ],
+  "sentry.OrganizationMapping": [],
+  "sentry.OrganizationMember": [
+    "sentry.Organization",
+    "sentry.Team"
+  ],
+  "sentry.OrganizationMemberMapping": [
+    "sentry.User"
+  ],
+  "sentry.OrganizationMemberTeam": [
+    "sentry.OrganizationMember",
+    "sentry.Team"
+  ],
+  "sentry.OrganizationOnboardingTask": [
+    "sentry.Organization",
+    "sentry.Project"
+  ],
+  "sentry.OrganizationOption": [
+    "sentry.Organization"
+  ],
+  "sentry.PagerDutyService": [
+    "sentry.OrganizationIntegration"
+  ],
+  "sentry.PendingIncidentSnapshot": [
+    "sentry.Incident"
+  ],
+  "sentry.PerfStringIndexer": [],
+  "sentry.PlatformExternalIssue": [
+    "sentry.Group",
+    "sentry.Project"
+  ],
+  "sentry.ProcessingIssue": [
+    "sentry.Project"
+  ],
+  "sentry.ProguardArtifactRelease": [
+    "sentry.ProjectDebugFile"
+  ],
+  "sentry.Project": [
+    "sentry.Organization",
+    "sentry.Team"
+  ],
+  "sentry.ProjectArtifactBundle": [
+    "sentry.ArtifactBundle"
+  ],
+  "sentry.ProjectAvatar": [
+    "sentry.Project"
+  ],
+  "sentry.ProjectBookmark": [
+    "sentry.Project"
+  ],
+  "sentry.ProjectCodeOwners": [
+    "sentry.Project",
+    "sentry.RepositoryProjectPathConfig"
+  ],
+  "sentry.ProjectDebugFile": [
+    "sentry.File"
+  ],
+  "sentry.ProjectIntegration": [
+    "sentry.Project"
+  ],
+  "sentry.ProjectKey": [
+    "sentry.Project"
+  ],
+  "sentry.ProjectOption": [
+    "sentry.Project"
+  ],
+  "sentry.ProjectOwnership": [
+    "sentry.Project"
+  ],
+  "sentry.ProjectPlatform": [],
+  "sentry.ProjectRedirect": [
+    "sentry.Organization",
+    "sentry.Project"
+  ],
+  "sentry.ProjectTeam": [
+    "sentry.Project",
+    "sentry.Team"
+  ],
+  "sentry.ProjectTransactionThreshold": [
+    "sentry.Organization",
+    "sentry.Project"
+  ],
+  "sentry.ProjectTransactionThresholdOverride": [
+    "sentry.Organization",
+    "sentry.Project"
+  ],
+  "sentry.PromptsActivity": [],
+  "sentry.PullRequest": [
+    "sentry.CommitAuthor"
+  ],
+  "sentry.PullRequestComment": [
+    "sentry.PullRequest"
+  ],
+  "sentry.PullRequestCommit": [
+    "sentry.Commit",
+    "sentry.PullRequest"
+  ],
+  "sentry.QuerySubscription": [
+    "sentry.Project",
+    "sentry.SnubaQuery"
+  ],
+  "sentry.RawEvent": [
+    "sentry.Project"
+  ],
+  "sentry.RecentSearch": [
+    "sentry.Organization"
+  ],
+  "sentry.RegionOutbox": [],
+  "sentry.RegionScheduledDeletion": [],
+  "sentry.RegionTombstone": [],
+  "sentry.Relay": [],
+  "sentry.RelayUsage": [],
+  "sentry.Release": [
+    "sentry.Organization",
+    "sentry.Project"
+  ],
+  "sentry.ReleaseActivity": [
+    "sentry.Release"
+  ],
+  "sentry.ReleaseArtifactBundle": [
+    "sentry.ArtifactBundle"
+  ],
+  "sentry.ReleaseCommit": [
+    "sentry.Commit",
+    "sentry.Release"
+  ],
+  "sentry.ReleaseEnvironment": [
+    "sentry.Environment",
+    "sentry.Organization",
+    "sentry.Release"
+  ],
+  "sentry.ReleaseFile": [
+    "sentry.File"
+  ],
+  "sentry.ReleaseHeadCommit": [
+    "sentry.Commit",
+    "sentry.Release"
+  ],
+  "sentry.ReleaseProject": [
+    "sentry.Project",
+    "sentry.Release"
+  ],
+  "sentry.ReleaseProjectEnvironment": [
+    "sentry.Environment",
+    "sentry.Project",
+    "sentry.Release"
+  ],
+  "sentry.Repository": [],
+  "sentry.RepositoryProjectPathConfig": [
+    "sentry.Project",
+    "sentry.Repository"
+  ],
+  "sentry.ReprocessingReport": [
+    "sentry.Project"
+  ],
+  "sentry.Rule": [
+    "sentry.Actor",
+    "sentry.Project"
+  ],
+  "sentry.RuleActivity": [
+    "sentry.Rule"
+  ],
+  "sentry.RuleFireHistory": [
+    "sentry.Group",
+    "sentry.Project",
+    "sentry.Rule"
+  ],
+  "sentry.RuleSnooze": [
+    "sentry.AlertRule",
+    "sentry.Rule"
+  ],
+  "sentry.SavedSearch": [
+    "sentry.Organization"
+  ],
+  "sentry.ScheduledDeletion": [],
+  "sentry.SentryApp": [
+    "sentry.ApiApplication",
+    "sentry.User"
+  ],
+  "sentry.SentryAppAvatar": [
+    "sentry.SentryApp"
+  ],
+  "sentry.SentryAppComponent": [
+    "sentry.SentryApp"
+  ],
+  "sentry.SentryAppInstallation": [
+    "sentry.ApiGrant",
+    "sentry.ApiToken",
+    "sentry.SentryApp"
+  ],
+  "sentry.SentryAppInstallationForProvider": [
+    "sentry.SentryAppInstallation"
+  ],
+  "sentry.SentryAppInstallationToken": [
+    "sentry.ApiToken",
+    "sentry.SentryAppInstallation"
+  ],
+  "sentry.SentryFunction": [
+    "sentry.Organization"
+  ],
+  "sentry.ServiceHook": [],
+  "sentry.ServiceHookProject": [
+    "sentry.ServiceHook"
+  ],
+  "sentry.SnubaQuery": [
+    "sentry.Environment"
+  ],
+  "sentry.SnubaQueryEventType": [
+    "sentry.SnubaQuery"
+  ],
+  "sentry.StringIndexer": [],
+  "sentry.Team": [
+    "sentry.Actor",
+    "sentry.Organization"
+  ],
+  "sentry.TeamAvatar": [
+    "sentry.Team"
+  ],
+  "sentry.TeamKeyTransaction": [
+    "sentry.Organization",
+    "sentry.ProjectTeam"
+  ],
+  "sentry.TimeSeriesSnapshot": [],
+  "sentry.User": [],
+  "sentry.UserAvatar": [
+    "sentry.User"
+  ],
+  "sentry.UserEmail": [
+    "sentry.User"
+  ],
+  "sentry.UserIP": [
+    "sentry.User"
+  ],
+  "sentry.UserOption": [
+    "sentry.User"
+  ],
+  "sentry.UserPermission": [
+    "sentry.User"
+  ],
+  "sentry.UserReport": [],
+  "sentry.UserRole": [
+    "sentry.User"
+  ],
+  "sentry.UserRoleUser": [
+    "sentry.User",
+    "sentry.UserRole"
+  ],
+  "sessions.Session": [],
+  "sites.Site": [],
+  "social_auth.UserSocialAuth": [
+    "sentry.User"
+  ]
+}

+ 206 - 0
fixtures/backup/model_dependencies/sorted.json

@@ -0,0 +1,206 @@
+[
+  "sessions.Session",
+  "sites.Site",
+  "sentry.Option",
+  "sentry.ControlOption",
+  "sentry.Actor",
+  "sentry.RegionOutbox",
+  "sentry.ControlOutbox",
+  "sentry.ApiKey",
+  "sentry.AuthProviderDefaultTeams",
+  "sentry.AuthProvider",
+  "sentry.ControlFileBlob",
+  "sentry.ControlFile",
+  "sentry.FileBlob",
+  "sentry.File",
+  "sentry.Broadcast",
+  "sentry.CommitAuthor",
+  "sentry.ProjectDebugFile",
+  "sentry.ProguardArtifactRelease",
+  "sentry.DeletedOrganization",
+  "sentry.DeletedProject",
+  "sentry.DeletedTeam",
+  "sentry.Email",
+  "sentry.EventAttachment",
+  "sentry.EventUser",
+  "sentry.GroupCommitResolution",
+  "sentry.GroupRedirect",
+  "sentry.GroupRelease",
+  "sentry.Organization",
+  "sentry.IdentityProvider",
+  "sentry.DocIntegration",
+  "sentry.ExternalActor",
+  "sentry.ExternalIssue",
+  "sentry.Integration",
+  "sentry.IntegrationExternalProject",
+  "sentry.IntegrationFeature",
+  "sentry.LatestRepoReleaseEnvironment",
+  "sentry.User",
+  "sentry.NotificationSetting",
+  "sentry.OrganizationMapping",
+  "sentry.OrganizationMemberMapping",
+  "sentry.OrgAuthToken",
+  "sentry.ProjectPlatform",
+  "sentry.PromptsActivity",
+  "sentry.PullRequest",
+  "sentry.PullRequestComment",
+  "sentry.RecentSearch",
+  "sentry.RelayUsage",
+  "sentry.Relay",
+  "sentry.ReleaseFile",
+  "sentry.Repository",
+  "sentry.SavedSearch",
+  "sentry.ScheduledDeletion",
+  "sentry.RegionScheduledDeletion",
+  "sentry.SentryFunction",
+  "sentry.ServiceHook",
+  "sentry.RegionTombstone",
+  "sentry.ControlTombstone",
+  "sentry.UserEmail",
+  "sentry.UserIP",
+  "sentry.UserPermission",
+  "sentry.UserReport",
+  "sentry.UserRole",
+  "sentry.UserRoleUser",
+  "sentry.TimeSeriesSnapshot",
+  "sentry.Monitor",
+  "sentry.MonitorLocation",
+  "sentry.MetricsKeyIndexer",
+  "sentry.StringIndexer",
+  "sentry.PerfStringIndexer",
+  "sentry.ExportedData",
+  "sentry.ExportedDataBlob",
+  "nodestore.Node",
+  "replays.ReplayRecordingSegment",
+  "social_auth.UserSocialAuth",
+  "sentry.ServiceHookProject",
+  "sentry.LostPasswordHash",
+  "sentry.OrganizationIntegration",
+  "sentry.Identity",
+  "sentry.Team",
+  "sentry.FeatureAdoption",
+  "sentry.DashboardTombstone",
+  "sentry.Commit",
+  "sentry.BroadcastSeen",
+  "sentry.UserAvatar",
+  "sentry.TeamAvatar",
+  "sentry.OrganizationAvatar",
+  "sentry.DocIntegrationAvatar",
+  "sentry.FileBlobIndex",
+  "sentry.FileBlobOwner",
+  "sentry.ControlFileBlobIndex",
+  "sentry.ControlFileBlobOwner",
+  "sentry.AuthIdentity",
+  "sentry.Authenticator",
+  "sentry.AuditLogEntry",
+  "sentry.AssistantActivity",
+  "sentry.ArtifactBundleFlatFileIndex",
+  "sentry.ArtifactBundle",
+  "sentry.ApiApplication",
+  "sentry.UserOption",
+  "sentry.OrganizationOption",
+  "sentry.ApiAuthorization",
+  "sentry.ApiGrant",
+  "sentry.ApiToken",
+  "sentry.FlatFileIndexState",
+  "sentry.ArtifactBundleIndex",
+  "sentry.ReleaseArtifactBundle",
+  "sentry.DebugIdArtifactBundle",
+  "sentry.ProjectArtifactBundle",
+  "sentry.CommitFileChange",
+  "sentry.OrganizationMember",
+  "sentry.SentryApp",
+  "sentry.PagerDutyService",
+  "sentry.SentryAppComponent",
+  "sentry.SentryAppInstallation",
+  "sentry.SentryAppInstallationForProvider",
+  "sentry.SentryAppInstallationToken",
+  "sentry.OrganizationAccessRequest",
+  "sentry.Project",
+  "sentry.ProjectBookmark",
+  "sentry.ProjectKey",
+  "sentry.ProjectOwnership",
+  "sentry.ProjectRedirect",
+  "sentry.PullRequestCommit",
+  "sentry.RawEvent",
+  "sentry.ReprocessingReport",
+  "sentry.Rule",
+  "sentry.RuleActivity",
+  "sentry.ProjectTransactionThresholdOverride",
+  "sentry.ProjectTransactionThreshold",
+  "sentry.NotificationAction",
+  "sentry.DiscoverSavedQuery",
+  "sentry.DiscoverSavedQueryProject",
+  "sentry.NotificationActionProject",
+  "sentry.ProjectTeam",
+  "sentry.ProcessingIssue",
+  "sentry.OrganizationOnboardingTask",
+  "sentry.LatestAppConnectBuildsCheck",
+  "sentry.RepositoryProjectPathConfig",
+  "sentry.ProjectIntegration",
+  "sentry.GroupTombstone",
+  "sentry.Release",
+  "sentry.ReleaseProject",
+  "sentry.OrganizationMemberTeam",
+  "sentry.Group",
+  "sentry.GroupHistory",
+  "sentry.Environment",
+  "sentry.EnvironmentProject",
+  "sentry.Distribution",
+  "sentry.Deploy",
+  "sentry.Dashboard",
+  "sentry.DashboardProject",
+  "sentry.Counter",
+  "sentry.SentryAppAvatar",
+  "sentry.ProjectAvatar",
+  "sentry.AppConnectBuild",
+  "sentry.ProjectOption",
+  "sentry.Activity",
+  "sentry.DashboardWidget",
+  "sentry.GroupOwner",
+  "sentry.GroupAssignee",
+  "sentry.GroupBookmark",
+  "sentry.GroupEmailThread",
+  "sentry.GroupEnvironment",
+  "sentry.GroupHash",
+  "sentry.GroupInbox",
+  "sentry.GroupLink",
+  "sentry.GroupMeta",
+  "sentry.GroupResolution",
+  "sentry.GroupRuleStatus",
+  "sentry.GroupSeen",
+  "sentry.GroupShare",
+  "sentry.GroupSnooze",
+  "sentry.GroupSubscription",
+  "sentry.PlatformExternalIssue",
+  "sentry.EventProcessingIssue",
+  "sentry.SnubaQuery",
+  "sentry.SnubaQueryEventType",
+  "sentry.QuerySubscription",
+  "sentry.ProjectCodeOwners",
+  "sentry.ReleaseActivity",
+  "sentry.ReleaseCommit",
+  "sentry.ReleaseEnvironment",
+  "sentry.ReleaseHeadCommit",
+  "sentry.ReleaseProjectEnvironment",
+  "sentry.RuleFireHistory",
+  "sentry.AlertRule",
+  "sentry.AlertRuleActivity",
+  "sentry.TeamKeyTransaction",
+  "sentry.MonitorEnvironment",
+  "sentry.MonitorCheckIn",
+  "sentry.AlertRuleExcludedProjects",
+  "sentry.Incident",
+  "sentry.IncidentSeen",
+  "sentry.IncidentProject",
+  "sentry.RuleSnooze",
+  "sentry.DashboardWidgetQuery",
+  "sentry.PendingIncidentSnapshot",
+  "sentry.IncidentSnapshot",
+  "sentry.IncidentActivity",
+  "sentry.IncidentSubscription",
+  "sentry.AlertRuleTrigger",
+  "sentry.AlertRuleTriggerExclusion",
+  "sentry.AlertRuleTriggerAction",
+  "sentry.IncidentTrigger"
+]

+ 150 - 35
src/sentry/backup/dependencies.py

@@ -1,24 +1,93 @@
 from __future__ import annotations
 
-from django.db.models.fields.related import ManyToManyField
+from enum import Enum, auto, unique
+from typing import NamedTuple
+
+from django.db import models
+from django.db.models.fields.related import ForeignKey, ManyToManyField, OneToOneField
 
 from sentry.backup.helpers import EXCLUDED_APPS
+from sentry.silo import SiloMode
+from sentry.utils import json
+
+
+@unique
+class ForeignFieldKind(Enum):
+    """Kinds of foreign fields that we care about."""
+
+    # Uses our `FlexibleForeignKey` wrapper.
+    FlexibleForeignKey = auto()
+
+    # Uses our `HybridCloudForeignKey` wrapper.
+    HybridCloudForeignKey = auto()
+
+    # Uses our `OneToOneCascadeDeletes` wrapper.
+    OneToOneCascadeDeletes = auto()
+
+    # A naked usage of Django's `ManyToManyField`.
+    ManyToManyField = auto()
+
+    # A naked usage of Django's `ForeignKey`.
+    DefaultForeignKey = auto()
+
+    # A naked usage of Django's `OneToOneField`.
+    DefaultOneToOneField = auto()
+
+
+class ForeignField(NamedTuple):
+    """A field that creates a dependency on another Sentry model."""
+
+    model: models.base.ModelBase
+    kind: ForeignFieldKind
+
+
+class ModelRelations(NamedTuple):
+    """What other models does this model depend on, and how?"""
+
+    model: models.base.ModelBase
+    relations: dict[str, ForeignField]
+    silos: list[SiloMode]
+
+    def flatten(self) -> set[models.base.ModelBase]:
+        """Returns a flat list of all related models, omitting the kind of relation they have."""
+
+        return {ff.model for ff in self.relations.values()}
 
 
-def sort_dependencies():
-    """
-    Similar to Django's except that we discard the important of natural keys
-    when sorting dependencies (i.e. it works without them).
-    """
+def normalize_model_name(model):
+    return f"{model._meta.app_label}.{model._meta.object_name}"
+
+
+class DependenciesJSONEncoder(json.JSONEncoder):
+    """JSON serializer that outputs a detailed serialization of all models included in a
+    `ModelRelations`."""
+
+    def default(self, obj):
+        if isinstance(obj, models.base.ModelBase):
+            return normalize_model_name(obj)
+        if isinstance(obj, ForeignFieldKind):
+            return obj.name
+        if isinstance(obj, SiloMode):
+            return obj.name
+        if isinstance(obj, set):
+            return sorted(list(obj), key=lambda obj: normalize_model_name(obj))
+        return super().default(obj)
+
+
+def dependencies() -> dict[str, ModelRelations]:
+    """Produce a dictionary mapping model type definitions to a `ModelDeps` describing their dependencies."""
+
     from django.apps import apps
 
+    from sentry.db.models.base import ModelSiloLimit
+    from sentry.db.models.fields.foreignkey import FlexibleForeignKey
+    from sentry.db.models.fields.hybrid_cloud_foreign_key import HybridCloudForeignKey
+    from sentry.db.models.fields.onetoone import OneToOneCascadeDeletes
     from sentry.models.actor import Actor
     from sentry.models.team import Team
-    from sentry.models.user import User
 
     # Process the list of models, and get the list of dependencies
-    model_dependencies = []
-    models = set()
+    model_dependencies_list: dict[str, ModelRelations] = {}
     for app_config in apps.get_app_configs():
         if app_config.label in EXCLUDED_APPS:
             continue
@@ -26,42 +95,86 @@ def sort_dependencies():
         model_iterator = app_config.get_models()
 
         for model in model_iterator:
-            models.add(model)
-            # Add any explicitly defined dependencies
-            if hasattr(model, "natural_key"):
-                deps = getattr(model.natural_key, "dependencies", [])
-                if deps:
-                    deps = [apps.get_model(*d.split(".")) for d in deps]
-            else:
-                deps = []
+            relations: dict[str, ForeignField] = dict()
 
-            # Now add a dependency for any FK relation with a model that
-            # defines a natural key
+            # Now add a dependency for any FK relation with a model that defines a natural key.
             for field in model._meta.fields:
                 rel_model = getattr(field.remote_field, "model", None)
                 if rel_model is not None and rel_model != model:
                     # TODO(hybrid-cloud): actor refactor.
                     # Add cludgy conditional preventing walking actor.team_id, actor.user_id
                     # Which avoids circular imports
-                    if model == Actor and (rel_model == Team or rel_model == User):
+                    if model == Actor and rel_model == Team:
                         continue
 
-                    deps.append(rel_model)
+                    if isinstance(field, FlexibleForeignKey):
+                        relations[field.name] = ForeignField(
+                            model=rel_model,
+                            kind=ForeignFieldKind.FlexibleForeignKey,
+                        )
+                    elif isinstance(field, HybridCloudForeignKey):
+                        relations[field.name] = ForeignField(
+                            model=rel_model,
+                            kind=ForeignFieldKind.HybridCloudForeignKey,
+                        )
+                    elif isinstance(field, ForeignKey):
+                        relations[field.name] = ForeignField(
+                            model=rel_model,
+                            kind=ForeignFieldKind.DefaultForeignKey,
+                        )
 
-            # Also add a dependency for any simple M2M relation with a model
-            # that defines a natural key.  M2M relations with explicit through
-            # models don't count as dependencies.
+            # Also add a dependency for any simple M2M relation.
             many_to_many_fields = [
                 field for field in model._meta.get_fields() if isinstance(field, ManyToManyField)
             ]
             for field in many_to_many_fields:
                 rel_model = getattr(field.remote_field, "model", None)
                 if rel_model is not None and rel_model != model:
-                    deps.append(rel_model)
+                    relations[field.name] = ForeignField(
+                        model=rel_model,
+                        kind=ForeignFieldKind.ManyToManyField,
+                    )
+
+            # Finally, get all simple O2O relations as well.
+            one_to_one_fields = [
+                field for field in model._meta.get_fields() if isinstance(field, OneToOneField)
+            ]
+            for field in one_to_one_fields:
+                rel_model = getattr(field.remote_field, "model", None)
+                if rel_model is not None and rel_model != model:
+                    if isinstance(field, OneToOneCascadeDeletes):
+                        relations[field.name] = ForeignField(
+                            model=rel_model,
+                            kind=ForeignFieldKind.OneToOneCascadeDeletes,
+                        )
+                    elif isinstance(field, OneToOneField):
+                        relations[field.name] = ForeignField(
+                            model=rel_model,
+                            kind=ForeignFieldKind.DefaultOneToOneField,
+                        )
+                    else:
+                        raise RuntimeError("Unknown one to kind")
+
+            model_dependencies_list[normalize_model_name(model)] = ModelRelations(
+                model=model,
+                relations=relations,
+                silos=list(
+                    getattr(model._meta, "silo_limit", ModelSiloLimit(SiloMode.MONOLITH)).modes
+                ),
+            )
+    return model_dependencies_list
+
+
+def sorted_dependencies():
+    """Produce a list of model definitions such that, for every item in the list, all of the other models it mentions in its fields and/or natural key (ie, its "dependencies") have already appeared in the list.
+
+    Similar to Django's algorithm except that we discard the importance of natural keys
+    when sorting dependencies (ie, it works without them)."""
 
-            model_dependencies.append((model, deps))
+    model_dependencies_list = list(dependencies().values())
+    model_dependencies_list.reverse()
+    model_set = {md.model for md in model_dependencies_list}
 
-    model_dependencies.reverse()
     # Now sort the models to ensure that dependencies are met. This
     # is done by repeatedly iterating over the input list of models.
     # If all the dependencies of a given model are in the final list,
@@ -71,32 +184,34 @@ def sort_dependencies():
     # If we do a full iteration without a promotion, that means there are
     # circular dependencies in the list.
     model_list = []
-    while model_dependencies:
+    while model_dependencies_list:
         skipped = []
         changed = False
-        while model_dependencies:
-            model, deps = model_dependencies.pop()
+        while model_dependencies_list:
+            model_deps = model_dependencies_list.pop()
+            deps = model_deps.flatten()
+            model = model_deps.model
 
             # If all of the models in the dependency list are either already
             # on the final model list, or not on the original serialization list,
             # then we've found another model with all it's dependencies satisfied.
             found = True
-            for candidate in ((d not in models or d in model_list) for d in deps):
+            for candidate in ((d not in model_set or d in model_list) for d in deps):
                 if not candidate:
                     found = False
             if found:
                 model_list.append(model)
                 changed = True
             else:
-                skipped.append((model, deps))
+                skipped.append(model_deps)
         if not changed:
             raise RuntimeError(
                 "Can't resolve dependencies for %s in serialized app list."
                 % ", ".join(
-                    f"{model._meta.app_label}.{model._meta.object_name}"
-                    for model, deps in sorted(skipped, key=lambda obj: obj[0].__name__)
+                    normalize_model_name(m.model)
+                    for m in sorted(skipped, key=lambda obj: normalize_model_name(obj))
                 )
             )
-        model_dependencies = skipped
+        model_dependencies_list = skipped
 
     return model_list

+ 2 - 2
src/sentry/backup/exports.py

@@ -7,7 +7,7 @@ import click
 from django.core.serializers import serialize
 from django.core.serializers.json import DjangoJSONEncoder
 
-from sentry.backup.dependencies import sort_dependencies
+from sentry.backup.dependencies import sorted_dependencies
 
 UTC_0 = timezone(timedelta(hours=0))
 
@@ -41,7 +41,7 @@ def exports(dest, old_config: OldExportConfig, indent: int, printer=click.echo):
 
     def yield_objects():
         # Collate the objects to be serialized.
-        for model in sort_dependencies():
+        for model in sorted_dependencies():
             if (
                 not getattr(model, "__include_in_export__", old_config.include_non_sentry_models)
                 or model.__name__.lower() in old_config.excluded_models

+ 1 - 1
src/sentry/backup/helpers.py

@@ -4,7 +4,7 @@ from enum import Enum
 from typing import Type
 
 # Django apps we take care to never import or export from.
-EXCLUDED_APPS = frozenset(("auth", "contenttypes"))
+EXCLUDED_APPS = frozenset(("auth", "contenttypes", "fixtures"))
 
 
 def get_final_derivations_of(model: Type) -> set[Type]:

+ 0 - 2
tests/sentry/backup/__init__.py

@@ -70,8 +70,6 @@ def targets(expected_models: list[Type]):
                     if isinstance(f, models.ManyToManyField):
                         continue
 
-                    # TODO(getsentry/team-ospo#156): Maybe make these checks recursive for models
-                    # that have POPOs for some of their field values?
                     if field_name not in data:
                         mistakes.append(f"Must include field: `{field_name}`")
                         continue

+ 55 - 0
tests/sentry/backup/test_dependencies.py

@@ -0,0 +1,55 @@
+from difflib import unified_diff
+
+from sentry.backup.dependencies import DependenciesJSONEncoder, dependencies, sorted_dependencies
+from sentry.testutils.factories import get_fixture_path
+
+encoder = DependenciesJSONEncoder(
+    sort_keys=True,
+    ensure_ascii=True,
+    check_circular=True,
+    allow_nan=True,
+    indent=2,
+    encoding="utf-8",
+)
+
+
+def test_detailed():
+    fixture_path = get_fixture_path("backup", "model_dependencies", "detailed.json")
+    with open(fixture_path) as fixture:
+        expect = fixture.read().splitlines()
+
+    actual = encoder.encode(dependencies()).splitlines()
+    diff = list(unified_diff(expect, actual, n=3))
+    if diff:
+        raise AssertionError(
+            "Model dependency graph does not match fixture. If you are seeing this in CI, please run `bin/generate-model-dependency-fixtures` and re-upload:\n\n"
+            + "\n".join(diff)
+        )
+
+
+def test_flat():
+    fixture_path = get_fixture_path("backup", "model_dependencies", "flat.json")
+    with open(fixture_path) as fixture:
+        expect = fixture.read().splitlines()
+
+    actual = encoder.encode({k: v.flatten() for k, v in dependencies().items()}).splitlines()
+    diff = list(unified_diff(expect, actual, n=3))
+    if diff:
+        raise AssertionError(
+            "Model dependency graph does not match fixture. If you are seeing this in CI, please run `bin/generate-model-dependency-fixtures` and re-upload:\n\n"
+            + "\n".join(diff)
+        )
+
+
+def test_sorted():
+    fixture_path = get_fixture_path("backup", "model_dependencies", "sorted.json")
+    with open(fixture_path) as fixture:
+        expect = fixture.read().splitlines()
+
+    actual = encoder.encode(sorted_dependencies()).splitlines()
+    diff = list(unified_diff(expect, actual, n=3))
+    if diff:
+        raise AssertionError(
+            "Model dependency list does not match fixture. If you are seeing this in CI, please run `bin/generate-model-dependency-fixtures` and re-upload:\n\n"
+            + "\n".join(diff)
+        )