Problem
Seed currently asks paths to identify documents, place them in a hierarchy, identify publications, and determine inherited permissions. Moving a document can therefore change identity, break links or require permanent redirects, and change access at the same time.
For this reason we kept our private documents implementation very simple — by not allowing them to be nested. But users obviously want to organize their documents, and they expect some kind of inherited permissions idea like in Notion or Google Drive.
First Attempt
Our initial idea was to separate stable identity of resources (nodes) from their mutable location:
A stable NodeID identifies a node within a space.
A node has one canonical parent plus an optional name within that parent.
Paths and breadcrumbs are derived from canonical locations.
Capabilities target stable nodes (not paths), and apply either to that exact node or to its whole subtree, which is the default.
The biggest issue with this approach is that in order to evaluate the hierarchy of nodes we need to check the authority first, but because we want inherited permissions, authority depends on the hierarchy. So there's a bit of a chicken-and-egg problem here.
Solution
After wrestling for a few weeks with this problem I think I finally came up with a construct that might work.
As always in software — adding another layer of indirection can help solve many problems. In this case we segregate concerns even further — we first wanted to separate document identity from their hierarchy, and now I propose to separate authority too.
The idea draws heavily from the ongoing Ink & Switch project called Keyhive. The main piece of it is Groups (of keys), and that every Document is a Group. This lets Keyhive to express transitive authority quite nicely. They call this whole idea "Convergent Capabilities" — a middle ground between pure Object Capabilities and ACL.
If you think of access control solutions as a spectrum I could describe it like this:
ACL - Convergent Capabilities - Certificate Capabilities - Object CapabilitiesI propose to do something similar — introduce a concept of a Group, which becomes the root of authority for a resource and starts a chain of capabilities that can be delegated.
Permanodes
I bring back the idea of a Permanode. I don't care what we call it though.
Permanode establishes a new Group (and also a Resource, because resources are groups too). It doesn't need to be signed, but it has to declare its owner. Permanode is useless until further signed elsewhere by the declared owner.
Roughly the model could look something like this:
type Permanode = {
nonce: Bytes
owner: PublicKey
ts: Timestamp
}
type GroupID = CID<Permanode>
type Delegate = PublicKey | GroupID
type Capability = {
// Permanode is the start of the capability chain.
// If permanode is the proof, this capability must be signed
// by the declared owner.
proof: CID<Capability> | GroupID
delegate: Delegate
access: AccessLevel
ts: Timestamp
signer: PublicKey
signature: Signature
}
// Waterfall authority from most powerful to least powerful.
// Sync level is there for the future when we have E2EE.
type AccessLevel = "admin" | "write" | "read" | "sync"
type Revocation = {
capability: CID<Capability>
proof: CID<Capability>
ts: Timestamp
signer: PublicKey
signature: Signature
}Space Group
Spaces are Groups too. For compatibility with existing data with can say that the root of the Space's Group is a deterministic Permanode that can be derived from the space's public key alone.
All existing Capability blobs can be assumed to have this Permanode in the proof field (because we didn't implement delegations yet, only space owner is able to issue caps).
Transitive Permissions
This model allows to express transitive permissions. It's important to note that we only talk about authority hierarchies for now, not the actual hierarchies of documents and names. Just the authority graph.
TODO
Do you like what you are reading? Subscribe to receive updates.
Unsubscribe anytime