A space created through com.atproto.simplespace is never declared to the authority store, so listRepos/registerNotify/notifyWrite answer SpaceNotFound #105
Labels
No labels
breaking-change
bug
documentation
duplicate
enhancement
good first issue
help wanted
performance
question
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Grandiras/ATProto.NET#105
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
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?
ISimpleSpaceStoreandISpaceAuthorityStorehold separate state, and nothing bridges them at space-creation time.CreateSimpleSpaceEndpoint(src/ATProtoNet.Server/Spaces/SimpleSpaceEndpoints.cs) writes only throughISimpleSpaceStore.CreateSpaceAsync. But three of the authority endpoints gate on the other store first:ListSpaceReposEndpoint—SpaceAuthorityEndpoints.cs:156RegisterNotifyEndpoint—SpaceAuthorityEndpoints.cs:228NotifyWriteEndpoint—SpaceAuthorityEndpoints.cs:348Each calls
ISpaceAuthorityStore.GetSpaceStateAsyncand throwsSpaceNotFoundunless it answersGranted.InMemorySpaceAuthorityStoreanswersGrantedonly for a space passed toDeclareSpace, andEfCoreSpaceAuthorityStore<T>(added in #102) only for one passed toDeclareSpaceAsync. Neither is called by anything but tests —grep -rn DeclareSpace --include=*.csfinds onlySpaceServerEndpointTests.So a service registered the documented way —
AddSpaceAuthority<T>(key).AddSimpleSpace<T>()— creates a space that mints credentials correctly (the access policy reads thesimplespacestore) but whose writer set can never be populated:notifyWriteis refused withSpaceNotFound,registerNotifyis refused, andlistReposreturns the same. Since the writer set is the sync boundary, no syncer can find anything in the space. The operator's only fix today is to callDeclareSpace/DeclareSpaceAsyncout of band after everycreateSpace, which is not documented anywhere.The same gap exists for deletion:
deleteSpaceflags the space in thesimplespacestore, but the authority store keeps answeringGranted, solistReposon a deleted space returns its writer set instead ofSpaceDeleted.Worth deciding where the bridge belongs rather than patching one call site:
simplespaceendpoints callISpaceAuthorityStore.DeclareSpace…/MarkDeleted…(needs those on the interface, which they currently are not — they are implementation-specific methods on both concrete stores), orISpaceAuthorityStoreadapter that reads space existence and deletion from anISimpleSpaceStoreand keeps only the writer set and subscriptions of its own, which is what a PDS actually wants, orGetSpaceStateAsyncfall back to the registeredISpaceAccessPolicy.A regression test belongs with it: create a space through
createSpace, then drivenotifyWriteandlistReposfor it through the test host, asSpaceServerEndpointTestsalready does for a manually declared space.Filed by Claude while working on #102 (run).
🤖 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.
Pushed
claude/issue-105.What the fix is
I took the adapter option from the issue rather than adding
DeclareSpace…/MarkDeleted…toISpaceAuthorityStore. The two stores answer different questions, and only one of them is written bycreateSpace:SimpleSpaceAuthorityStore(src/ATProtoNet.Server/Spaces/SimpleSpaceAuthorityStore.cs) implementsISpaceAuthorityStoreby reading space existence and deletion from anISimpleSpaceStore, and delegating the writer set and the notification registrations to an inner store.AddSpaceAuthority<TStore>()now registersTStoreconcretely and resolvesISpaceAuthorityStorethrough a factory that wraps it in the bridge when anISimpleSpaceStoreis present. The decision happens at resolution, soAddSpaceAuthority<T>().AddSimpleSpace<T>()works in either order.deleteSpaceneeds no second write:listReposanswersSpaceDeletedfrom the same moment, which is what tells a syncer that missed the notification to drop its copy.simplespacestore has never heard of falls through to the inner store, soDeclareSpace/DeclareSpaceAsynckeep working for a bespoke space type running alongside the baseline. A store registered asISpaceAuthorityStorebeforeAddSpaceAuthority<T>()is left exactly as registered (documented; wrap by hand).InMemorySpaceStoreWarningunwraps the bridge before checking whether the writer set is in-process, so the startup line still appears.Tests
SpaceServerEndpointTestsnow drives the path the issue asked for through the test host —createSpaceover the owner's session,notifyWritewith real service auth signed by the writing account, thenlistReposwith a credential minted for that space — plusregisterNotifyon a created space,listReposafterdeleteSpace(using a credential minted before the deletion, since a credential outlives the space), andnotifyWritefor a space neither store knows still answeringSpaceNotFound. The two existing tests that calledDeclareSpaceout of band no longer need to.SimpleSpaceAuthorityStoreTestscovers the store directly (granted / deleted / fall-through / unknown / write-through), andSpaceStoreRegistrationTestspins both registration orders and the unwrapped no-simplespacecase.Verified:
dotnet buildclean (0 warnings),dotnet test tests/ATProtoNet.Tests/→ 1708 passed, 0 failed, 5 skipped. I also confirmed the new tests are genuine regression tests by stubbing the bridge back out — 4 of them fail without it — then restored and re-ran.Docs: the
docs/spaces.mdparagraph that told operators to declare eachsimplespacespace to the authority store now describes the bridge and scopesDeclareSpace…to bespoke space types; the XML docs on bothDeclareSpaceoverloads say the same.CHANGELOG.mdhas an entry under[Unreleased] → Fixed.⏱ 7m 50s · 42 turns · tokens: 136.6k in / 30.3k out (+4230.9k cache-read)
📜 Full session transcript
→ Opened PR: #106