···7373 including invite-code minting.
7474- `ZDS_INVITE_REQUIRED`: set to `true` to require invite codes for account
7575 creation. `describeServer` reflects this value.
7676+- `ZDS_PERMISSIONED_DATA`: set to `true` to opt into experimental
7777+ `com.atproto.space.*` permissioned-data routes. Default: disabled. The
7878+ current route slice is gated and non-mutating until space storage is
7979+ implemented.
7680- Passkeys do not require a separate deployment secret. WebAuthn RP ID is
7781 derived from the public URL host.
7882
+121
docs/permissioned-data.md
···11+# permissioned data plan
22+33+Permissioned data support is experimental. ZDS should ship it behind an
44+operator opt-in flag, off by default, before any `com.atproto.space.*` route is
55+publicly reachable.
66+77+Planned flag:
88+99+```sh
1010+ZDS_PERMISSIONED_DATA=true
1111+```
1212+1313+Until that flag is enabled, ZDS should reject permissioned-data routes as
1414+unimplemented and should not create or mutate space tables.
1515+1616+The first implementation slice wires the flag and route gate only. Even when
1717+enabled, the space methods return `MethodNotImplemented` until storage and
1818+semantics are implemented.
1919+2020+## references
2121+2222+- Discourse: <https://discourse.atprotocol.community/t/permissioned-data-pds-lexicons/879>
2323+- Branch: <https://github.com/bluesky-social/atproto/tree/permissioned-data>
2424+- Lexicons:
2525+ <https://github.com/bluesky-social/atproto/tree/permissioned-data/lexicons/com/atproto/space>
2626+- Reference implementation path:
2727+ `packages/pds/src/actor-store/space` on the permissioned-data branch
2828+2929+The Discourse post rates the lexicon shape higher than the implementation. Use
3030+the lexicons as the main contract, and use the branch implementation to
3131+understand storage and flow rather than copying it blindly.
3232+3333+Feedback should respect the research already behind the sketch and avoid
3434+overfitting to ZDS's idiosyncratic needs. The protocol is trying to cover likely
3535+use cases from large shared spaces down to small contextual ones, while leaving
3636+substantial application-specific authorization and UX in user land. ZDS feedback
3737+should name concrete implementation pressure, but frame it as design input for
3838+that broader scope.
3939+4040+## surface
4141+4242+The branch adds these `com.atproto.space.*` methods:
4343+4444+- space lifecycle: `createSpace`, `getSpace`, `listSpaces`,
4545+ `updateSpaceConfig`, `deleteSpace`
4646+- membership: `addMember`, `removeMember`, `getMembers`, `getMemberState`,
4747+ `getMemberOplog`, `getMemberGrant`, `notifyMembership`
4848+- records: `createRecord`, `putRecord`, `deleteRecord`, `applyWrites`,
4949+ `getRecord`, `listRecords`, `getBlob`, `getRepoState`, `getRepoOplog`,
5050+ `notifyWrite`
5151+- credentials and deletion fanout: `getSpaceCredential`, `notifySpaceDeleted`
5252+5353+This is not a thin wrapper around public repo writes. It is a separate
5454+space-scoped data model with its own read perimeter, write notifications,
5555+member state, record state, oplogs, and signed set commitments.
5656+5757+## storage shape
5858+5959+The reference branch uses space-scoped tables:
6060+6161+- `space`: URI, owner/member flags, app access policy, creation/deletion
6262+ timestamps
6363+- `space_member`: member DIDs and member revision
6464+- `space_record`: current records by `(space, collection, rkey)` with CID,
6565+ DAG-CBOR value, repo revision, and indexed timestamp
6666+- `space_repo`: record set hash and current repo revision
6767+- `space_member_state`: member set hash and current member revision
6868+- `space_record_oplog`: incremental record changes by `(space, rev, idx)`
6969+- `space_member_oplog`: incremental membership changes by `(space, rev, idx)`
7070+- `space_credential_recipient`: registered services to notify for writes
7171+7272+ZDS currently has one public repo namespace per DID: `records`, `repo_blocks`,
7373+`commits`, `seq_events`, `blobs`, plus account/auth state. Permissioned data
7474+should not be squeezed into those public repo tables. It needs a new
7575+space-scoped storage module, probably `src/atproto/space.zig` plus space
7676+storage helpers in `src/storage/store.zig` or a split-out storage module once
7777+the shape is clear.
7878+7979+## Zat first
8080+8181+Before implementing local primitives, check Zat's current public API. Recent
8282+Zat work is relevant:
8383+8484+- `zat.mst.Mst` has the performance-oriented middle layer ZDS already uses:
8585+ lazy CID stubs, cached clean-node CIDs, direct DAG-CBOR node serialization,
8686+ borrowed-key insertion, `collectBlocks`, and ordered `walk`.
8787+- `zat.car.streamBlocks` supports zero-allocation CAR iteration for large repo
8888+ workflows.
8989+- `zat.cbor` owns DAG-CBOR values, CIDs, encoding, and parsing helpers.
9090+- `zat.Tid`, `zat.Did`, `zat.Nsid`, `zat.Rkey`, and `zat.AtUri` own syntax
9191+ parsing and formatting for standard AT Protocol identifiers.
9292+- `zat.Keypair`, `zat.jwt`, `verifyJose`, and strict repo verification helpers
9393+ keep the JOSE-vs-repo-signature distinction explicit.
9494+- `zat.oauth` owns OAuth client/primitive helpers, while ZDS owns PDS policy,
9595+ stored grants, and route behavior.
9696+9797+Likely future Zat candidates:
9898+9999+- `SpaceUri` parsing/formatting
100100+- LtHash and set commitment primitives
101101+- member-grant and space-credential JWT helpers
102102+103103+Do not edit or patch the sibling `zat` repo from ZDS without stopping and
104104+making the proposed Zat change explicit.
105105+106106+## first milestone
107107+108108+Implement in this order:
109109+110110+1. Add the feature flag and route gating, still with no functional space
111111+ storage.
112112+2. Add a storage-only space state model and tests: create space, add/remove
113113+ member, write/delete record, update record/member oplogs.
114114+3. Add LtHash/set commitment support, using Zat if it exists by then or a small
115115+ internal module if it does not.
116116+4. Expose the smallest route slice: `createSpace`, `addMember`,
117117+ `createRecord`, `listRecords`, `getRecord`, `getRepoState`.
118118+5. Add grants, space credentials, member/repo oplog sync, and notify fanout.
119119+120120+Each step should keep permissioned data dark unless `ZDS_PERMISSIONED_DATA` is
121121+enabled.