A generic endorsement lexicon
exploring applications and design
I was initially thinking how a testimonial service would fit perfectly on atproto. I usually don't read them anymore on websites because they feel fake. On atproto, verifying that a testimonial actually belongs to a person would simply require to check if a record is present in their PDS. The UI could even link to the record for quick proof.
While thinking about how to implement it, I realised a simple and generic lexicon opens the door to many other use cases: a vouching system like https://github.com/mitchellh/vouch, a product invite list, restaurant reviews or employees referral.
Vouch (A community trust management system) is interesting because it can (not the default) rely on a graph: Admin vouches for Alice who vouches for Bob. If there is a path between a user and the root, then the user can submit pull requests. I imagine an invite list for a new product could work the same way.
How does it look on atproto?
Let's start with the simplest record structure (stored in the endorser PDS)
{
"$type": "social.endorsement",
"subject": "did:plc:bandw5ruoigtuwf4q2xvqsaz"
}It already unlocks the graph traversal use case for vouch. The only constraint is the subject needs to be an atproto identifier to keep the traversal alive (as opposed to any url or external id).
For the testimonial use case, we also need to store a message.
Where should we store it?
The simplest approach is to add a new message key to the record.
{
"$type": "social.endorsement",
"subject": "did:plc:bandw5ruoigtuwf4q2xvqsaz",
"message": "I'm investing in this product so you can believe me when I say it will 10x your output."
}What about a restaurant review? It needs a message and a rating.
{
"$type": "social.endorsement",
"subject": "did:plc:bandw5ruoigtuwf4q2xvqsaz",
"message": "I wish I could give a proper rating, but the waiter never came to us.",
"rating": 1
} We can sense that different products will need different properties. Even with the same properties, the schema might also be slightly different: a message could be capped at 300 characters, a rating could be 0-3 stars or 0-5 score.
We could conclude that there is no generic enough schema for the idea to work but I believe there is still value in keeping the common part (subject connection) and extracting the specifics.
What about a custom json metadata?
{
"$type": "social.endorsement",
"subject": "did:plc:bandw5ruoigtuwf4q2xvqsaz",
"metadata": {
"message": "Chef kiss",
"rating": 5
}
}This makes the schema flexible at the cost of using an unknown type for metadata. We will quickly lose guarantee about the record shape which makes it hard to work with.
To prevent this, we can narrow the schema using a union type:
{
"metadata": {
"type": "union",
"refs": [
"#reviewMetadata",
"#testimonialMetadata",
"#recommendationMetadata"
],
"closed": true
}
}
//with
{
"defs": {
"reviewMetadata": {
"type": "object",
"required": ["rating", "message"],
"properties": {
"rating": {
"type": "integer",
"minimum": 1,
"maximum": 5
},
"message": {
"type": "string",
"maxLength": 300
}
}
}
...
}
//record
{
"$type": "social.endorsement",
"subject": "did:plc:bandw5ruoigtuwf4q2xvqsaz",
"metadata": {
"$type": "social.endorsement#reviewMetadata",
"message": "I would recommend the Hawaiian pizza but people won't trust my review.",
"rating": 5
}
}While I think it technically works, app developers have to make requests for updating the lexicon which is time consuming and creates a closed system.
Open union
A better approach is to make the union open.
Instead of:
{
"closed": true
}we use:
{
"closed": false
}Now the endorsement lexicon can define a few known metadata types if useful, while still allowing applications to provide their own.
For example, a restaurant application could own:
com.example.restaurant.reviewwhile a testimonial service could own:
com.example.testimonial.endorsementThe generic endorsement lexicon doesn't need to know either of them in advance.
A record could look like:
{
"$type": "social.endorsement",
"subject": "did:plc:bandw5ruoigtuwf4q2xvqsaz",
"metadata": {
"$type": "com.example.restaurant.review",
"rating": 5,
"message": "Chef's kiss"
}
}This gives us an interesting separation of responsibilities.
social.endorsement defines the relationship:
Alice → endorses → BobThe metadata type defines the meaning of that relationship:
Alice → reviewed → Bob
Alice → recommends → Bob
Alice → vouches for → Bob
Alice → referred → BobApplications that only care about the graph don't even need to understand the metadata.
A Vouch-like application could simply query endorsement records and follow their subject fields.
A restaurant application, on the other hand, could ignore every endorsement whose metadata type isn't its own review lexicon.
The same underlying records can therefore participate in both a generic social graph and application-specific experiences.
Would that be useful?
Are there other use case that it unlocks?
Are there existing app that would benefit from it?