Design note · 11 Sep 2026 · accepted, not yet implemented
A layer is not one of five fixed stages. It is a node in a versioned processing graph — a step, a revision, a feature, an open question, a candidate output — that runs against a paper and produces a new version of it. Layers stay visible as work proceeds rather than collapsing into a current state. The site's job is to show that graph, in general and applied to every paper, and to say honestly which cells are current, stale, absent, or impossible.
General from particular. Methods, formats, docs, agents, schema and standards describe the machinery and show no paper's output. A paper's pages show that paper's output and explain no machinery.
Layer from view. A layer is a unit of work with inputs, outputs and dependencies. A view — graph, list, table, marked document, comparison — is a way of looking at one. Views are shared vocabulary: a new layer usually reuses existing views, occasionally earns a new one.
Everything else follows. /extract is a layer's output for one paper, not a page. /agents is the general half of the induction layer. /standards is reference material about external formats, sitting outside the pipeline and connected to the layers that target them. An Introduction sits outside both, for the reader arriving cold.
Left to right by dependency. Dashed borders are corpus-scope layers — schema and vocabulary decisions that apply to every paper and, when they change, make every dependent paper-instance stale. Filled boxes are artifacts rather than steps.
The shape matters more than the contents. Induction fans in — three candidates reconciled to one. Interchange fans out — one tree into three encodings that never rejoin. Coverage is the only layer that reads the paper again after induction, which is why it is the one that can find what everything else missed. And the two corpus-scope nodes on the left have edges into almost everything on the right, which is why a schema decision is never a small change.
The kind is not decoration: it determines whether a layer can be re-run mechanically, and what its output supersedes.
| Kind | Produces | Re-runnable | Example |
|---|---|---|---|
| step | An artifact from declared inputs | Yes, mechanically | coverage spans, MIRA export |
| candidate | One of several parallel outputs, kept rather than collapsed | Yes — and worth re-running with a different model | each of the three readers; a prompt variant |
| judgement | A decision stored so it can be disputed | No — recomputing destroys the record | coverage adjudication, review gate |
| feature | A new capability that revises existing artifacts | Once, then it is part of the schema | stance and alternatives |
| question | A decision about meaning, then a re-typing of what it governs | The decision no; the propagation yes | #19 dissociates-with, #20 epistemic |
#19 is the case that proves the model. "Is dissociates-with an opposition?" has a general half — the definition, the decision, the declaration in the exporter — and a per-paper half: which of this paper's relations are affected, and what changed when the answer landed. It is a layer with 65 instances across 10 papers, it sits upstream of every export, and when it resolves, every export downstream of it is stale. Exactly like a processing step. That is why it belongs in the same structure as one, and why a new paper arriving later gets the same question asked of it automatically.
Everything above describes layers that exist. This describes how one comes to exist, and how an existing one is corrected — which turn out to be the same act.
A declaration is a proposal. It says a step should exist and what it does. It goes up as a page — the question, the mechanics, a worked example, what it would change, what it feeds — and it is discussed, improved, and at some point accepted. Adding a step to the pipeline and correcting an existing one are one operation: a new version of a declaration.
This generalises something the repository already does by hand. Every schema change so far — stance, the relation vocabulary, the epistemic question — has been an issue, then an argument, then an edit. That is a proposal being accepted, without a place to stand or a record of who accepted it.
Three things, and the third is what makes the pipeline worth building.
| Evidence | Asks |
|---|---|
| the logic | Is the question worth asking, and is it the right question? |
| the code | Does the mechanism answer the question it claims to? |
| the outputs | What did the runs actually produce, across the papers they ran on? |
The third is why an output approval matters beyond itself. Reading Gädeke's claim tree v3 and approving it is evidence about the claim-tree declaration, not only about Gädeke. A declaration accepted on argument alone is a guess; one accepted with approved outputs behind it has been tried. So the two kinds of approval are not parallel — the per-paper ones accumulate into the corpus-level one.
Approval applies to a version. What differs is whose version.
| Subject | Scope | Asks | Recorded in |
|---|---|---|---|
| declaration | corpus | Should this step exist, and is this what it should do? | runs/approvals.jsonl |
| output | paper | Is what this run produced right? | runs/<paper>/approvals.jsonl |
Keeping these apart is not pedantry. They were conflated once already, under one word review, and the result was two things in the repository with the same name doing different jobs.
A proposal needs somewhere for people to argue with it, and a static site cannot hold a comment thread. It does not need to: the layer's GitHub issue is the thread. relation-vocab already declares issue: 19 and its page links it. The field simply stops being something only question layers carry, and the page shows the discussion's state rather than only its address.
If accepting a declaration revision changes what a layer does, every run of that layer is stale. That follows from the propagation rule already built — but only if the declaration is itself a hashed input to every run, and today it is not: reads hashes scripts and prompts, not pipeline/layers.yaml.
Closing that makes revising a definition turn the matrix amber for every paper the layer touches, which is exactly what should happen and currently does not. It is a small change and the sharpest test of whether the model is real.
proposed → accepted → superseded. The open: true flag on the question layers is a crude form of it, and replication — declared, with no runner and no runs — is simply a proposed layer that nobody has accepted or tried. Saying so is better than an empty column with no explanation.
status: proposed and an issue for its comments — which is what it has. That it can describe itself without strain is the best evidence available that the model holds.
A cell is not a tick. The interesting states are the two the site cannot currently express — stale, and impossible — and the jagged edge is the normal condition, not a defect to hide.
current ran, and every declared input still hashes to what the run recorded.
stale ran, but an input changed since. The output is still there and no longer trustworthy.
absent never run. The site shows what would run it, and what that depends on.
n/a declared impossible, with a reason. Artiushin's verification is image inspection; Kammer's needs a specialist fMRI pipeline.
open a question-layer with no decision yet. Downstream is provisional by definition.
Staleness flows along dependency edges. Re-type a relation and every export is stale; regenerate the exports and the formats report is stale; change a prompt and every run that used it is stale.
This is the rule that would have caught the three drifts found this week: Gädeke's OXA measuring 89 of 93 relations after the stance work, the standards page publishing 57% after the exporter changed, and a verification banner describing a run superseded by the audit.
None of those were noticed by anyone looking at the page. All three were computable.
| Paper | induct | tree | abs↔cl | verify | spans | adjud | marked | stance | mira | oxa | dg | formats |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Gädeke | ||||||||||||
| Kolb | ||||||||||||
| Wengert | ||||||||||||
| Rozak | · | |||||||||||
| Headley | ||||||||||||
| Ejdrup | ||||||||||||
| Scheller | ||||||||||||
| Bouyeure | ||||||||||||
| Artiushin | · | |||||||||||
| Kammer | · |
Drawn from the artifacts on disk today. It is mostly honest already — what it cannot yet say is stale, and every drift we found this week would have shown as an amber column.
This was the open question. The answer is not a hand-written changelog and not git log — it is the run record, which three parts of the repository already keep independently and none of them share.
runs/<paper>/manifest.json — per role: prompt path, prompt hash, model, output, output hash. Its README already states the rule: "if the hash of a prompt file no longer matches what a run recorded, that run was produced by a different prompt than the one now in the tree."
verification/<paper>/provenance.json — argv, interpreter, data commit, every file opened with a hash, exit code.
mappings/<paper>.json — a sha per verdict, discarded rather than applied when the sentence it judged has changed.
Plus byte-stable exports, where git diff is the staleness check. Four mechanisms, one problem, no shared vocabulary.
Every layer run appends a record. Inputs and outputs by path and hash; the declared dependency edges come from the layer definition, not from the record.
Version history is the ledger sorted by date. Staleness is re-hashing the inputs. Propagation is a walk over the dependency graph. The changelog line is the one hand-written field, written at the moment of the change by whoever ran the layer rather than reconstructed from a diff months later.
Git stays the audit trail underneath. The ledger is the index into it.
runs/gadeke-2026-guilt-insula/ledger.jsonl {"layer":"stance", "v":3, "ran":"2026-09-10T18:22Z", "kind":"feature", "note":"Six alternatives authored from the controls' closing clauses; false contradiction replaced with qualifies.", "in": [{"path":"claims/gadeke.../*.md","sha":"a41c…"}, {"path":"docs/claim-format.md","sha":"9be0…"}], "out":[{"path":"claims/gadeke.../alt-*.md","sha":"77d2…"}], "by": "claude-opus-5"} {"layer":"mira-export", "v":7, "ran":"2026-09-11T08:04Z", "kind":"step", "note":"Regenerated after stance; 6 nodes gain haak:stance.", "in": [{"path":"claims/gadeke.../*.md","sha":"a41c…"}, {"path":"scripts/export_mira.py","sha":"5f31…"}], "out":[{"path":"exports/gadeke….mira.jsonld","sha":"c0a8…"}], "by": "scripts/export_mira.py"}
The second record's first input is the first record's output hash. That single fact is what makes the whole graph checkable: change the claims and a41c… no longer matches, so the export is stale, so the formats report that consumed it is stale, and the site can say so without anyone remembering to.
The matrix is not a picture of the corpus. It is a view over the ledger, and every cell in it is a row that can be fetched, rendered, compared or run.
A cell is (paper, layer, version). A view is how you look at it. Everything the site does is one of four verbs on that tuple: render it, fetch it, compare two of them, or run the layer that produces one.
| Verb | Call | Gives |
|---|---|---|
| render | /papers/<paper>/<layer>[@v]/<view> | The page. @v omitted means current; the view defaults to the layer's own. |
| fetch | /data/<paper>/<layer>[@v].json | The same cell as data, pre-rendered in a static build. This is what makes a candidate exportable rather than merely visible. |
| compare | /compare?a=…&b=… | Two cells side by side: two versions of one layer, or two layers of one paper — mira against oxa, draft against final, v2 against v3. |
| run | POST /_run {paper, layer} | Dev mode only. Runs the layer and its unmet dependencies, appends to the ledger. The published build shows the same command as copy-and-run text. |
A layer page is then a query with the paper unbound, and a paper page a query with the layer unbound. The matrix is the query with both unbound — which is why it is worth building as a real view over the ledger rather than as a table someone maintains. Useful queries fall out of it: every stale cell, every paper missing coverage, every instance of one question across ten papers, everything a given prompt version produced.
Two consequences worth stating. Candidates become first-class for free — a second prompt variant is a version like any other, addressable, fetchable and comparable against the one that shipped, instead of being overwritten by it. And a cell that cannot be rendered can still be described: absent cells carry the command and the dependency chain, so the jagged edge is navigable rather than blank.
Six views cover every layer we have. A new layer picks from them; earning a new view should be rare and deliberate.
| View | Shows | Used by |
|---|---|---|
| graph | Shape — the deductive spine, what hangs off what | tree, stance, verification status over the tree, relation re-typing |
| list | The text. The only form in which every claim can be read | every layer |
| table | Columns you sort and scan | verification, coverage gaps, formats, candidates |
| document | The paper itself, marked in place | coverage, abstract↔claims |
| comparison | Two encodings of one thing, side by side | formats, draft ⇄ final, reproduced ⇄ published, before ⇄ after a revision |
| overlap | Set relations between parallel candidates | the three readers; prompt variants |
Comparison is the view the pipeline model makes newly important. If a layer produces a new version, the interesting thing is rarely the new state alone — it is the diff. What did the stance layer change? What did re-typing 65 relations change? A revision layer with no before-and-after view is just a silent edit.
| Surface | Scope | Holds |
|---|---|---|
| / (introduction) | outside | What this is, why panel-level claims, what the prototype shows and does not. For the reader arriving cold — including eLife. |
| /pipeline | general | The graph above. Every layer's definition, kind, dependencies, views, and the command that runs it. The corpus matrix under it. |
| /pipeline/<layer> | general | One layer in general: what it asks, what it produces, its state across all ten papers, and what it found corpus-wide. |
| /papers | particular | Ten papers with their fill state visible — the jagged edge as the index's main information. |
| /papers/<paper> | particular | This paper's instance of the pipeline. Layer navigation, each layer's own structure and views, provenance on every generated thing, and its version history from the ledger. |
| /standards | reference | What we assume and know about MIRA, OXA, CiTO, Discourse Graphs and our own schema. Outside the pipeline, linked from the layers that target each format. |
| /docs | reference | The CLI and the machinery. Already correct; unchanged. |
Note what disappears: /extract, /agents, /layers, /formats, /formats/mira and /findings are all either a layer in general or a layer applied to a paper. None of their content is lost; all of it moves to the altitude it belongs at.
Deliberately: the model and its data before any page is moved. Rearranging pages first would just relocate the drift.
runs/, verification/, mappings/, and the export files. Every layer's runner appends to it from then on.corpus-facts.json beside the layer detection already there.Answers to the five questions the last draft left open, recorded here so the implementation does not have to re-ask them.
| Question | Decision | Implication |
|---|---|---|
| A · running | Local dev mode runs layers; the published build shows copy-and-run. | Two behaviours from one cell definition. The command string is in the layer declaration either way. |
| B · grouping | Groups are real. Induction groups five runs. | A group is a layer with children, not a display convention — the same shape will be wanted for binding related questions into one parent, as sub-issues. |
| C · candidates | First class, exportable and comparable. | Drives the addressing above: a candidate is a version, reachable by the same call as any other. |
| D · naming | versions / views / layers. | A layer runs and produces a version; a view renders one. "Step" stays informal, for a layer whose kind is mechanical. |
| E · questions | #19 becomes a layer after the issue is examined. | The reading happens in the issue; the decision and its propagation become a declared layer with per-paper state, so a new paper gets the question asked of it automatically. |