made using Leaflet
whey long notes
Editor

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

see IPLD and CID and dag-cbor

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

made using Leaflet