~/blygger.org/spec/0.1/

The Blygger Protocol — Version 0.1

Status: DRAFT. This document specifies protocol version 0.1 at conformance Level 1 (the publish side). It becomes stable when the v0.1 reference deployment ships, and frozen at protocol 1.0. Until then, breaking changes are permitted but must bump the manifest version and be recorded in the project devlog.

The key words MUST, MUST NOT, SHOULD, SHOULD NOT, RECOMMENDED, MAY are to be interpreted as described in RFC 2119.


1. Introduction (non-normative)

Blygger is a decentralized public writing medium built on static files and RSS. A blygg is a directory of files — mounted anywhere on the publisher's own domain, at a path (conventionally /blyg/) or at the domain root — containing versioned items of writing, a manifest, an archive index, and an RSS feed. Anything that can serve files can serve a blygg; anything that can read RSS can follow one.

Four design invariants shape everything below:

  1. The protocol is a static file contract. It governs only the published artifact — the page. How the publisher composes, edits, imports, or generates content (the studio) is entirely out of scope.
  2. Every blygg feed is a valid RSS 2.0 feed whose items carry self-contained HTML. A plain RSS reader always sees a sensible microblog.
  3. AI is never in the protocol. Generation, if any, happens in the studio at authoring time; the page publishes output plus provenance. Readers need no models or keys.
  4. Identity is never in the protocol. The only authenticated entity is the publishing client at its domain (the origin). DNS is the namespace.

The protocol has two planes. The state plane — canonical item files and the archive index — is ground truth and losslessly complete. The notification plane — the RSS feed — is a lossy, windowed signal that something changed. Readers reconstruct state from the state plane; they never treat the feed as authoritative.

2. Terminology

3. Conformance levels

Level Meaning
L0 Any plain RSS feed, grandfathered via a reader-side wrapper (summary fragment + link). No blygg constructs.
L1 This specification. Blygg identity: stable ids, versioned items, canonical item files, manifest, archive index, rollup semantics, withdrawal, pins, threads and local transclusion.
L2 (future — protocol 0.3) Cross-client constructs: stub metadata, thread nesting, forked_from lineage, blogroll, webmention.
L3 (future) Encrypted/permissioned content.

Levels are strict supersets. A conforming reader at any level MUST ignore constructs it does not understand rather than reject the document containing them (this is what lets levels and versions advance without breaking anyone).

A publisher conforms at L1 by serving the surfaces in §4 with the semantics in §§5–10. A reader conforms at L1 by following the rules in §11.

4. The publication surface

A blygg is the following file tree under its origin. The origin's location is the publisher's free choice — a domain root (https://example.com/), any path (https://example.com/blyg/, https://example.com/notes/b/), or a subdomain. The surface is strictly origin-relative: no protocol construct may assume any particular path component, and readers MUST NOT infer anything from the mount path. The file names within the surface (blygg.json, feed.xml, items/…) are protocol-fixed and MUST NOT vary per deployment — a known manifest filename at an arbitrary origin is what keeps free mounting discoverable (a reader handed any base URL fetches blygg.json relative to it). /blyg/ is the reference client's default mount and the convention used in examples throughout this document; it carries no protocol meaning.

Publishers MUST serve:

Path (relative to origin) Content Spec
blygg.json Manifest §6
feed.xml RSS 2.0 feed §7
items/index.json Archive index §6.2
items/{id}.json Canonical item document §5
items/{id}/v{n}.json Pinned version document (only for pinned versions) §8
media/… Media objects referenced by items §5.4

Publishers SHOULD additionally serve human-readable HTML (a feed page, item permalink pages); their form is presentation, not protocol, except where noted (§8.4, §10.5).

Requirements:

5. The item document — items/{id}.json

Ground truth for one item. Example (a fragment):

{
  "blygg": "0.1",
  "id": "7c9wk2mhq0v3xj8tn5rzfd41bg",
  "kind": "fragment",
  "origin": "https://example.com/blyg/",
  "author": { "name": "Venkatesh Rao", "url": "https://example.com/blyg/" },
  "created": "2026-07-17T18:00:00Z",
  "updated": "2026-07-18T09:30:00Z",
  "version": 3,
  "content_md": "Markdown source of the *latest* version.",
  "content_html": "<p>Markdown source of the <em>latest</em> version.</p>",
  "content_hash": "sha256:9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
  "media": [
    { "url": "media/x7f2.png", "mime": "image/png", "alt": "a diagram" }
  ],
  "changelog": [
    { "version": 1, "at": "2026-07-17T18:00:00Z", "note": null },
    { "version": 2, "at": "2026-07-17T21:12:00Z", "note": "typo", "pinned": true },
    { "version": 3, "at": "2026-07-18T09:30:00Z", "note": "sharpened the claim" }
  ]
}

5.1 Identity

5.2 Versioning and rollup presentation

5.3 Kinds

5.4 Media

5.5 The author field

author is OPTIONAL, per-item, client-asserted, and opaque:

5.6 Reserved (do not emit at 0.1)

6. Manifest and archive index

6.1 Manifest — blygg.json

{
  "blygg": "0.1",
  "level": 1,
  "generator": "blygg-ref/0.1.0",
  "site": "https://example.com/blyg/",
  "title": "Venkat's blygg",
  "author": { "name": "Venkatesh Rao", "bio": "…", "avatar": "media/avatar.png",
              "links": [{ "label": "Home", "url": "https://venkateshrao.com" }] },
  "feed": "feed.xml",
  "items": "items/index.json",
  "updated": "2026-07-18T09:30:00Z"
}

The manifest author is the publication identity — a person, a collective, a masthead: the imprint, where per-item author (§5.5) is the byline. When an item carries no author, no assertion is made; readers fall back to this site-level identity for display.

6.2 Archive index — items/index.json

Every item ever published — including withdrawn items — with no window, ordered by updated descending:

{
  "updated": "2026-07-18T09:30:00Z",
  "items": [
    { "id": "7c9wk2…", "kind": "fragment", "created": "…", "updated": "…", "version": 3 }
  ]
}

The index is what makes new and lagging subscribers lossless: any reader can enumerate it and fetch item documents, regardless of how much feed window it missed.

7. The feed — feed.xml

RSS 2.0 with the blygg: namespace (https://blygger.org/ns/0.1):

<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:blygg="https://blygger.org/ns/0.1">
  <channel>
    <title>Venkat's blygg</title>
    <link>https://example.com/blyg/</link>
    <description>…</description>
    <lastBuildDate>Sat, 18 Jul 2026 09:30:00 GMT</lastBuildDate>
    <blygg:level>1</blygg:level>
    <blygg:manifest>https://example.com/blyg/blygg.json</blygg:manifest>
    <item>
      <guid isPermaLink="false">blygg:7c9wk2mhq0v3xj8tn5rzfd41bg:v3</guid>
      <link>https://example.com/blyg/f/7c9wk2mhq0v3xj8tn5rzfd41bg/</link>
      <title>Sharpened the claim — Markdown source of the latest…</title>
      <description><![CDATA[<p>rendered HTML of latest version</p>]]></description>
      <pubDate>Sat, 18 Jul 2026 09:30:00 GMT</pubDate>
      <blygg:id>7c9wk2mhq0v3xj8tn5rzfd41bg</blygg:id>
      <blygg:kind>fragment</blygg:kind>
      <blygg:version>3</blygg:version>
      <blygg:created>2026-07-17T18:00:00Z</blygg:created>
      <blygg:item>https://example.com/blyg/items/7c9wk2mhq0v3xj8tn5rzfd41bg.json</blygg:item>
    </item>
  </channel>
</rss>

Rules:

8. Pins — items/{id}/v{n}.json

A pin is the publisher's irrevocable hosting promise for one specific published version: this exact content stays fetchable at this URL forever — surviving later edits and withdrawal. Only pinned versions get per-version files; unpinned history stays withheld. (Analogy: the item id plays the IPNS-name role — a mutable pointer; a pinned version plays the CID role — immutable and citable. Blygger inverts IPFS's default because it is an authoring medium: mutable by default, immutable by explicit act.)

{
  "blygg": "0.1",
  "id": "7c9wk2mhq0v3xj8tn5rzfd41bg",
  "kind": "fragment",
  "version": 2,
  "at": "2026-07-17T21:12:00Z",
  "note": "typo",
  "pinned": true,
  "origin": "https://example.com/blyg/",
  "author": { "name": "Venkatesh Rao", "url": "https://example.com/blyg/" },
  "content_md": "…that version's markdown…",
  "content_html": "<p>…that version's HTML…</p>",
  "content_hash": "sha256:…"
}

Rules:

  1. Irrevocable. Once pinned, the file MUST return 200 forever — including after the item is withdrawn. Unpinned versions and unknown ids: 404.
  2. Any published version with content MAY be pinned, including retroactively (exposing withheld history is the publisher's right). Withdrawal endcaps (§9) MUST NOT be pinned — there is nothing to cite.
  3. Pinning does not freeze the id: the live stream continues under the same identity; the pin guarantees only the citation.
  4. Media referenced by any pinned version MUST be retained forever. Pinned documents carry no media array; their content_html references media directly, relying on media immutability (§5.4).
  5. Threads are pinnable like fragments: a pinned thread version serves its publish-time content_html (baked snapshots included) and its transclusions provenance, with "kind": "thread".
  6. author rides outside the pin promise. content_hash covers content_md only, so the pin's immutability guarantee covers content, never the author bytes. A pinned file SHOULD carry the assertion as published with that version, but both serve-time and publish-time assertion strategies are conformant. Verifiable authorship, if wanted, is a signature inside an authorspace grammar (§5.5), not a protocol feature.

8.4 No historical-version HTML route

Pinned versions are served as JSON only. No route ever serves an older version as an HTML page — a general historical route would gut withheld-unless-pinned. Consequently a public page's version display is an indicator, not navigation: the right presentation is discrete pin citations (e.g. "v6 · pinned: v2, v4" linking to the v{n}.json files) — pins are a sequence of frozen citable artifacts of one identity, not pages of one document. A pinned-only HTML route MAY be added in a future version, purely additively.

9. Withdrawal

Withdrawing is the only exit for a published item. There is no delete: no permanent-delete state exists in the protocol. (Discarding a never-published draft is a hard delete — nothing was ever public.)

Withdrawing publishes a permanent endcap: a version bump with content_md = "", content_html = "", media = [], an optional note, "kind": "withdrawn", updated set, and the changelog retained plus the endcap entry. For threads, the endcap also empties transclusions to [] (§10.4).

10. Threads and transclusion

A thread is an item with "kind": "thread": long-form markdown that transcludes the publisher's own fragments. Threads use the same identity, versioning, changelog, withdrawal, and pin machinery as fragments. At 0.1, threads are local-only: transclusion targets MUST be fragments of the same origin; thread-in-thread nesting and remote sources arrive at L2.

10.1 Grammar (permanent protocol surface)

10.2 Publish-time resolution

Resolution happens in the studio at publish time; readers never resolve anything.

```html

…fragment html…

```

The wrapper is a bare blockquote plus data attributes — no link inside; any provenance link shown on an HTML page is presentation, not part of the published content_html. - content_md keeps the directives — it is the authoring source of truth. Republishing a thread re-resolves every directive to the then-latest versions.

10.3 Provenance

Thread item documents carry a top-level "transclusions" array of { "id": …, "version": … } in directive order — the exact versions baked into this thread version. Fragments omit the key entirely; threads always carry it (a withdrawn thread's endcap carries []).

10.4 Snapshot independence

The rule that makes future network cycles harmless, applied locally first:

10.5 Feed and presentation

Feed entries for threads carry the full baked self-contained HTML (automatic, given the snapshot rule). Threads have no length cap. How an HTML feed page excerpts threads is presentation, not protocol.

11. Reader conformance

A conforming reader (the import side; reference implementation arrives with protocol 0.2):

  1. MUST treat item documents as ground truth and the feed as a lossy signal.
  2. MUST roll up by blygg:id: highest version wins; ties broken by updated. Timestamps are self-asserted by origins; ordering across origins is the reader's own policy.
  3. MUST treat a withdrawal endcap as roll-up-to-null and SHOULD drop the item from its local archive. A later version under the same id is the item returning.
  4. MUST ignore unknown kinds, unknown JSON members, unknown blygg:* XML elements, and reserved constructs, without rejecting the containing document.
  5. MUST NOT reject an item over its author contents, and MUST NOT treat equal author values from different origins as the same entity (§5.5).
  6. SHOULD backfill from items/index.json when the feed window has been missed; a reader offline for any duration recovers losslessly.
  7. MUST scope everything it learns to the origin: ids, authors, and trust do not transfer across origins.

12. Security and privacy considerations

13. Future constructs (non-normative)

Reserved or planned, so 0.1 implementations leave room:

What will never appear: reply primitives (this is a network of soapboxes, not a conversation medium), follower graphs or any protocol "follow" object, addressable authors, AI constructs on the wire, and content-addressed identity.