Quick reaction to 's All data should be permissioned data and the emerging permissioned data atproto spec, in particular Richard's proposal to drop deniable crypto from the spec.

We have established that data in atproto is stored in repositories. What is an atproto repository?

At the lowest level, as far as I understand it, an atproto repo is a key-value store. The keys are record paths and the values are DRISL-encoded, lexicon-defined records with $type fields. The only thing that's different at this level with regard to public and permissioned data is the key format:

Public data

Key:

at://did:web:iame.li/place.stream.chat.message/3mpmmppz7le24

Value:

{
  "$type": "place.stream.chat.message",
  "text": "brb",
  "streamer": "did:web:iame.li",
  "createdAt": "2026-07-01T23:01:18.093Z"
}

Permissioned data

Key:

at://did:web:example.com/space/com.atmoboards.forum/streamplace-secrets/did:web:iame.li/com.atmoboards.thread/3mpr7ae3oef22

Value:

{
  "$type": "com.atmoboards.thread",
  "text": "i won't actually brb, i signed off for the day ;)",
  "createdAt": "2026-07-01T23:01:19.173Z"
}

How do I sync an atproto repository?

The atproto-savvy among you might be objecting now. No, you say, atproto repositories are Merkle trees. And you'd be right until permissioned data lands. The Merkle tree format is, until recently, how we presented atproto repositories to syncing applications. It's the format that you get over the firehose or when exporting a CAR file.

But the MST structure isn't the canonical data. Take an atproto CAR export, delete all of the Merkle tree structure, and retain all of the records themselves and the SignedCommit at the root. The MST structure you just deleted can be deterministically re-derived from what you have left and the SignedCommit will be verifiable.

I contend that in the permissioned data world, an "atproto repository" is the key-value store, and we have two ways of syncing atproto repositories:

  • "Public sync" is what we have now: organize the records as a Merkle tree, sign the root of the tree, publish the MST diff to the firehose.

  • "Deniable sync" is the new piece in the permissioned data proposal, which utilizes an LtHash set-hash digest and an HMAC to facilitate data sync in a way that doesn't allow the syncer to forward the authenticated data to anybody else.

Both sync methods drop you at the same place: you have a cryptographic proof of the contents of the repository tied to the verification key in the user's DID document.

But if you set things up this way conceptually, we now separated out the concerns of "permissioning" and "sync". Technically, there's nothing stopping you from syncing a public repository using deniable sync or a permissioned repository using public sync. Would you ever want to?

Private to Public

Streamplace is working on our "VOD Drafts" feature, allowing users to upload a video and tweak the title, description, and thumbnail before it goes live. Here's a common use case: I'd like to share this "draft video" with a few people before publishing it publicly. We can accomplish this by putting each VOD in its own space and shifting the access control over time. This yields an AT-URI like this:

at://did:web:iame.li/space/place.stream.videoSpace/3mprd6d7acd22/did:web:iame.li/place.stream.video/3mprd6d7acd22

We then grant access to an ever-expanding group of people:

  1. 1.

    I upload the video, and only I have access to this space.

  2. 2.

    Then I share it with some collaborators, so they can add subtitles in a variety of languages.

  3. 3.

    Then, before the public release, my paid subscribers get exclusive access to my new videos three days early.

All of this is easily accommodated by slowly granting ever-larger groups of people access to read the at://did:web:iame.li/space/place.stream.videoSpace/3mprd6d7acd22 space. Easy!

After this, I want to make the video public. Now things get tricky. With the current proposal, I have two bad options:

  1. 1.

    I could "move" the video to my public repository at an AT-URI like

at://did:web:iame.li/place.stream.video/3mprd6d7acd22

But now all of the likes and comments that my paid subscribers left on the original video have strongRefs to the incorrect key; no good. Not necessarily impossible to reconcile by implementing applications, but difficult and confusing; you need to hold on to two primary keys.

  1. 2.

    I could leave the video in the space, but grant read access to the space to anyone who asks. This presents two problems.

There's a scaling problem; spaces are only designed to scale to around ~1,000,000 users. I'm extremely popular, so that's not gonna work.

There's also a discoverability problem: how do the thousands of applications that index Streamplace VODs find out that I dropped a new video? It's not on the firehose; you won't hear about it unless you were already subscribed to the space.

Or we just make the space public

Here's an alternative approach: I make the space public, meaning I just start publishing the space over the public firehose. Instead of doing deniable sync with an HMAC, we just start doing public sync with a SignedCommit. Something like:

  1. 1.

    I mark the space as public somehow. My PDS starts syncing the space over the public firehose, starting with a replay of every record that has been created in it so far.

  2. 2.

    All of the other PDSses (space hosts1) that have repos in the space likewise begin publishing publicly in the same way. (They don't even necessarily need to stop doing deniable sync entirely; public sync and deniable sync arrive at same state of the same atproto repository, after all.)

  3. 3.

    The AT-URIs remain the same; all of the place.stream.like and place.stream.vod.comment records added by the paid subscribers remain valid.

  4. 4.

    All of the indexing applications -- many of which only display public data -- discover the new video exists, this solves the discoverability problem. They don't really need to care much about the fact that it's in a space, just as long as they index the record by the full space URI.

  5. 5.

    Going forward, any changes to the space repository show up over the firehose next to my public repository, and for the most part we can forget the space ever existed. It's just another atproto repo that we get over the firehose.

Sync your data your way

This is just one of a variety of use cases that open up if we treat atproto repositories as the user-sovereign base containers of atproto data and allow multiple syncing methods. Maybe there's even use cases where you could go public --> private --> public --> private. But let me know what you think!