Skip to content

A page of the record that verifies itself

Terminal window
mnema site # writes .mnema/site/index.html
mnema site --out _site # writes _site/index.html

One HTML file from the committed tree. Each decision is a card: its reasons, what was turned down, who recorded it and who accepted it, its history and the paths it is addressed at. Under a decision are the public notes (observations and memories) that belong to it, and a note that was taken back stays, whole, with when, by whom and why. A note that belongs to no decision is listed apart, and a timeline lists each act the record holds, in the record’s order (not the clock’s). A decision has an anchor by its number (#ADR-12) when no other decision on the page carries that number, and always one by its id. Tasks, handoffs, runs and the private tree are not on it.

Each act says whether a person or an agent carried it out, in the words the show verb uses (a person, or agent and the name the act declared), next to the identity (mnid:...) the record holds. The badge is the act’s own which as the record holds it, and the page does not check it any further; neither it nor an identity is a person’s name.

Every decision is on the page without JavaScript. With it, a filter opens on the decisions in force and narrows them by state, identity, agent or person, the day the decision was recorded, text (notes included) and path (by segment: packages/code matches an address packages/code/src/x.ts and an address packages, and does not match packages/codex), and the page says how many of how many it shows. A link to a decision the filter hides (the timeline’s “open”, #ADR-12, a note’s anchor) gives the filter way: it goes to all states, and clears the other choices if they still hide it. Nothing is stored: no cookie, no local storage, nothing in the address.

A field of the record is drawn the way the show verb words it: whitespace collapsed in a short field (title, identity, agent, topic, relation, path, reason) and a right-to-left override, a bell, an escape or a lone carriage return shown as an escape, so a hostile value cannot reorder, forge or split what is read. The stored lines of a tail, drawn when it is opened, are the exception: they are the bytes of the files, as stored.

The file also carries the record’s files, and the page runs the same verifier as mnema verify over them in the reader’s browser (no request, no key, nothing loaded from elsewhere) and prints its sentence, after it has drawn the page. It is mnema verify’s verdict for a fresh clone of the repository, and the record’s own declaration of the backup key init made is what lets a clone say that key as a backup rather than as a key whose tail is missing. The stored events are not written into the page a second time: opening a tail under Events draws its lines from those files, so without JavaScript they are not drawn (the files are still in the page, in base64).

What the verdict covers: the hash chain and the checkpoint signatures against the public keys the page carries. It does not say that a key belongs to the person a list names.

The lists are another line, beside the verdict. mnema site writes them into the page, so a reader without JavaScript has them. Once the verdict is verified, the page derives them again in the browser, from the files it just verified, with the same TypeScript the command writes them with (bundled into the page), and holds what it shows against that: the text of every decision card, note and timeline entry, the attributes the filters read, which decisions, notes, entries and tails there are and in what order, and the counts. It says match, or names each difference by its item (ADR-9 (state): the page says "accepted"; the events say "proposed", ADR-12 is on the page and not in the events). Over a record that did not verify it says the lists were not checked, and it never says match; without JavaScript the lists are all there and the page says it has not checked them. A difference is not a verdict on the chain: the chain’s line is the verifier’s and does not change.

That is not a second, independent reader: it is the product’s own code run twice, and it can only catch a page that disagrees with it. The Python reader verifies the chain and does not derive decisions. A page handed over by the party it checks carries the script that checks it, so the check is worth most when the page is made again from a clone. It does not read a style (a rule that hides a card, a colour) and says nothing about one. It reads text and structure, and only where the lists are: every element directly in the decision list, the list of notes apart and the timeline must be an item the events give, with no text between them, and the parts of the page’s <main> must be the ones the page has. It does not read text outside the lists (the paragraphs about the verdict, the headings, the filters’ labels), the attributes it was not told to compare (a hidden or a class put on a field, which it does not see), or an element wrapped around a field, such as a link around a title: what such a field says is compared, what is added around it is not. On a large record the derivation holds the browser’s main thread for about one to two seconds after the verdict (the filters wait until it ends).

Only the committed (public) tree is in the file, and all of its text is: a repository that must not be read should not publish one.

Without --out the page is written under the record, in .mnema/site/, and the record’s own .gitignore is given the line /site/ before the first byte, so a git add .mnema leaves it out: it is derived from the record and rebuilt at will, never committed. A record made before the line existed gets it the next time mnema site (without --out, or with one under site/) or mnema init runs: that appends /site/ to .mnema/.gitignore (and * -text to .mnema/.gitattributes, which it creates if it is absent), a change in the record’s folder that is yours to commit. A mnema site --out _site outside the record, as in the workflow below, touches nothing under .mnema/. And mnema doctor asks git whether the page is ignored and whether git already tracks something under .mnema/site/ (it says the command to stop tracking it, git rm -r --cached .mnema/site, and never runs it). A directory named by --out that is inside the record and outside site/ is refused, by name, before anything is written: the folder whose contents are the proof receives nothing derived. Neither reader of the record reads site/.

When the page sits inside a larger site, mnema site --out _site --back-link / adds one link at its top that goes back to the address given, labelled “Back” (the label is fixed). The address is an http:// or https:// address, or a path on the same site (one / first, or relative); a protocol-relative //host, a backslash, an https: with no // and any other scheme are refused, by name, before anything is written. Without the option the page is exactly the one above.

To publish it on GitHub Pages, build it in a workflow, with --out _site so that the artifact is outside the record (the default is for reading the page on your own machine). The CLI is what writes the page; the Action reads and verifies the record, writes to it only if handed a checker-key, and is not involved:

name: record-site
on:
push:
branches: [main]
permissions:
contents: read
pages: write
id-token: write
jobs:
site:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: '24'
- run: npm i -g @mnema/code
- run: mnema site --out _site
- uses: actions/upload-pages-artifact@v4
with:
path: _site
- uses: actions/deploy-pages@v4

The npm i -g line is the one in Install; with a checkout of this repository built from source, run node packages/code/dist/cli.js site --out _site instead.

mnema 0.1.4 · commit cebc4e8da68c