Claude: changes for issue #38 #62
No reviewers
Labels
No labels
breaking-change
bug
documentation
duplicate
enhancement
good first issue
help wanted
performance
question
wontfix
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Grandiras/ATProto.NET!62
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "claude/issue-38"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #38.
🤖 Reviewing this PR — follow along in the action run; the reply will link the full session transcript.
📜 Live transcript — updating every ~5 minutes while the task runs.
Solid, well-documented addition of EF Core-backed PDS stores with good test coverage (including a real-SQL SQLite run, not just the EF in-memory provider). The keyset pagination, case-insensitive lookup handling, content-addressed blob dedup, and
TryAddSingletonorder-independence fix are all reasonable and match the documented contracts. Two non-blocking correctness concerns worth a look before/after merge: a check-then-insert race in blob content dedup, and a silently-ignored options update on repeatedAddAtProtoPdsEfCoreStoresregistration. Neither is a merge-blocker for a first cut of this feature, but the blob race is worth tracking since concurrent uploads of identical content are exactly the case dedup is meant to handle well.⏱ 1m 31s · 12 turns · tokens: 56.4k in / 7.2k out (+784.9k cache-read)
📜 Full session transcript
@ -0,0 +241,4 @@context.Set<PdsBlobRefEntity>().Add(new PdsBlobRefEntity{Did = blob.Did,Cid = blob.Cid,PutBlobAsynccheckscontentExistsand only inserts aPdsBlobEntityrow if absent, but the check-then-insert isn't atomic. Two accounts uploading identical content concurrently (exactly the scenario blob dedup targets) can both observecontentExists == falseand bothAddthe same CID; the secondSaveChangesAsyncthen throws a PK-violationDbUpdateExceptioninstead of succeeding. Consider catching the unique-constraint violation and treating it as "already exists", or using a provider-agnostic upsert.@ -0,0 +55,4 @@services.RemoveAll<IAccountStore>();services.RemoveAll<IRepoStore>();optionsis registered withTryAddSingleton, but the store registrations (IAccountStore/IRepoStore) are re-added viaAddSingletonafterRemoveAllon every call. IfAddAtProtoPdsEfCoreStores<TContext>()is invoked a second time with a differentconfiguredelegate, the new stores end up wired to the first call'sPdsEfCoreStoreOptionsinstance — the second call's configuration is silently dropped rather than applied or erroring.@claude-bot Please fix the conflicts and work on the feedback.
🤖 On it — follow along in the action run; the reply will link the full session transcript.
📜 Live transcript — updating every ~5 minutes while the task runs.
Merged
mainintoclaude/issue-38and worked through both review comments — pushed asdbaf40c, so the PR is up to date and conflict-free.Conflicts
Three, all from the federation work in #40 landing on
main:CHANGELOG.md(both added entries at the same spot — kept both),docs/pds.md(the new Session signing key section and my Persistent Storage (EF Core) section collided — kept both, session key under Configuration where it belongs), and ausingblock inPdsHostingExtensions.cs.Review feedback
Blob dedup race (
EfCoreRepoStore.cs:244) — real, and fixed.PutBlobAsyncnow retries once against a fresh context whenSaveChangesAsyncthrowsDbUpdateException: the winner stored the same content-addressed bytes, so the retry finds the row and writes only its own reference. One retry is provably enough — after it, content is shared and the reference row is keyed by this DID, so no third writer can insert either. I also closed the mirror-image window: aPdsBlobRefs→PdsBlobsforeign key withRestrictstops orphan collection deleting content that another account has just referenced, and collection now treats losing that race as a no-op rather than failing the delete that triggered it.The test drives the actual race rather than approximating it — a
SaveChangesInterceptorinserts the conflicting content row between the store's existence check and its save, and the test asserts two save attempts, so the recovery path is what makes it pass. It runs on SQLite only; the EF in-memory provider lets a rawArgumentExceptionout of its backing dictionary instead of wrapping it asDbUpdateException, which is now recorded as aReportsConstraintViolationsflag on the suite.Silently-dropped options on a repeat registration (
PdsEfCoreStoreExtensions.cs:58) — also real, also fixed. The options registration is now replaced alongside the stores, and each store closes over theoptionsinstance from the call that registered it. The test proves it behaviourally: a second call configuringClientSideAccountLookup+MaxClientSideLookupRows = 1actually changes what the resolvedIAccountStorereturns.Merge follow-through
The merge left the EF stores behind the interfaces #40 introduced, so I brought them forward — an EF-backed PDS would otherwise have thrown on every federation call:
ListAllRecordsAsync/ListBlobCidsAsynconEfCoreRepoStore. Records are ordered in memory by the ordinalcollection/rkeyMST key, not by SQL —ORDER BY (Collection, Rkey)is a different ordering ("a.b.c/x"sorts before"a.b/y"because.precedes/), and a collation need not be ordinal. A test pins the result againstInMemoryRepoStoreusing exactly that case.EfCoreRepoCommitStore<TContext>+PdsRepoHeadEntity, registered byAddAtProtoPdsEfCoreStores. Durable records paired with the default in-memory head store would restart the revision sequence on every process start, which relays read as the repo rewinding.PdsAccountEntity.RotationKey, mapped through create/update/read.Verified
dotnet buildclean (0 errors, no new warnings on touched files);dotnet test tests/ATProtoNet.Tests/— 1327 passed, 0 failed, including 82 in the EF store suite across both the in-memory and real-SQLite runs. CHANGELOG anddocs/pds.mdupdated in the same commit.⏱ 11m 15s · 74 turns · tokens: 117.5k in / 46.0k out (+6347.4k cache-read)
📜 Full session transcript