(it doesn’t have to)
0. The puzzle
Scroll to the bottom of this page. There’s a jigsaw puzzle there, and 47 people have placed pieces in it. Some of them placed a piece an hour ago. One placed a piece while you were reading this sentence. You can see the picture assembling itself, tile by tile, toward something that isn’t quite resolved yet.
Every piece placement is a git commit. When you drag a tile into position and it clicks into place, a commit lands in a public repository. It carries your GitHub username, a timestamp, and a structured record of which piece went where. The whole puzzle’s solving history is a git log. That log is not stored in a database that I control. It is not behind a paywall or an API rate limit. It is not going to disappear if I stop paying for a server. Every person who has ever touched this puzzle is in that log, and the log will exist as long as one copy of the repository exists somewhere. Someone can fork it tonight. Someone can clone it in 2035. The history does not belong to me; it belongs to the commits, which means it belongs to everyone who made one.
The standard way to build a
puzzle like this would be a database table: piece_id, slot_x, slot_y,
user_id, timestamp. The table lives on a server. The server is somebody’s
responsibility. When that person gets tired or goes broke or moves on, the
table disappears, and the history disappears with it. Every comment thread,
every leaderboard, every “who solved it first” record: gone.
Your forum threads from 2008 are gone.
1. The asymmetry
But your 2008 blog post still renders.
If someone published it as static HTML or Markdown on a personal domain and kept paying eight dollars a year for hosting, the URL still works. The page still loads. The words are still there. You can read it right now on a browser that didn’t exist when it was written, running on hardware the author never imagined, fetched over a protocol version that postdates the post itself. None of that matters. The file is a file.
Markdown won the durability war by being boring. There is no schema to migrate.
No application server to restart. No vendor to outlive. A .md file is a text
file; a text file from 2008 is as readable today as it was then. The boring-ness
is the point. When a format makes no demands on its environment, the environment
can change freely around it. People who chose flat files in 2006 were not
visionaries; they were lazy in the exact right way.
The forum did not have that option. A phpBB community lives only while someone tends the server. The moment the hosting bill bounces or the moderator takes a new job, the state goes with them. Not just the posts, but the replies, the edits, the votes, the relationships between pieces of content. When the operator left, the database closed.
And this is not just about old forums. It is the same story for every layer of interactivity we add to a page today. Want comments? You need mutable state. Want reactions, edits, collaborative cursors, presence indicators? Each one needs a write path, and every write path needs a server, and every server needs someone responsible for it. The content stays up. The interactions disappear.
Reads are durable; writes are not.
The question is why we ever stopped building things that work that way.
2. Why this happened
The read path won, and it won completely. Over the 2000s and 2010s, the publishing layer settled into defaults that are genuinely excellent: write a flat file, run it through a static-site generator, push it to a CDN. Jekyll in 2008, Hugo a few years later, GitHub Pages as a free host for anyone with a repository. The result was durable content that anyone could serve from anywhere. That part of the story went right.
The write path never got the memo. While the read path was reinventing itself around flat files and CDN delivery, the write path stayed in the 1995 mold: a server with mutable state, owned by an operator, with someone responsible for paying the bill. Comments, accounts, sessions, edit history, anything that required a user to change something: all of it still went through a database somewhere, administered by someone, dependent on that someone staying interested.
And every product that came along to fix WordPress reproduced the same architectural mistake. Ghost replaced WordPress with a cleaner editor; Substack replaced Ghost with built-in audiences. The hosting changed, the operator-dependency did not. The shape underneath stays exactly the same: a privileged server holding mutable state that the user does not own. Different paint, same chassis.
The Jamstack movement looked like it might break this pattern. Static frontends, decoupled backends, the whole read path served from a CDN. But “static frontend plus a SaaS backend” is not an architectural improvement; it is the same failure mode with extra steps. The HTML is still durable. The dynamic layer, the comments, the reaction counts, the personalization, still lives in a database somewhere. When that SaaS raises prices or shuts down, the participation layer dies exactly the way the phpBB forum died. The vocabulary changed. The structure did not.
3. The missing primitive
The structure needs one new thing: a write substrate as durable as the read substrate.
It has been sitting in .git/ the whole time. Git is append-only: commits are never modified, only accumulated. It is content-addressed: every object in the store is named by a cryptographic hash of its contents, so the history cannot be silently altered. It is signed: commits can carry GPG signatures that bind authorship to a public key. It is fully replicated by every clone: no single server holds the authoritative copy. It is forkable without permission. These properties are the reason version control works at all. They also happen to be exactly the properties that the write path for the web has never had.
The unit of change in this substrate is a git commit, not a SQL row. Call this commit-as-write: a structured, signed, append-only record of a reader’s action, living in the same repository as the content it touches, replicated everywhere the content is replicated.
Build with commit-as-write as the default and you get git-native publishing: the static web with a write path that matches the read path’s durability.
The Ink & Switch local-first essay (2019) is the philosophical predecessor to this argument. It named the problem clearly: users should own their data, software should work offline, and nothing should disappear because a vendor stopped paying its server bill. Git-native publishing is local-first applied specifically to the public web’s read-write substrate, using git’s existing infrastructure. The “git-based CMS” industry (Decap, TinaCMS, CloudCannon) is the closest existing term, but it describes the wrong layer: those tools put git behind the editorial workflow for site operators, not behind the participation layer for readers. Utterances and Giscus proved that reader writes can live in a GitHub-hosted repository without a separate database, but they write to Issues and Discussions, not to the commit log, and neither project claims to generalize the pattern.
This substrate has a vocabulary REST does not.
4. Git’s vocabulary is strictly richer than REST’s
REST’s vocabulary is five verbs applied to named resources: GET, POST, PUT, PATCH, DELETE. That model has been the operating assumption of the writable web for roughly 25 years. It is not wrong; it maps cleanly onto databases, fits HTTP semantics, and scales to most application needs. But it is a model for mutating state, not for accumulating history.
Git’s vocabulary includes all five of those operations and adds six that REST has no native equivalent for: branch, tag, merge, fork, signed commit, submodule. They are the core operations that make distributed version control work, and each one carries semantics REST cannot express.
| REST/DB verb | Git operation | What git adds that REST/SQL can’t |
|---|---|---|
POST (create) |
commit (new file) |
signed, time-stamped, cryptographically attributed |
PUT (replace) |
commit (overwrite) |
prior versions preserved automatically |
PATCH (partial) |
commit (line-level diff) |
the diff is the structured patch, no separate schema |
DELETE |
commit (remove) or revert |
reversible; deletion is a record, not an erasure |
GET |
read working tree | or read any historical state, by hash |
(none) |
branch |
parallel / private / proposed state, native |
(none) |
tag |
named / canonical / published version |
(none) |
merge |
consensus and reconciliation as first-class ops |
(none) |
fork |
take all your data and leave, lossless |
(none) |
signed commit | authentication baked into the data layer |
(none) |
submodule |
composable embedded references across repos |
Two entries in that table carry the most rhetorical weight. A signed commit binds authorship to a public key at the data layer, not at the application layer. You do not need an accounts table or a session store; identity travels with the record itself. A fork means a user can take the entire history and leave: not an export, not a backup request, but a full lossless copy with its own future. A DELETE /users/me removes your account; it does not give you your history.
The deeper point is that git’s log is already an event store in the sense Greg Young articulated with event sourcing and CQRS: each commit is a domain event, and the working tree is a projection derived from replaying those events. The same log can feed many different applications via different read projections: a comment widget, a reaction aggregator, a moderation log. None of them require a schema migration when a new projection is added; they just read the same log through a different lens. The commit-as-write primitive that §3 named is, in event-sourcing terms, an append to an immutable event log.
A commit message can carry a typed payload, making the log a free event store with no schema layer:
op: react
target: posts/your-blog-will-outlive-your-database
value: 🔥
actor: queelius
ts: 2026-04-24T22:45:00Z
Any reader action that can be expressed as a typed operation and a target fits this shape: a comment, a reaction, a vote. The next section uses a jigsaw puzzle for exactly this reason: discrete pieces, unambiguous positions, multiple actors, shared state.
5. The jigsaw, a worked example
Go back to the puzzle at the bottom of the page. Those 47 people who have placed pieces in this puzzle: every move they made is a commit in a public repository, signed by their GitHub identity, timestamped, carrying a structured payload. When you place a piece, your client pushes something that looks like this:
op: place
piece: 042
slot: [3, 7]
actor: queelius
ts: 2026-04-24T22:47:13Z
The shape should look familiar. No schema negotiation. No database column to add. The commit message is the record.
Before that commit lands, a pre-commit hook runs a verifier. Piece 042 either fits in slot [3, 7] or it does not; the source image is the ground truth. Most multi-author write systems cannot validate writes before accepting them, because there is no ground truth to check against: a comment is whatever the user typed, and the server has no way to reject it on correctness grounds. The jigsaw has ground truth. The pre-commit hook can reject a bad placement the way a compiler rejects a type error, not by policy but by reference to something real.
If two readers grab piece 042 at the same moment, one commit lands first. The second client gets a conflict, fetches fresh state, and retries with a piece that is actually available. Git’s model is optimistic concurrency: no locking, no coordination, just commit and retry if you collide. No CRDT machinery required; no special conflict-resolution protocol.
Two people placing different pieces never conflict at all. The moves commute: order does not matter for the final picture.
Participation is visceral in a way comments are not. People want to place a piece; they want to see the picture advance. And the picture forming in front of you is the demo itself: you can watch the assembly happen across all contributors in real time.
Each week’s puzzle uses a freshly-generated image. Reverse image search returns nothing; the picture is genuinely new to everyone who shows up.
When the puzzle is complete, the solving history is the git log. Anyone can clone it tonight. Anyone can study it in 2035. Every contributor is in that log, signed by the identity they used, in the order they placed their pieces. That record does not belong to me: it belongs to the commits. The log will exist as long as one copy of the repository exists anywhere.
That is the shape of git-native publishing in practice. But there is a class of interactions the commit log cannot hold, and pretending otherwise would be the same mistake this essay is trying to name.
6. Honest limits
This is not a substrate for high-frequency writes. Placing 500 jigsaw pieces per week is fine; 500 tweets per second is not. Git was designed for human-paced collaboration, not real-time mutable state at scale. Twitter, Instagram feeds, live chat: wrong problem. Comments, reactions, puzzle moves, forum threads, slow social: those fit.
The MVP uses GitHub OAuth and the GitHub commit API. That dependency is real but contingent. The architecture binds to git, not to Microsoft. Any git host works. GitHub is where most readers already have accounts; it is the simplest starting point.
Moderation is post-hoc, not pre-hoc. A revert or rebase can remove a bad commit, but the commit has to land first. WordPress’s approval queue blocks spam before anyone sees it; this model cannot do that. For open-internet participation with anonymous actors, this is a genuine cost. For contexts where all participants are identified before they can commit, the gap narrows. For open-internet participation, it does not.
The right-to-be-forgotten cuts against append-only history. Git’s content-addressing means each commit hash depends on every commit before it. True deletion requires rebasing, and that rebase must be accepted by every clone holder. Cooperation cannot be guaranteed. This is a structural cost, not an engineering problem waiting for a solution: anything that enters the commit payload may sit there forever.
The argument here is not that this beats everything.
7. The claim, and the invitation
This is git-native publishing. The unit of change is commit-as-write. These are not new tools; they are a new name for a category that has existed without one, and a label for a primitive that has been available since git became ubiquitous.
I am not selling anything. There is no product here. The argument is narrower: the durable write substrate has been absent from the web’s read-write architecture since the beginning, and git already provides it for the class of interactions where that absence hurts most. It either holds up or it doesn’t.
If you have built something in this shape, I want to know. Not because it validates the category, but because I want to study what you learned. Reach me at lex@metafunctor.com or as @queelius.
The puzzle is at /arcade/jigsaw. Place a piece. It takes thirty seconds. When you do, your name goes into the commit log, signed by your GitHub identity, alongside everyone else who has touched it.
This essay’s own source is markdown in a git repository at github.com/queelius/git-native-publishing. The library that powers the jigsaw is at github.com/queelius/git-native. When you read this essay, you are reading commits in the substrate it is naming. The argument is demonstrated by the thing you are holding.
Discussion