Simplespace independent read/write access #
The policy split follows the September 9, 2026 lexicons (upstream change). ZDS v0.4.0 also adopts the October 1 credential, notification, and spaceType changes; see the current Spaces contract. These experimental updates intentionally remove the previous request shapes.
Client changes #
- Replace
addMember({space, did})withputMember({space, did, read, write}). Both flags are required booleans. This is an upsert: subsequent calls replace both permissions, includingfalsevalues. - Replace
policyincreateSpacewithreadPolicyandwritePolicy. Both are required and accept the same lexicon policy unions as before. updateSpaceaccepts either policy independently; omitted fields stay unchanged. A supplied oldpolicyfield returnsInvalidRequest, including when mixed with the new fields.- Read both policies from
getSpace; the oldpolicyoutput is removed. - Read
readandwritefrom eachlistMembersentry. The page default is 100, maximum 1000. The owner needs a covering read grant to enumerate members. - Managing apps receive
checkUserAccesswithaccess=readoraccess=write. Write checks omitclientId. Each policy can name a different managing app.
For example, a public-readable space with selected writers:
{
"spaceType": "example.shared.notes",
"readPolicy": {"$type": "com.atproto.simplespace.defs#publicPolicy"},
"writePolicy": {"$type": "com.atproto.simplespace.defs#memberListPolicy"},
"appAccess": {"$type": "com.atproto.simplespace.defs#open"}
}
Read policy governs credential issuance. Write policy governs which writers the space authority tracks and forwards notifications for. A resident can store records in their own repo even when the space denies write access; those writes do not register the writer or get forwarded by the authority. This also applies when both roles are hosted by ZDS.
Stored data #
Startup migrates the previous SQLite schema in one transaction:
- The previous policy and managing app become the read policy and managing app.
- The write policy and managing app start with the same values.
- Every existing member starts with read and write enabled.
- Records, memberships, app-access configuration, and deleted-space tombstones are retained. Future restarts do not reapply the access defaults.
This preserves previous access while removing compatibility code from the API. Old clients must update; no legacy route or single-policy translation remains. The policy-schema migration alone does not change credential expiry. The v0.4.0 credential cutover does: clients must reacquire credentials and replace Spaces DPoP with HTTP message signatures. OAuth DPoP is unchanged.
Before deploying, take a consistent database backup and volume snapshot, rehearse startup on a disposable copy, and compare the account, policy, membership, and record state. A binary rollback cannot safely represent independently changed permissions using the old schema.
Validation #
just test covers migration preservation and repeat execution, member upserts,
and independent policy decisions. just smoke-permissioned exercises read-only,
write-only, neither, and both permissions through credential issuance and actual
record writes, plus old payload rejection and partial policy updates.
Use just bench space 1000 and just bench space 10000 for the storage baseline.
These are single-caller storage measurements; they do not measure remote
managing-app latency. Use just bench http separately for the public HTTP paths.