Habitat & permission spaces for organizations

At Habitat, we're building a data ownership layer for organizations. Over the last couple of weeks, we've been hard at work studying the permission-ed spaces design, and slowly porting over our previous permission-ed data implementation ( cliques !) to spaces. We're fans of the design that the team has put out, and the level at which the protocol is opinionated vs. leaves up to developers. In this Leaflet, I'll be talking about what Habitat's constraints for permission-ed data are and what layers we intend to build on top of the basic spaces protocol.

Organizational permissions

Organizational / enterprise / business permissions have different shapes and requirements than in the big-world social case. Luckily, the spaces protocol is low-level enough that we can build for these requirements on top of it.

There's been a lot of talk in the ecosystem about a shared spec for communities or groups on permission-ed spaces, such as Roomy's Arbiter spec. In terms of organizational use-cases, this sort of thing is non-negotiable: you need some concept of teams and roles that remain stable across different products. This is not the case today--some products might try to keep in sync with a Google email group list, but typically each product (e.g. Figma, Notion, Asana) has it's own bespoke way of defining teams and organizational permission-ing. These routinely get out of sync (who hasn't had to wrangle a new team member's permissions in every single app) and are often maintained in an ad-hoc fashion, rather than by a central process within an organization.

Habitat is also solving for the needs of developers that will build products on top of our platform. For example, in our document editor , we want to support richer permissions than "in the doc" or "not in the doc". We'd like to support owner / editor / viewer / commenter roles on a doc: these should be application-defined and translated into spaces under the hood. These roles might also inherit from each other (if you can edit, you can comment), and ideally would be interoperable, so another docs product could fetch, understand, and reuse these roles in their product. Permissions is one of the most complex things to get right (coming from Figma/Glean, we'd know!), so the more primitives Habitat can get right here, the more developers can focus on delivering the best product experience. At a super rough level, what we are thinking this would look like are XRPCs for product developers to define roles on entities, likely modeled with OpenFGA relationships. These relationships can then be exposed as records within spaces for interoperability with other apps.

On the other hand, solving for the needs of organizations & communities which already have a governance structure in place simplifies other things for us. For example, for all spaces defined in Habitat, the organization gets to be the space host. How the organization governs the space is up to them, and we can offer some helpful defaults, possibly with the help of managed DIDs. There's also a higher level of trust between spaces and apps requesting space credentials: in the proposed permission-ed spaces designed, there's a tradeoff between interoperability (can any app read from a space) and privacy (if i write a record into a space, any app that can read the space can see it, including ones i might not want). In an organizational setting, apps are typically approved by IT or HR admins and need to satisfy basic security and privacy policies, so spaces can be interoperable with the knowledge that there is a review process for apps that read from them.

Building on Habitat

At its core, Habitat is a platform, and we want to empower people to build for themselves, so our platform should be agnostic to the types of products or applications that can be built on top, similar to AT protocol's relationship to the ATmosphere. The first couple groups building on Habitat are building custom software for their own communities, and us ourselves are just getting into thinking about our first workplace product built on top of Habitat.

This means that our developer platform must be excellent, much like AT protocol. Here, I want to talk about a couple of key things we're building to extend AT protocol for Habitat's developer platform.

But first, a bit of bike-shedding to set some context. It feels like the term "PDS" is a bit overloaded at the moment because we often refer to it as if it was 1:1 with a repo (for example, we say "my PDS" vs "your PDS" when technically speaking our repos may very well be on the same PDS). This fact doesn't really matter for the ATmosphere broadly, since everything needs to work the same regardless of whose repo is on which PDS, including setups like self-hosted repos. But for Habitat, organization member repos are always hosted behind the same server, or PDS, since an organization fully owns member repos. In other words, by design, all of an organization's data is hosted (conceptually) on one server. We can make use of this distinction to offer helpful developer APIs.

Because all the repos live on one PDS (or ODS, an Organizational Data Server, if you will), we can offer convenience functions for things like backfilling and realtime updates across a whole organization (which today happens via per-user / per-repo endpoints). Today, in public AT protocol, relays aggregate across repos via calling /xrpc/com.atproto.repo.subscribeRepos, and individually ask every user for updates. AppViews talk to relays or the jetstream in order to avoid that aggregation work themselves. Spaces offer a similar design, where syncers must independently aggregate all the data in order to maintain an up to date view of the space. However, in Habitat, since all member repos are on the same PDS, we can offer a single endpoint /xrpc/com.habitatproto.spaces.subscribeSpaces which functions like a built-in relay to our PDS, aggregating updates across the entire organization.

To implement authorization for this, we give each org an organizational DID, and introduce the concept of an org credential, which functions like an OAuth token for the whole org. Because every space within an org has the org DID as its space host, this OAuth token can be presented to /xrpc/com.habitatproto.spaces.subscribeSpaces to get an event stream for the entire space. It's a normal OAuth token with scopes similar to user OAuth tokens in the rest of AT protocol--it can request scopes for specific lexicons or space types, but we can also extend it to request specific repos.

The backfill mechanisms in permissioned spaces (getRepoOpLog, listSpaces, listRecords in the permissioned spaces proposal) work almost the same in Habitat. In permissioned spaces, for authentication, these endpoints support either a user's personal OAuth token or a space credential. Other users' OAuth tokens can't be supported because a repo can't validate the token if it's from another PDS and can't validate the membership if the space host is on another PDS. In our case, since everything about a given space is on the same PDS, we can read across spaces and repos with any OAuth token issued by the PDS. Since an org credential is just an OAuth token, syncing clients can use it to crawl the entire organization. Apps built on top of Habitat will have an authorization flow where admins of the org approve and issue an org credential that the app uses to sync relevant data.

To better support event updates and backfills, we are building Sap (named after Tap). Sap calls subscribeSpaces, and broadcasts all the writes happening in the org with additional permissions metadata events. It will emit flattened permissions so apps can build their own replicated index of permissions. Additionally, if apps want to resolve whether a user has permission to a space or how a user got permission to a space (what we're using OpenFGA for under the hood), they can query that directly through our API and the relationship records we store to keep it interoperable.

We haven't explored this quite yet, but it seems like there are some consequences here for enabling local-first applications on top of Habitat because aggregation across user repos has a much simpler model. Eventually we may even bring back our syncing mechanism from cliques across PDS'es but for the org use-case, so orgs can sync data with each other without a third party server.

Questions, comments & concerns

This was quite a write up, and everything I wrote here is under active development. We'll be getting a proper spec and documentation into place, and appreciate any feedback or questions on the design until then!

-Habitat team