Skip to content

fix(backup): reconcile legacy profiles and ignore Finder metadata - #248

Merged
steipete merged 1 commit into
mainfrom
fix/backup-legacy-profile-merge
Oct 24, 2026
Merged

steipete merged 1 commit into
mainfrom
fix/backup-legacy-profile-merge

Conversation

@steipete

Copy link
Copy Markdown
Owner

Backup sync can stop indefinitely when an older backup still contains a legacy profile whose handle is now owned by its canonical numeric profile. Finder-created data/.DS_Store files can independently block validation and sync.

The profile import path previously reconciled only profile_me and canonical numeric IDs, leaving other legacy rows to collide on the unique handle constraint. Reuse the deferred merge plans to reserve colliding handles and fold raw-proven numeric identities into canonical profiles after every reference has been imported. Unproven or conflicting identities remain separate, and identity vetoes survive repeated imports even when an intervening backup supplies no numeric identity.

Evaluate each alias independently. Preserve current canonical state unless matching imported snapshots establish a newer state, including when its handle is reserved. Apply approved newer mutable state coherently, protect more recent handle owners, and keep the original observation times so historical imports cannot acquire artificial freshness or roll newer data back. Refresh derived identity data after recovered handles are assigned.

Ignore only regular files named exactly .DS_Store under data/ during inventory validation and checkout-cleanliness checks. Exclude them explicitly from Git staging and remove previously tracked metadata from the next backup commit. Unexpected files, directories, and symlinks remain errors. Update the backup documentation and unreleased changelog.

Validation:

  • Reproduced the original UNIQUE constraint failed: profiles.handle failure before the fix using synthetic profiles.
  • Regression coverage includes tweet, DM, follow, moderation, history, affiliation, bio-entity, and identity-index references; export/validate round trips; conflicting identities; repeated imports; multiple aliases; handle reuse; and historical observation ordering.
  • Finder metadata tests cover root and nested data files, sync without global Git ignores, staging exclusions, previously tracked metadata cleanup, and symlink/directory rejection.
  • ./scripts/bun-canary.sh ./scripts/run-vitest.mjs run src/lib/backup.test.ts src/lib/x-profile.test.ts — 2 files, 125 tests passed.
  • ./scripts/bun-canary.sh ./scripts/run-vitest.mjs run src/lib — 115 files, 1,441 tests passed.
  • ./scripts/bun-canary.sh run --bun check — formatting, lint, and type checks passed.
  • Supplemental profile-history tests passed all 4 cases, including dated snapshot deduplication and preservation of newer evidence.

No Playwright or end-to-end tests were run.

@clawsweeper

clawsweeper Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

🦞👀
ClawSweeper picked this up.

Pull request received. I will update this pull request when review starts.

ClawSweeper review complete

ClawSweeper finished reviewing this revision. The review result is being finalized.

View the workflow run.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-24T22:06:28.221111Z 2f8a7f5 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2f8a7f5c2c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines 603 to 605
provenLegacyProfileIds: new Set([profileId]),
preserveCanonicalMutableStateForLegacyProfileIds:
preserveCanonicalMutableState ? new Set([profileId]) : undefined,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Free reserved source handles when merging into an existing stub

When the destination already contains a canonical stub for user 4242 and another profile owns friend, importing a raw-proven legacy profile for 4242 with handle friend still fails with UNIQUE constraint failed: profiles.handle. Reconciliation first assigns the legacy row a birdclaw_stale_* handle, then this deferred merge calls mergeCurrentProfile, whose placeholder-target branch adopts that reserved source handle. Its call to allocateReservedProfileHandle at line 827 returns the same handle because it excludes the source row from collision checks, so the source never releases it before the target update. Exclude the source's current handle in the existing-target branch as well, as the new missing-target branch already does. This failure rolls back the entire import and blocks backup sync for this combination.

Useful? React with 👍 / 👎.

@clawsweeper clawsweeper Bot added clawsweeper:intake:bug Preliminary submission classification; full source review pending. clawsweeper:intake:needs-environment Preliminary submission classification; full source review pending. clawsweeper:intake:needs-repro Preliminary submission classification; full source review pending. P2 Normal priority bug or improvement with limited blast radius. merge-risk: 🚨 compatibility 🚨 Merging this PR could break existing users, config, migrations, defaults, or upgrades. rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. and removed clawsweeper:intake:bug Preliminary submission classification; full source review pending. clawsweeper:intake:needs-repro Preliminary submission classification; full source review pending. clawsweeper:intake:needs-environment Preliminary submission classification; full source review pending. labels Oct 24, 2026
@clawsweeper

clawsweeper Bot commented Oct 24, 2026

Copy link
Copy Markdown

Codex review: blocked before merge.

What this changes

This PR reconciles proven legacy profile identities during backup imports and excludes regular Finder metadata from backup validation and commits.

Example: A backup contains profile_friend with numeric identity 4242, while the destination already has profile_user_4242 owning friend.

  • Before: Import fails with UNIQUE constraint failed: profiles.handle.
  • After: The legacy profile and its references merge into the canonical profile while newer canonical state is preserved.

Review scores

Measure Result What it means
Overall readiness 🦪 silver shellfish (2/6) Useful, well-targeted work with substantial integration coverage, but a concrete import failure blocks readiness.
Proof confidence 🦐 gold shrimp (3/6) Real behavior proof is not required for maintainer- or bot-authored pull requests.
Patch quality 🦪 silver shellfish (2/6) 1 actionable review finding remain.

Product

Kind: Bug fix · Worth it: Yes · Fix scope: Complete
User problem: Valid older backups can block sync on legacy profile handle collisions, and Finder metadata can independently prevent validation.
Reason: The maintainer-authored direction repairs the existing lossless backup contract through its current owners without adding configuration or a parallel implementation.

Merge readiness

⛔ Blocked before merge - 2 items remain

Keep open: this maintainer-authored PR addresses a real backup failure, but the expanded merge path still aborts for an existing canonical stub.

Priority: P2
Reviewed head: 2f8a7f5c2cc3f9b979e8b4c834f4a43b298b0e9d

Before merge

  • Free the reserved source handle before updating an existing stub (P2) - The newly expanded deferred path reaches this branch when profile_user_4242 already exists as an undated stub, another profile owns friend, and an imported profile_friend proves numeric identity 4242. Reconciliation reserves profile_friend's handle as birdclaw_stale_<digest>. The placeholder-target branch then chooses that source handle for the canonical row, but this allocation returns the same value because collision checks exclude the source row. The source never releases the handle, so the target update throws UNIQUE constraint failed: profiles.handle and rolls back the entire import. Exclude the source's current handle here, as the changed missing-target branch already does, and add an import regression for this combination.
  • Add data-model compatibility proof - The review found that existing stored data may not work after upgrade. Show that existing data still loads and works with this change.

Findings

  • [P2] Free the reserved source handle before updating an existing stub — src/lib/profile-identity.ts:827

Tests

  • Missing end-to-end proof: Supply a v0.16.2-to-head CLI upgrade/import/export run preserving settings, local-only rows, references, and observation times; the added stub-collision regression must fail before the repair and pass afterward.
Agent review details

How this fits together

Backup commands validate Git-backed JSONL, import portable rows into SQLite, reconcile profile identities and references, then export and publish the merged store.

flowchart LR
  A[Backup CLI] --> B[Git checkout and inventory validation]
  B --> C[JSONL import transaction]
  C --> D[Profile identity reconciliation]
  D --> E[SQLite profiles and references]
  E --> F[Validated export and Git publication]
Loading

Technical review

Best possible solution:

Use the existing reserved-handle allocator to free the source handle before either canonical-target branch adopts it.

Do we have a high-confidence way to reproduce the issue?

Main only defers profile_me reconciliation, leaving other legacy handle collisions actionable; the PR's additional existing-stub failure is traceable through reservation, allocation, and the unique constraint without executing target code.

Is this the best way to solve the issue?

Extending deferred reconciliation in the existing identity owner is appropriate, but handle transfer must free the reserved source handle in both target branches.

Full review comments:

  • [P2] Free the reserved source handle before updating an existing stub — src/lib/profile-identity.ts:827
    The newly expanded deferred path reaches this branch when profile_user_4242 already exists as an undated stub, another profile owns friend, and an imported profile_friend proves numeric identity 4242. Reconciliation reserves profile_friend's handle as birdclaw_stale_<digest>. The placeholder-target branch then chooses that source handle for the canonical row, but this allocation returns the same value because collision checks exclude the source row. The source never releases the handle, so the target update throws UNIQUE constraint failed: profiles.handle and rolls back the entire import. Exclude the source's current handle here, as the changed missing-target branch already does, and add an import regression for this combination.
    Confidence: 0.99

Overall correctness: patch is incorrect
Overall confidence: 0.96

AGENTS.md: not found in the target repository.

Codex review notes: model internal, reasoning medium; reviewed against 40e2e23af737.

Provenance checked

  • Deferred legacy profile reconciliation keeps the original intent (1353329: Safely adopt proven selected-account aliases after references are imported while preserving newer canonical state, as documented in fix: fail account sync when backup fails #127.)
  • Strict backup inventory and checkout cleanliness changes intended behavior with a stated reason (131f6a1: Reject unexpected backup contents and dirty publication inputs; the PR explicitly narrows this policy for regular Finder metadata while retaining symlink and directory rejection.)
  • Profile snapshot observation times keeps the original intent (a5838a3: Record deduplicated profile-history evidence; explicit historical observation times now preserve that evidence without replacing newer observations.)

Testing

Proof path: in-process harness.

Security

None.

Evidence

Acceptance criteria:

  • ./scripts/bun-canary.sh ./scripts/run-vitest.mjs run src/lib/backup.test.ts src/lib/x-profile.test.ts src/lib/profile-history.test.ts
  • ./scripts/bun-canary.sh run --bun check
  • Verify the new existing-stub regression fails on 2f8a7f5 and passes with the allocator repair.

What I checked:

Likely related people:

  • Peter Steinberger: Raw commit 131f6a1 adds src/lib/profile-identity.ts:342 relative to its recorded parents. This identifies author metadata, not feature responsibility or a PR merger. (role: source-line author; confidence: high; commits: 131f6a1a7074; files: src/lib/profile-identity.ts)
  • Paul Rodriguez: Raw commit 5ad517d adds src/lib/backup.ts:498 relative to its recorded parents. This identifies author metadata, not feature responsibility or a PR merger. (role: source-line author; confidence: medium; commits: 5ad517d8c1d3; files: src/lib/backup.ts)

Review metrics

Metric Value Why it matters
Production and test changes Production +216/-57 lines; tests +675/-49 lines The larger test portion covers distinct reference-preservation, identity-veto, observation-ordering, and Git metadata failures.

Labels

Label changes:

  • add P2: The repair has clear value for affected backup stores, with a remaining failure limited to a particular profile collision.
  • add merge-risk: 🚨 compatibility: The new reconciliation path can abort imports of existing stores containing a canonical stub and a reused handle despite passing current checks.
  • add rating: 🦪 silver shellfish: Overall readiness is 🦪 silver shellfish; proof is 🦐 gold shrimp and patch quality is 🦪 silver shellfish.
  • add status: ⏳ waiting on author: ClawSweeper has contributor-facing work open and is waiting for author action.

Label justifications:

  • P2: The repair has clear value for affected backup stores, with a remaining failure limited to a particular profile collision.
  • merge-risk: 🚨 compatibility: The new reconciliation path can abort imports of existing stores containing a canonical stub and a reused handle despite passing current checks.
  • rating: 🦪 silver shellfish: Overall readiness is 🦪 silver shellfish; proof is 🦐 gold shrimp and patch quality is 🦪 silver shellfish.
  • status: ⏳ waiting on author: ClawSweeper has contributor-facing work open and is waiting for author action.

Rating scale

6/6 🦀 challenger crab · 5/6 🦞 diamond lobster · 4/6 🐚 platinum hermit · 3/6 🦐 gold shrimp · 2/6 🦪 silver shellfish · 1/6 🧂 unranked krab. Overall follows the weaker of proof and patch quality; ✨ marks media proof (a screenshot, video, or linked artifact) that directly shows the changed behavior.

Workflow

ClawSweeper edits this one comment on every review. Comment @clawsweeper re-review for a fresh review only. Repair and merge execution lanes are retired.

Reviewed October 24, 2026, 6:18 PM ET / 22:18 UTC.

@steipete
steipete merged commit 2382589 into main Oct 24, 2026
5 checks passed
@steipete
steipete deleted the fix/backup-legacy-profile-merge branch October 24, 2026 22:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

merge-risk: 🚨 compatibility 🚨 Merging this PR could break existing users, config, migrations, defaults, or upgrades. P2 Normal priority bug or improvement with limited blast radius. rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant