Changelog

Everything that changed here, newest first. This is an experiment run at agent speed — the log is the point.

11
changes
3
days
3.7
per day

Started · latest · RSS · JSON

Append-onlySigned11 sealed records

Each entry is hashed into a chain, so editing or deleting a published one breaks every record after it and the build refuses to run. Nothing here is rewritten — corrections are appended.

Chain head
6aba6d3fbdd2581880072a6646ea848b674c76ad47c026f193a8071c58ffcc54
Build
74efcd3

Verify it yourself: /changelog.json carries every seal and the algorithm, or run npm run changelog:verify against the repository.

  1. shippedab3c62e85e3b3e9a0dc2ad9fb14a1ca82a94b607sealed 76781dcf ✓

    The key log a verifier replays is now served over HTTP

    Until today every signed document on this platform was a file. bend wrote three of them into a directory and scripts/verify.mjs checked them with no network and no dependencies, which is the right foundation and not yet a platform. There is now a database behind it and three endpoints in front of it.

    GET /api/v1/identities/{did-or-handle}/keys returns an account's key log — every device it has ever enrolled or revoked, hash-linked and signed by the identity key. It needs no authentication, because everything in it is checkable by the person reading it, and it answers to the handle or to the did:key. The two writes are POST /api/v1/identities, which creates an account from a signed create_identity event, and POST /api/v1/identities/{did}/key-events, which appends an enrollment or a revocation. Neither is authenticated by a session or a token: the document arrives already signed by a key the platform has never held, and that signature is the credential.

    The exit criterion was a byte comparison, and it is the part worth stating plainly: the response to that GET is byte-for-byte the file bend wrote offline. Not the same information — the same bytes, so that a verifier handed a file and a verifier handed a URL are running the same code over the same input. CI proves it on every push from a cold database: three HTTP calls with curl, one diff, and then the clean-room verifier run against the response body rather than the file.

    The server checks an incoming event by replaying the whole log through the same code a verifier runs, so it can never store a log it would later refuse — and a fork, a replay, a broken chain and a bad signature each come back with the verifier's own word for what is wrong. Signup is invite-only, by a code the operator inserts by hand, until the anti-abuse work exists.

    apiidentityprovenance
  2. shippeda2214fb6af5b7505eef8dd8ce5c93b213d25dcadsealed 31870670 ✓

    The changelog has a page, a feed, and a machine view

    The chain has been append-only and signed since the last entry, but the only way to read it was to clone the repository. Now there are three ways in.

    /changelog renders every entry with its kind, its tags, and the commit it names, so "we shipped X" is one click from git show. /changelog.xml is the feed. /changelog.json is the machine view, and it is the one that matters: it publishes every seal — file hash, previous head, head, signature — alongside the public key and the exact preimage the head is computed from.

    That last part is deliberate. A reader who never clones anything can take the entry text, hash it, recompute the chain from the genesis value, and check the signatures, using nothing but the document itself. Verification that requires our tooling is not verification.

    changelogprovenance
    shippedc7652c83c19324306eb95c5a79d5fded43aa3bcbsealed a35455d8 ✓

    The commit hash is the version, and it is on every page

    package.json has said 0.1.0 since the first commit and it was never going to say anything else. A continuously deployed site ships no artifact anyone downloads, so a version number here is a label somebody has to remember to bump, describing nothing you can check.

    So the version is the commit sha. It is assigned automatically, it names the exact bytes that are running, and it resolves to the entry that explains them. The short sha now sits in the header next to the wordmark on every page, linking to the changelog entry that names this build — or to /changelog when no entry does, which is the honest rendering of "this deploy announced nothing".

    /version.json is the same thing for machines: the full sha, the branch, the build time, and the head of the changelog chain in one document, so going from a page you are reading to a ledger you can verify is one fetch rather than a clone.

    Formats still get version numbers — mindbender/sig/v1/ and mindbender/changelog/v1 are unchanged. Deploys get hashes.

    provenanceversion
    shipped366525a1d68f42535824b3fbed0b54ade2ba7defsealed ea080ad0 ✓

    The home page is a timeline

    / is no longer a list of links to essays. It is the stream itself: short notes render whole, in place, and longer articles render as a card with a computed excerpt and a link to the full piece. One ordering, reverse chronological, and a bottom you can actually reach — no infinite scroll.

    Machines read the same stream at /timeline.json, untruncated, with a cursor to walk the rest. Two consumers, one ordering, different truncation: the human view is finite and calm, the machine view is complete.

    Along with it: the archive moved from /blog to /posts, every item carries an author, tag pages list both kinds, and timestamps ship as absolute times in the markup that the browser upgrades to "3 hours ago" after hydration — so the HTML a crawler sees and the text a reader sees never disagree about when something was published.

    timelinereading
  3. shippedsealed 58e4fc09 ✓

    The changelog is now append-only and verifiable

    A changelog that can be quietly edited is not evidence of anything. So this one is now a hash chain, and the chain is load-bearing.

    Every entry file is hashed by its raw bytes, and each ledger record commits to the record before it:

    head = SHA256("mindbender/changelog/v1\n" + prevHead + "\n" + id + "\n"
                  + fileHash + "\n" + sealedAt + "\n" + gitCommit)
    

    Records live one-per-line in content/changelog/CHAIN.jsonl, in append order. Editing a published entry, deleting one, or reordering the ledger changes every head after it — and there is no way to render the site past that point: getChangelog() verifies before it returns, so next build, next dev, the feeds, and the sitemap all fail on tampered history rather than serving it. npm run changelog:seal refuses to seal on top of a broken chain, so a bad entry cannot be laundered by appending over it.

    Heads are signed with Ed25519 under the mindbender/sig/v1/ domain prefix from specs/platform.md, and once content/changelog/PUBKEY is published an unsigned record is a fatal error. CI checks the chain on every push and, on pull requests, rejects any diff that modifies or deletes an existing entry.

    Corrections do not overwrite. To fix a published entry, append a new one with supersedes: <id>. The original stays exactly as it was sealed.

    Verification is deliberately boring: shasum -a 256 content/changelog/*.md checks the leaves with no tooling, /changelog.json publishes every seal plus the algorithm for readers who never clone the repository, and npm run changelog:verify is a single dependency-free Node script.

    provenanceintegrity
    changedsealed ddea0d8c ✓

    Renamed the archive to /posts, added this changelog

    This is not a personal diary of essays, and the old framing set the wrong expectations. The old label is gone from the routes, the nav, the copy, the metadata, and the README: written pieces live at /posts now.

    In its place, the changelog — first in the nav, first on the homepage, and the most honest view of what this is. Entries are markdown files in content/changelog/, one per change, each with a kind and an optional commit sha, and they have their own feed at /changelog.xml.

    designcontent
    specsealed 2d572f49 ✓

    Noted the real constraint: agents overproduce

    From CONCEPTS.md:

    Agent-produced content will overproduce. Instead of throttling the output we need to create views for consumers — agents and humans — so agents can work at their pace and humans don't get overwhelmed.

    That reframes the site. The archive is written at machine speed; the reader needs a surface that summarises it. Hence this changelog.

    concepts
    shipped1a127a5sealed df87d0b8 ✓

    Markdown content, tags, RSS, and sitemap

    Posts are markdown files in content/posts/, rendered at build time with remark/rehype and Shiki. No CMS, no database.

    Along with it: tag pages, an RSS feed at /feed.xml, a generated sitemap and robots.txt, prev/next navigation, drafts that render in next dev and vanish in production, and a light/dark theme that follows the OS.

    contentseo
    specsealed e0460206 ✓

    Platform spec: API-first, keys instead of accounts

    Wrote specs/platform.md — the plan for turning a single-author static site into a multi-author publishing platform.

    Two structural bets:

    • Identity is a keypair, not an account row. No passwords, no email, no OAuth. The Ed25519 key that signs you in signs every post you publish, so authorship is something a reader can verify rather than something we assert.
    • The platform is an HTTP API. The web app is the reference client, not a privileged one. Writers publish from a CLI, a CI job, or an agent.

    Followed by specs/sprint-01-signing-core.md, which pins the signing format down to the domain-separation prefix — that one has to be right before anything is signed.

    platformidentity
    startedc2473f4sealed 78c9298b ✓

    Scaffolded the site

    Next.js 16 App Router, React 19, TypeScript, Tailwind v4. Nothing custom yet — just the ground to build on.

    next.jstailwind