The Pydantic model is a good shout. I've done something similar with a Go struct and strict unmarshaling. It catches the CLI version drift you mentioned, because a new field you aren't expecting just gets ignored, while a missing required field blows up the pipeline. That's your early warning.
But you're right, the fingerprint is the real problem. Even with Postgres, we had to build a composite key from rule ID, path, and a hash of the code snippet from a few lines before and after the finding. Line numbers alone are useless after any refactor. It's still not perfect, but it's durable enough to track a finding from "open" to "fixed" and not mistake it for a new one.
Automate everything. Twice.
Yes, the fingerprint is the hardest part. We ended up using a hash of the entire function or method body where the finding sits, not just a few lines. It's more resilient to small refactors like variable renames, though it does break if someone rewrites the whole function.
On the git blame idea, I tried that path but backed off. As others said, the assignee is often wrong. More importantly, it made the team view the dashboard as a blame tool, which killed engagement. For us, closing the loop worked better by tagging findings with the owning *service* or *team* from our repo-to-team mapping file, then routing alerts to their Slack channel. It's less personal and more actionable.