permissioned data proposal 94 notes #
Historical design notes: credential and membership examples below predate the October 1, 2026 alpha. Use permissioned data for the current ZDS contract.
These notes track Bluesky proposal PR #94 from the perspective of ZDS. They are not implementation instructions and should not be treated as a stable protocol contract. The proposal is a draft; use this page to reduce wrong-way risk while keeping ZDS's current permissioned-data work clearly experimental.
PR: https://github.com/bluesky-social/proposals/pull/94
working posture #
The original exploration treated this surface as a sandcastle without external production users. That assumption no longer describes the deployment: consumers need coordinated migrations for breaking changes. Avoid deepening older experimental shapes when the proposal has already moved away from them.
Prefer small alignment work that preserves optionality:
- keep the API reference and operator docs explicit that this is experimental
- isolate prototype code under the existing
spaceand permissioned-data modules - add tests around durable semantics that are likely to survive
- avoid adding new member-list behavior or resurrecting old credential names
ZDS's immediate goal is modest: get ahead of the likely direction enough to store private data on the user's PDS, while keeping the implementation easy to reshape. Do not write code or docs as if the draft is settled.
recent design pressure #
On 2026-06-24, Richard Barnes pushed back publicly on the proposal's broad scope. His useful objection is that "permissioned data" appears to combine several product shapes that can have different needs:
- personal data: bookmarks, mutes, drafts
- gated content: paid newsletters, subscriber-only posts
- socially shared data: private posts and stories
- groups: private forums, communities, group chats
He also questioned the cost of creating a parallel data universe and called out URI-scheme complexity. This is worth tracking even if the thread did not fully engage Daniel's earlier long-form diaries. Richard has done foundational protocol/security work, so treat the critique as meaningful design pressure rather than as implementation guidance.
Eli Mallon pointed back to the diary motivation: for permissioned data, "rebroadcastability" is an antipattern. That is the core distinction that makes this more than ordinary public repo records with app-level filtering. If a reader can freely rebroadcast the same bytes into the public sync graph, the privacy boundary has already failed.
Daniel's reply clarified the current bet: the motivating use case is groups,
and the proposal tries not to be dogmatic about one protocol per product
category. If the group-shaped primitive is expressive enough for the simpler
cases without becoming overwrought, that is a reasonable design win. He also
said he was leaning back toward keeping at. Proposal 0016 now uses canonical
at:// URIs with a literal space path marker, and ZDS follows that shape.
ZDS implications:
- treat the
at://{authority}/space/{type}/{skey}shape as the current experimental foundation, without maintaining the superseded scheme as an alias - keep private personal data as the motivating local use case, not a claim that ZDS has solved groups
- do not encode a universal group model into ZDS while the proposal is still deliberately pushing group semantics up to applications/space hosts
- preserve the ability to store private records and blobs now, but keep the protocol surface clearly experimental
broad matches #
The current ZDS implementation broadly matches these proposal instincts:
- permissioned data is separate from public repo data
- permissioned records use an
at://address with aspacemarker that names space authority, space type, space key, writer repo, collection, and record key - one writer has one permissioned repo per space
- record sets are represented with LtHash-style set commitment state
- general reader/group semantics should not be encoded as a universal protocol member list
- apps that receive access to a space should expect whole-space read semantics, not just access to the authorizing user's own writer repo
proposal alignment points #
These are the main design points ZDS tracks against proposal 0016 and its follow-up pull requests:
- Space declarations are lexicon definitions, not records. That appears to make a space type behave like a collection/schema authority: one NSID-shaped path declares a reusable space type that many space repos, accounts, spaces, and apps can share. This trades away the direct repo-authority feel of records in favor of namespace authority, fallback behavior, and a single path segment. App designers or project maintainers may publish the initial declaration using DNS and lexicon-publishing authority, but the declaration becomes a reusable commons resource after that. Lexicon unpublishing also should not break existing spaces that depend on the declaration, which makes lexicon resolution override behavior appropriate in a way that would be surprising for normal AT records.
- ZDS should use the proposal names directly:
getDelegationTokenfor the PDS-issued OAuth query, andlistRepoOpsfor incremental permissioned-repo sync. com.atproto.space.listReposis implemented as a space-credential-only view of the authority's writer registry.notifyWriteadvances that registry with the writer's revision and SHA-256 digest of its LtHash state; raw LtHash state remains private to the writer's repo host.registerNotifystores an expiring whole-space registration for a service DID, andunregisterNotifyremoves it idempotently. ZDS follows the branch's 24-hour registration window.- Delegation tokens identify a requesting user and contain no app identity.
Optional app identity arrives separately as a verified client attestation.
Credential issuance and use follow PR #99's ES256 DPoP binding: the
credential carries
cnf.jktand an attestedclient_idwhen present. - ZDS currently uses the proposal's permitted
#atprotosigning-key fallback. For remote authorities it resolves#atproto_space_hostwhen present and falls back to#atproto_pds; ZDS does not yet publish dedicated space key or host entries for its own resident authorities. - Commit state uses versioned deniable commits: random
ikm, a signature over(space, author, rev, ikm), and PR #100's direct HKDF-Expand-derived HMAC over the record-set hash. The CAR record index uses canonical DAG-CBOR map ordering. Wire bytes use AT JSON$bytesvalues. - OAuth
space:scopes distinguishreadfrom collection-constrainedread_self, useauthority=, and separate lifecycle/admin capability with repeatedmanage=parameters. Permission sets usespaceType, may name cross-namespace collections, and resolveauthority=selfat grant issuance. - Delegation tokens and verified client attestations are short-lived, single-use inputs consumed atomically when minting a space credential.
listBlobsenumerates the permissioned repo's referenced blob CIDs for full or revision-filtered sync;getBlobremains the authenticated byte path.- Space deletion semantics need review. The draft distinguishes space-authority deletion from member repo ownership and does not imply arbitrary erasure of every writer's local data.
implementation traps #
These are low-level details to keep visible before attempting parity work:
- LtHash live-record elements must stay unique. The draft shape uses
{collection}/{rkey}/{record_cid}so the same live element is not added more than once. - Byte order matters. LtHash lanes are little-endian, while the TLS-style length-prefixed context used around signed commit material is big-endian.
- The operation log is a sync shortcut, not permanent history. A host may
compact or drop it; syncers compare set hashes and recover from the full
two-root CAR returned by
getRepo. - Permissioned sync is relay-less and pull-based. Real-time behavior comes from write notifications through the space authority, but correctness cannot rely on every notification arriving.
- Space types should be particular. Avoid designing one universal community space that gives every app access to every modality; apps can group many narrower spaces under a community authority at the application layer.
export and backup pressure #
The current proposal defines com.atproto.space.getRepo for full-state
recovery. This is not the public MST repo shape: the CAR roots are a deniable
signed commit and a DAG-CBOR map from collection/rkey to record CID. Record
blocks follow in the same lexicographic order, while blobs are fetched
separately. Account-wide backup still has to enumerate the resident's spaces
and export each writer repo; the proposal does not define one aggregate backup
archive.
For ZDS, avoid inventing a getSpaceRepo endpoint unless the proposal moves
that way. The safer design pressure is exportability:
- residents should be able to enumerate their own permissioned spaces
- residents should be able to enumerate their own writer repos and records inside those spaces
- backup/export can be a higher-level artifact, such as a manifest plus records grouped by space
- blobs should probably be referenced or streamed separately from record export instead of assuming one giant blob archive is always practical
- any future export path should be resumable or easy to retry for large media sets
This is separate from sync. Sync methods need protocol parity; backup/export can be an operator or resident affordance built from stable lower-level primitives.
simplespace surprise #
PR #94 removes the protocol-level member list as a universal authorization
substrate, but it also sketches a required PDS baseline called
com.atproto.simplespace.*.
That baseline currently includes:
createSpaceupdateSpacedeleteSpaceaddMemberremoveMemberlistMembers
It also introduces simple policies such as public, member-list, and
managing-app, plus a managing-app access check hook.
This is not the same as reviving the older generic member-list experiment. ZDS now treats it as a small baseline space management API while still leaving richer application/group semantics to apps.
Some commentary describes the boring access model as a list of DIDs. In ZDS
terms, do not read that as permission to restore the old protocol member-list
sync surface. Treat it as space-management policy, especially for
com.atproto.simplespace.*, including the service-authenticated
checkUserAccess policy hook used during managing-app credential minting.
Implementation note: ZDS exposes the proposal namespace directly and does not
carry the old com.atproto.space.createSpace / updateSpaceConfig /
deleteSpace lifecycle surface. HappyView keeps compatibility aliases for its
own older namespace; ZDS intentionally does not.
useful tests before more surface #
Before adding more endpoints, prefer tests that exercise durable semantics:
- per-space and per-writer repo isolation
- canonical permissioned
at://parsing, formatting, and stored-row migration - OAuth scope gating for authority reads, self reads, writes, and management
- ranged blob reads through permissioned-data auth
- write oplog ordering and cursor behavior
- record-set commitment changes across create, update, and delete
- deletion behavior for authority spaces versus writer repos
These tests should stay separate from public-repo conformance tests so the experimental surface can move without muddying stable PDS behavior.