Appearance
Diff Documents
FreshMD-LD has built-in diff authoring through its polarity system. The remove parameter in both parse() and generate() enables CRDT-style workflows, state management, and collaborative editing with human-readable diffs.
The Polarity System
Every predicate, type, and reverse link in MD-LD can be prefixed with - to retract it:
markdown
[Alice] {my:author} ← assert: Alice is an author
[Alice] {-my:author} ← retract: Alice is no longer an authorThis turns MD-LD into an append-only semantic ledger: you never delete text, you write a new entry that cancels a previous assertion.
Intra-Document Cancellation
When a retraction matches a positive triple in the same document, both cancel out — neither appears in the final graph:
markdown
[my] <tag:you@example.com,2026:>
## Article {=my:article}
[Alice] {my:author} ← added
[Alice] {-my:author} ← cancel — both disappear
[Bob] {my:author} ← this survivesResult: only my:article my:author "Bob" in quads. The remove array is empty.
External Retractions
When a retraction finds no matching positive in the current document, it becomes an external retraction stored in remove:
markdown
[my] <tag:you@example.com,2026:>
## Article {=my:article}
[Alice] {-my:author} ← external retract: targets a prior document
[Bob] {my:author}Result:
quads: containsmy:article my:author "Bob"remove: containsmy:article my:author "Alice"(targets the prior document state)
Merge Resolution
The merge() function resolves external retractions against earlier documents:
javascript
const v1 = `# Article {=ex:article .ex:Article}
[Alice] {author}
[Draft] {status}`;
const v2 = `# Article {=ex:article}
[Alice] {-author}
[Bob] {author}
[Draft] {-status}
[Published] {status}`;
const final = merge([v1, v2]);
// quads: author=Bob, status=Published
// remove: [] — all retractions resolvedThe hard invariant always holds: quads ∩ remove = empty set — a triple is never simultaneously present and retracted.
Polarity on All Annotation Forms
Retractions work on every annotation type:
markdown
## Alice {=my:alice .ex:Employee -.ex:Contractor} ← add Employee, retract Contractor
[project-x] {+my:project-x -?my:assignedTo} ← retract an object link
[my:team] {+my:team -!my:hasMember} ← retract a reverse link
{-.my:Draft .my:Published} ← change document stateThe Ledger Pattern
The real power is in append-only files — a journal, a log, a knowledge base — where every change is a new entry:
markdown
[my] <tag:you@example.com,2026:>
[j] <my:journal/>
# 2026-05-01 — started project {=j:2026-05-01 .j:Entry j:date ^^xsd:date}
Project status: [planning] {+my:project ?my:documents ?j:status}
# 2026-05-10 — project kicked off {=j:2026-05-10 .j:Entry j:date ^^xsd:date}
{=my:project}
[planning] {-my:status}
[active] {my:status}Merging these two entries produces my:project my:status "active" only. The history of the change lives in the text; the graph holds only the current truth.
Generating Diff Documents
Use generate() with a remove parameter to produce a diff document:
javascript
import { generate, DataFactory } from 'mdld-parse';
const { namedNode, literal } = DataFactory;
// Current state
const quads = [
DataFactory.quad(
namedNode('http://example.org/doc'),
namedNode('http://example.org/author'),
literal('Bob')
)
];
// What to retract
const remove = [
DataFactory.quad(
namedNode('http://example.org/doc'),
namedNode('http://example.org/author'),
literal('Alice')
)
];
const { text } = generate({
quads,
remove,
context: { ex: 'http://example.org/' }
});
console.log(text);
// [ex] <http://example.org/>
//
// # doc {=ex:doc}
// [Bob] {ex:author}
// [Alice] {-ex:author}Version Control Workflow
Full version control workflow using MD-LD's diff system:
javascript
import { parse, generate, merge } from 'mdld-parse';
// Version 1
const v1Text = `
[ex] <http://example.org/>
# Document {=ex:doc .ex:Article}
[Alice] {ex:author}
[Draft] {ex:status}
`;
// Version 2 — corrections
const v2Text = `
[ex] <http://example.org/>
# Document {=ex:doc}
[Alice] {-ex:author}
[Bob] {ex:author}
[Draft] {-ex:status}
[Published] {ex:status}
`;
// Merge to get final state
const merged = merge([v1Text, v2Text]);
console.log(merged.quads);
// Final state: author=Bob, status=Published
// All v1 assertions that were retracted in v2 are resolvedCollaborative Editing
CRDT-style collaborative editing:
javascript
// User A's document
const userA = `[ex] <http://example.org/>
# Task {=ex:task-1 .ex:Task label}
[Alice] {+ex:alice ?ex:assignee}
[high] {ex:priority}`;
// User B's corrections (applied after User A)
const userB = `[ex] <http://example.org/>
# Task {=ex:task-1}
[Alice] {+ex:alice -?ex:assignee}
[Bob] {+ex:bob ?ex:assignee}
[low] {-ex:priority}
[medium] {ex:priority}`;
const final = merge([userA, userB]);
// Result: task assigned to Bob, priority=mediumInline Correction Pattern
Corrections can appear in narrative prose:
markdown
[ex] <tag:carol@example.org,2026:>
New student [Alice] {=ex:new-student .prov:Person ex:name} is in our [class] {+ex:my-class !member}.
**Correction:** [Her] {=ex:new-student} name is not [Alice] {-ex:name}, it's [Ellie] {ex:name}.
**Further correction:** I asked her directly — she does not know [him] {+ex:bob -?ex:knows}.After generate(parse({text})):
markdown
[ex] <tag:carol@example.org,2026:>
# Ellie {=ex:Ellie .prov:Person label}
[Ellie] {ex:name}The generated document contains only the final resolved state — corrections applied, retractions removed.
Type Migration
Use retractions to change types:
markdown
## Alice {=my:alice .ex:Employee -.ex:Contractor}This adds ex:Employee and retracts ex:Contractor in a single annotation. If ex:Contractor was asserted in a prior document, it will be placed in remove. If it was asserted earlier in the same document, both cancel and neither appears in the output.
Querying Retractions
javascript
const result = parse({ text: myDocument });
// Current state
console.log(result.quads);
// What this document retracts from prior state
console.log(result.remove);
// Apply current retractions against a prior state
const priorQuads = [...];
const currentState = priorQuads.filter(priorQuad => {
return !result.remove.some(retractedQuad =>
retractedQuad.subject.value === priorQuad.subject.value &&
retractedQuad.predicate.value === priorQuad.predicate.value &&
retractedQuad.object.value === priorQuad.object.value
);
});