Part of Stem. This page defines the document kind. A document is the resource people edit: its state is a graph of Change blobs, and it is the only core kind besides the space root that uses Changes. This page attaches no schema of its own: a document's metadata is typed by the library metadata schema, and a typed document narrows it with attributesSchema as described in Typed documents.
Descriptor
{
"name": "Document",
"description": "A change-based resource: metadata and a block tree edited by signed deltas.",
"state": "changes",
"schema": "hm://hyper.media/metadata",
"naming": "any",
"children": true,
"access": "inherit",
"retention": "history",
"links": [
{ "path": "/icon", "kind": "file" },
{ "path": "/cover", "kind": "file" },
{ "path": "/attributesSchema", "kind": "schema" },
{ "path": "/childAttributesSchema", "kind": "schema" },
{ "path": "/schemaDefinition", "kind": "schema" }
]
}State
The state is unchanged from today. Replaying the Change graph with the metadata CRDT and the block-tree CRDT produces the read model described in document: metadata plus content, a tree of blocks. The version of a document is its sorted head CIDs.
For a Change-based kind the handler scans the block tree itself: every hm:// link in a block or annotation becomes an embed, link or mention link according to the block type (Embed blocks and inline embeds on U+FFFC are embeds and mentions; Link blocks and link annotations are links), and every ipfs:// reference becomes a file link. The links rules in the descriptor cover only metadata fields.
Rules
A document's identity is still its genesis Change; the Node blob binds that identity to a node id. Every head in a heads target must share one genesis, and a Node blob whose heads have a different genesis than the node's earlier heads is rejected. A document is never "recreated" at an id; a new document is a new node.
Creating a document means publishing a Node blob with no id and parent set; the CID of that blob becomes the document's node id and its mutable URL is hm://<owner>/<nodeId>. The signer needs write on the parent, and may be any key holding it, not only the owner. Later Node blobs for the same node need write on the node (which write on any ancestor provides, unless a grant is exact).
Changes carry no authority of their own. A Change is applied only when a Node blob by an authorized writer names it as a head or a dependency of one. Changes from a writer whose grant was later revoked stay stored and stop being applied; they are not deleted.
naming is any: a named document appears under its parent's pretty path; an unnamed one is reachable only by its node id URL or by links. Private documents are usually unnamed.
access defaults to inherit. A document under a public parent is public unless its Node blob says own. A document under a private parent is private to the same readers unless it carries grants of its own.
retention is history: a peer that follows a document keeps every Change reachable from its heads. The signed history is what makes attribution trustless.
Today (HM24)
A document was a path in a space. Its Ref carried space, path, genesisBlob, heads, generation, redirect and visibility. The path was identity, placement and permission scope at once, which is what the HM26 decision set out to separate. Under Stem the Ref's path becomes the node's parent and name, genesisBlob and generation disappear because node ids are never reused, redirect and the tombstone shape become Node targets, and visibility becomes grants plus the access mode. attributesSchema, childAttributesSchema and schemaDefinition keep working as metadata keys and now also emit schema links.
Example
A Node blob creating a named document under a folder node (a creating blob, so it has no id; its CID, bafyreiv4seh2kvj72ceuvw75efr6edt4sywb5wkh7dnsipzz7fk4zri3r2, becomes the document's node id):
{
"type": "Node",
"signer": { "/": { "bytes": "7QHm…" } },
"sig": { "/": { "bytes": "…" } },
"ts": 1759900100000,
"kind": "hm://z6MkiAKDcRSzQ4zPZfnJcS5HYx5MwgN6MU9foHihJGrhqNBj/stem/kinds/document",
"parent": "bafyreiwyojfljooa7lqsaj2xuid5zzzzg6zdmen4khvdgajgxbenyjqwx6",
"name": "sync-protocol",
"target": { "kind": "heads", "heads": [{ "/": "bafy2bzaced…" }] }
}The same document later, written by a collaborator through a grant and moved to be unnamed and private:
{
"type": "Node",
"signer": { "/": { "bytes": "7QFo…" } },
"sig": { "/": { "bytes": "…" } },
"ts": 1759990000000,
"space": { "/": { "bytes": "7QHm…" } },
"id": "bafyreiv4seh2kvj72ceuvw75efr6edt4sywb5wkh7dnsipzz7fk4zri3r2",
"kind": "hm://z6MkiAKDcRSzQ4zPZfnJcS5HYx5MwgN6MU9foHihJGrhqNBj/stem/kinds/document",
"parent": "bafyreiwyojfljooa7lqsaj2xuid5zzzzg6zdmen4khvdgajgxbenyjqwx6",
"target": { "kind": "heads", "heads": [{ "/": "bafy2bzacee…" }, { "/": "bafy2bzacef…" }] },
"prev": [{ "/": "bafy2bzaceg…" }],
"proof": { "/": "bafy2bzaceh…" },
"access": "own"
}See also
Documents: the Change model and CRDTs, unchanged.
Placement: names, moves and pretty paths.
Resources and nodes: how heads are folded.
Privacy: what inherit and own do to readers.
Do you like what you are reading? Subscribe to receive updates.
Unsubscribe anytime