Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions public/contracts/artifacts/contracts/mdbase.comment/1.0.0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
---
kind: mdbase.contract
contract_type: record
id: mdbase.comment
version: 1.0.0
name: Comment
description: Comments, replies and suggested edits anchored to text in Markdown records.
record_schema:
dialect: json-schema-2020-12
ref: ../../schemas/mdbase.comment/1.0.0.schema.json
---

# Comment

A comment is its own record, never markup inside the commented record. The
commented record's Markdown stays exactly as its author wrote it, and comments
can be queried, linked to and validated like any other record. The comment's
Markdown body is its text; a link to a person in that text is a mention.

## Threads

The first comment of a thread has no `in_reply_to`. It carries the thread's
anchor (`target`) and state (`status`, `resolved_by`, `resolved_at`). Each reply
is a separate record whose `in_reply_to` links to that first comment, so two
people replying at once write two records and never conflict. Replies repeat
the thread's `document`. Order a thread's replies by `created_at`.

Refer to records only with ordinary mdbase links: `document` to the commented
record, `in_reply_to` to the first comment, `created_by` and `resolved_by` to
records implementing `mdbase.person`. Implementing types declare these fields in
`collection.links` so rename reference updates keep them current. Never refer
to an author by name, email or account subject.

## Anchors

`target.quote` is the source of truth: `exact` is the anchored text and
`prefix` and `suffix` are the text around it. `target.text_position` records
where the quote was in one revision of the body, in Unicode code points of the
record's Markdown body as stored, after its frontmatter (profile
`markdown-body`). `basis.hash` is `sha256:` and the hex SHA-256 of that body's
UTF-8 bytes.

To find a thread's text:

1. When the body at the recorded offsets is still `exact`, use them.
2. Otherwise search the body for `exact`, preferring the occurrence whose
surrounding text best matches `prefix` and `suffix`, then the one nearest
the recorded offsets.
3. When `exact` is not found, the thread is detached. Show it apart from the
text rather than guessing a place. A detached thread is not an error.

An empty `exact` is an insertion point between `prefix` and `suffix`. A thread
without a `target` is about the whole record. A consumer may rewrite
`text_position` after finding the quote elsewhere; it must not change `quote`.

## Suggested edits

A comment with `motivation: editing` suggests replacing the target's quote with
`suggestion.replacement`; an empty replacement suggests deleting it. Accepting
a suggestion first finds the quote as above and must refuse, leaving the
thread open, when the quote cannot be found exactly. It then replaces the
quote in the commented record and resolves the thread with
`suggestion.outcome: accepted`. Rejecting resolves it with `rejected`.

## Lifecycle

A comment whose `document` no longer resolves is kept, not deleted: records
can be restored, and the discussion is still collection data. Consumers may
hide such comments. Withdrawing a comment sets `deleted_at` and empties its
body rather than deleting the record, so replies to it keep their thread.

Authorship links are editable collection data with the same trust as any other
field. They never prove who wrote a comment or grant anyone access.
26 changes: 26 additions & 0 deletions public/contracts/artifacts/contracts/mdbase.view/1.0.0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
kind: mdbase.contract
contract_type: record
id: mdbase.view
version: 1.0.0
name: Saved view
description: Shared query scope and stable named views executable through the Query profile.
record_schema:
dialect: json-schema-2020-12
ref: ../../schemas/mdbase.view/1.0.0.schema.json
---

# Saved view

A record exposed through `mdbase.view` stores shared query scope and one or more
stable named views. Each named view resolves to the query model in Chapter 11.
Optional `presentation` metadata is advisory and does not alter headless query
results.

Implementing this contract is how a collection declares which of its records
are saved views. A tool that advertises `view_records` discovers and executes
views through this contract's type implementations, not through a reserved type
name, path, or frontmatter value.

This artifact is passive. Implementing it grants no authority to read, execute,
or modify any record.
36 changes: 36 additions & 0 deletions public/contracts/artifacts/contracts/obsidian.base/1.0.0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
---
kind: mdbase.contract
contract_type: record
id: obsidian.base
version: 1.0.0
name: Obsidian Base
description: An Obsidian Bases saved-view source stored as a YAML document record.
record_schema:
dialect: json-schema-2020-12
value:
$schema: https://json-schema.org/draft/2020-12/schema
type: object
required: [views]
properties:
filters: {}
formulas: { type: object }
properties: { type: object }
views:
type: array
minItems: 1
items: { type: object, required: [type], properties: { type: { type: string }, name: { type: string } } }
---

# Obsidian Base

A record exposed through `obsidian.base` is an Obsidian Bases source: global
filters, formulas, property metadata, and one or more views. The schema is
deliberately permissive. Obsidian owns the format and adds keys over time;
unknown keys are preserved and ignored.

Tools that advertise `obsidian_bases_views` discover and execute these records
through this contract and evaluate them with the Obsidian Bases expression
dialect described in the [Obsidian Bases adapter](https://mdbase.dev/spec/adapters/obsidian-bases).

This artifact is passive. Implementing it grants no authority to read, execute,
or modify any record.
229 changes: 229 additions & 0 deletions public/contracts/artifacts/schemas/mdbase.comment/1.0.0.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://mdbase.dev/contracts/schemas/mdbase.comment/1.0.0.schema.json",
"title": "Comment",
"description": "A comment, reply or suggested edit on a Markdown record, stored as its own record. The comment's Markdown body is its text.",
"type": "object",
"required": [
"document",
"created_at"
],
"properties": {
"document": {
"type": "string",
"pattern": "\\S",
"description": "Link to the commented record, such as [[chapters/method]]. Replies repeat their thread's document so one query finds a record's whole discussion."
},
"in_reply_to": {
"type": "string",
"pattern": "\\S",
"description": "Link to the first comment of the thread this comment replies to. Absent on the first comment of a thread."
},
"motivation": {
"enum": [
"commenting",
"replying",
"editing"
],
"default": "commenting",
"description": "commenting starts a thread, replying answers one, and editing suggests replacing the target's text."
},
"target": {
"type": "object",
"description": "Where in the document the thread is anchored. Absent means the whole record. Only the first comment of a thread has a target.",
"properties": {
"quote": {
"type": "object",
"description": "The anchored text and the text around it. The quote is authoritative; positions are a hint.",
"required": [
"exact"
],
"properties": {
"exact": {
"type": "string",
"description": "The anchored text exactly as it appears in the record body. Empty for an insertion point, which prefix or suffix then locates."
},
"prefix": {
"type": "string",
"description": "Text immediately before exact, to tell repeated occurrences apart."
},
"suffix": {
"type": "string",
"description": "Text immediately after exact, to tell repeated occurrences apart."
}
},
"if": {
"required": [
"exact"
],
"properties": {
"exact": {
"type": "string",
"maxLength": 0
}
}
},
"then": {
"anyOf": [
{
"required": [
"prefix"
],
"properties": {
"prefix": {
"type": "string",
"minLength": 1
}
}
},
{
"required": [
"suffix"
],
"properties": {
"suffix": {
"type": "string",
"minLength": 1
}
}
}
]
},
"additionalProperties": false
},
"text_position": {
"type": "object",
"description": "Offsets of the quote in one revision of the record body.",
"required": [
"basis",
"unit",
"start",
"end"
],
"properties": {
"basis": {
"type": "object",
"required": [
"profile",
"hash"
],
"properties": {
"profile": {
"const": "markdown-body",
"description": "Offsets count the record's Markdown body as stored, after the frontmatter, so frontmatter edits never move them."
},
"hash": {
"type": "string",
"pattern": "^sha256:[0-9a-f]{64}$",
"description": "SHA-256 of the UTF-8 body the offsets were measured in."
}
},
"additionalProperties": false
},
"unit": {
"const": "unicode_code_point"
},
"start": {
"type": "integer",
"minimum": 0
},
"end": {
"type": "integer",
"minimum": 0
}
},
"additionalProperties": false
}
},
"required": [
"quote"
],
"additionalProperties": false
},
"suggestion": {
"type": "object",
"description": "The suggested edit of an editing comment: replace the target's quote with replacement.",
"required": [
"replacement"
],
"properties": {
"replacement": {
"type": "string",
"description": "The text to put in place of the quote. Empty suggests deleting it."
},
"outcome": {
"enum": [
"accepted",
"rejected"
],
"description": "What happened to the suggestion once its thread was resolved."
}
},
"additionalProperties": false
},
"status": {
"enum": [
"open",
"resolved"
],
"default": "open",
"description": "Whether the thread is still open. Only meaningful on the first comment of a thread."
},
"resolved_by": {
"type": "string",
"pattern": "\\S",
"description": "Link to the mdbase.person record of who resolved the thread."
},
"resolved_at": {
"type": "string",
"format": "date-time"
},
"created_by": {
"type": "string",
"pattern": "\\S",
"description": "Link to the mdbase.person record of the comment's author. Absent when the author has no person record."
},
"created_at": {
"type": "string",
"format": "date-time"
},
"modified_at": {
"type": "string",
"format": "date-time"
},
"deleted_at": {
"type": "string",
"format": "date-time",
"description": "When the comment was withdrawn. The record stays so its thread and replies keep their shape; its body should be emptied."
}
},
"allOf": [
{
"if": {
"properties": {
"motivation": {
"const": "editing"
}
},
"required": [
"motivation"
]
},
"then": {
"required": [
"target",
"suggestion"
],
"properties": {
"target": {
"type": "object"
},
"suggestion": {
"type": "object"
}
}
}
}
],
"additionalProperties": false
}
Loading
Loading