Draft
Mattress is a Composable & Social Framework for a Global RDF Graph
motivation
RDF provides a universal model for describing data but lacks social trust primitives. ATProto provides authenticated, content-addressed records but lacks a general-purpose graph model. Mattress unifies the two through an explicit separation between opinion (SID), content (CID), and concept (OID)
There are several specifications that make up the Mattress framework:
m.triple
atomic social triples. one triple per document. the full spec is as below
Example:
// at://did:example/party.whey.mattress.triple/1 { "$type": "party.whey.mattress.triple", "s": "mattress://did:example/party.whey.mattress.node/123", "p": "http://example.org/vocab/age", "o": { "$type": "party.whey.mattress.triple#literal", "value": "42", "datatype": "http://www.w3.org/2001/XMLSchema#integer" } }
Compiles down to:
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> . @prefix xsd: <http://www.w3.org/2001/XMLSchema#> . @prefix mtr: <https://mattress.whey.party/ns/triple#> . # reified record <at://did:example/party.whey.mattress.triple/1> a mtr:Triple ; rdf:type mtr:Triple ; rdf:subject <mattress://did:example/party.whey.mattress.node/123> ; rdf:predicate <http://example.org/vocab/age> ; rdf:object "42"^^xsd:integer . # the triple itself <mattress://did:example/party.whey.mattress.node/123> <http://example.org/vocab/age> "42"^^xsd:integer .
Typelex Schema:
import "@typelex/emitter"; namespace party.whey.mattress.triple { /** Record declaring an RDF triple */ @rec("tid") model Main { @required @readOnly $type: string ="party.whey.mattress.triple", /** Subject of triple, must be IRI (no blank nodes) */ @required s: string, /** Predicate of triple, must be IRI */ @required p: string, /** Object of triple, must either be IRI or Literal (no blank nodes) */ @required o: (Literal | Iri), } model Literal { /** Lexical Literal */ @required @readOnly $type?: string = "party.whey.mattress.triple#literal", @required value: string, @required datatype: string, lang?: string, } model Iri { @required @readOnly $type?: string = "party.whey.mattress.triple#iri", @required value: string, } }
m.node
it is just an unordered list of canonized triples
thats it
the Merkle Dag is a compiled Content-Addressed version of the relevant RDF subgraph. it optionally includes nodes for actually finding these node/triple declarations by URI
Example:
// at://did:example/party.whey.mattress.node/885 { "$type": "party.whey.mattress.node", "@id": "mattress://did:example/party.whey.mattress.node/123", "map": { "mattress://did:example/party.whey.mattress.triple/1": { // Triple "mtsid": "at://did:example/party.whey.mattress.triple/1", // social current version "mtcid": "bafy...", // Triple's object "mnsid": "at://did:example/party.whey.mattress.node/456", // social current version "mncid": "bafy..." }, "mattress://did:example/party.whey.mattress.triple/2": { // SIDs are optional "mtcid": "bafy...", "mncid": "bafy..." } } }
Compiles down to (here it uses RDF-star. you can use reifications if you want to)
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> . @prefix xsd: <http://www.w3.org/2001/XMLSchema#> . @prefix mnd: <https://mattress.whey.party/ns/node#> . @prefix mtr: <https://mattress.whey.party/ns/triple#> . @prefix cid: <https://ipld.io/cid/> . # Node record (the reified social document) <at://did:example/party.whey.mattress.node/885> a mnd:Node ; rdf:subject <mattress://did:example/party.whey.mattress.node/123> ; mnd:asserts <mattress://did:example/party.whey.mattress.triple/1> ; mnd:asserts <mattress://did:example/party.whey.mattress.triple/2> . # For each entry in `map`, attach metadata *to the assertion itself* << <at://did:example/party.whey.mattress.node/885> mnd:asserts <mattress://did:example/party.whey.mattress.triple/1> >> mnd:tripleSid <at://did:example/party.whey.mattress.triple/1> ; mnd:tripleCid <cid:bafy...> ; mnd:objectSid <at://did:example/party.whey.mattress.node/456> ; mnd:objectCid <cid:bafy...> . # alternate representation thats smaller and doesnt use RDF-star <at://did:example/party.whey.mattress.node/885#mattress://did:example/party.whey.mattress.triple/2> mnd:tripleCid <cid:bafy...> ; mnd:objectCid <cid:bafy...> .
Typelex:
import "@typelex/emitter"; namespace party.whey.mattress.node { /** Declaration of an RDF Node Document */ @rec("tid") model Main { /** the node that this node claims to be (might be self, might not) */ @required "@id": string, @required @readOnly $type: string ="party.whey.mattress.node", /** maps triple ids to strong triple pointers */ @required map: unknown /** * map: { * [string]: { * // Triple * "mtsid": string, // social current version * "mtcid": cid, * // Triple's Object * "mnsid": string, // social current version * "mncid": cid, * }, * } */ } }
(OID) mattress:// URIs
unlike WikiData, Mattress cant centrally declare a namespace for opaque identifiers, so we reuse at:// uris, but under a different URI scheme to denotate its different purpose. It is simply a namespaced global identifier (like NSIDs) but it also serves another multi purpose, it enables direct dereferencing to the host-authority for the limited use cases where this is needed.
ofc it does mean its tied to the authority's host existence if you do use it this way, but hey you dont have to! you can imagine this is just an opaque global namespaced identifier, and you can safely ignore its other use
i am debating over opening mattress:// uris for anything and not just at:// uris. if this is done, then the example mattress:// uris would look like this ->
mattress://at://did:example/party.whey.mattress.triple/123
(SID) host-authoritative IDs
your standard dereference-able IRI. its purpose is to be a mutable target. not much else but it is important because CIDs arent easily dereferencable unless you got the entire graph on your hands or you use IPFS (which i might)
i explicitly support atproto aturis because theyre cool but http IRIs works too if you need it
https://bob.com/resource/whatever at://did:example/party.whey.mattress.triple/123
(CID) Content-Address or hash
both Mattress Triple and Node assertions are using the m.triple and m.node json schema respectively but is also referencable by hash. the data structure of m.triple and m.node is built for canon form (unlike JSON-LD) so the hash should be stable. in addition, the JSON canonization scheme used is the DAG-CBOR serialization of JSON (IPLD) because then we can reuse atproto infra and the excellent hash format that is CIDs
bafyreiaty6q2fybubhegbs4hjjp75bq2umpy4te2vghv2ngvedzetokote