feat: rewrite docs
check / check (default, macos-latest) (push) Canceled after 0s
check / check (default, ubuntu-latest) (push) Canceled after 0s
check / check (py310, macos-latest) (push) Canceled after 0s
check / check (py310, ubuntu-latest) (push) Canceled after 0s

This commit is contained in:
alexm committed 2026-10-12 07:55:22 -04:00
1 parent 8b2df58266
commit 002e2c3552
140 files changed
+4555 -701

No files matched your search

+93 -37
View File
@@ -1,22 +1,40 @@
# Walkthrough: one paper, start to finish
This guide follows the example paper `hale2026harmonic` from a flawed first reading to a corrected, reviewed and signed document.
The paper is synthetic, written for the fixtures, but everything that happens to it happens to real papers.
Open [fixtures/packages/hale2026harmonic/](../fixtures/packages/hale2026harmonic/) beside this page: every ID below is in it.
The hand-written source of the first reading is [fixtures/src/hale2026harmonic.json](../fixtures/src/hale2026harmonic.json).
The paper is synthetic, written for the fixtures.
Its problems are modeled on the failures that the specification's rules address.
Three readers can use the guide, and each gets something different.
A newcomer knows what a paper and a citation are, not hashes or JSON, so every term is defined where it first appears.
An implementer who will write a reader, writer, converter or checker gets the chapter and test vector for each step.
An expert gets the status: [manuspec](https://git.scient.ing/infra/manuspec) is at version 0.1.0, and each format is `candidate` or `provisional` in [`registry/formats.json`](../registry/formats.json) (20 candidate, 7 provisional).
The `scienting-` prefix on format names, as in `scienting-doc/1`, is the formats' namespace.
It has nothing to do with the repository's name.
Open [fixtures/packages/hale2026harmonic/](../fixtures/packages/hale2026harmonic/) beside this page, because every ID below is in it.
A package is a folder of files, mostly JSON Lines (`.jsonl`: one JSON record per line).
The hand-written source of the first reading is [fixtures/src/hale2026harmonic.json](../fixtures/src/hale2026harmonic.json); no converter ran on it, and [`tools/build.py`](../tools/build.py) expands it into the package.
Each step says what a converter, a person or a checker does.
## 1. A PDF arrives
The team downloads the publisher's PDF.
It becomes a witness, identified by its SHA-256, with the short ID `qptsanv`, and a rights record: the library's subscription allows reading, text mining, and AI processing on the team's own machines, until August 31, 2027; whether it allows processing in the team's cloud is unknown.
The file becomes a witness, identified by its SHA-256 (a 64-digit hash that changes if any byte changes), with the short ID `qptsanv` ([spec/02](../spec/02-model.md)).
A person records its rights, meaning what the team may do with the file ([spec/14](../spec/14-rights.md)).
The library's subscription allows reading, text mining, and AI processing on the team's own machines for internal use, until August 31, 2027.
Whether it allows processing in the team's cloud is unknown.
The publisher stamped the download in the margin with the institution and the time.
Fingerprinting each page removes those lines first (the rules `downloaded-via` and `sharing-guidelines` fired on both pages), so page 101's fingerprint is `6272e6f3…` in this copy and in a colleague's download stamped a year later.
See [`pages.jsonl`](../fixtures/packages/hale2026harmonic/pages.jsonl).
The publisher stamped the download in the margin with the institution and the time, so two downloads rarely share a SHA-256.
A page fingerprint avoids the problem of downloads that differ in their stamps.
It is a hash of a page's text, size and images after the stamp lines are removed ([spec/07](../spec/07-geometry.md)), and the rules `downloaded-via` and `sharing-guidelines` fired on both pages (see [`pages.jsonl`](../fixtures/packages/hale2026harmonic/pages.jsonl)).
Page 101's fingerprint is `6272e6f3…` in this copy, stamped October 11, 2026, and in a second download from another institution, stamped March 2, 2027 ([vectors/fingerprints.jsonl](../vectors/fingerprints.jsonl)).
## 2. The first reading, with its mistakes
A converter reads the PDF into sections, paragraphs, sentences, a figure, a table, an equation, a footnote and references, and mints a permanent ID for every node.
A converter reads the PDF into sections, paragraphs, sentences, a figure, a table, an equation, a footnote and references.
Each of these is a node, and each node gets a permanent ID: seven characters minted once from the document key, the node's kind, a hash of its text and an occurrence count, then frozen ([spec/04](../spec/04-identity.md)).
An ID encodes no page, order or kind, so it keeps naming the node after the text is corrected.
Like every reading, this one has mistakes:
| What the reading did | What the page says |
@@ -32,12 +50,14 @@ Like every reading, this one has mistakes:
| The OW–HW row was missing. | It is there. |
| "Set B" spanned one column. | It spans two. |
The doubtful places become suspects with crops of the page, for a person to answer.
The converter does not guess, and nothing changes until a correction passes the gate.
The converter does not guess.
Each doubtful place becomes a suspect, with a crop (an image cut from the page) for a person to answer ([spec/05 §5.7](../spec/05-hashes.md)).
Unresolved suspects travel in `suspects.jsonl`, which this package does not need.
Nothing changes until a correction passes the gate ([spec/11](../spec/11-corrections.md)).
## 3. Twelve corrections
Each fix is appended to [`corrections.jsonl`](../fixtures/packages/hale2026harmonic/corrections.jsonl), numbered, with its evidence, its author and its reason.
Each fix is appended to [`corrections.jsonl`](../fixtures/packages/hale2026harmonic/corrections.jsonl), numbered, with its evidence (a crop, or a person who read the page), its author (`ana` throughout) and its reason.
| # | Operation | On | What it did |
| --- | --- | --- | --- |
@@ -55,72 +75,108 @@ Each fix is appended to [`corrections.jsonl`](../fixtures/packages/hale2026harmo
| 12 | `relabel` | figure `ajfvq4s` | `Figure l` to `Figure 1`. |
The document is now at revision 12.
Notice what did not happen: no other cell, sentence or row changed its ID or its hashes.
Inserting a row adds a row node; the table's own hash covers only its label and column definitions, so every other cell, and every value already taken from them, is untouched.
No uncorrected cell, sentence or row changed its ID or its hashes.
Only corrected nodes and their ancestors got new `node_hash` values; the sentence `sxgcc9h` is one of the ancestors, because it holds the relabeled equation.
Inserting a row adds a row node.
The table's own hash (`own_hash`) covers only its label and column definitions, so every other cell, and every value already taken from them, is untouched.
## 4. IDs that never move
After twelve corrections, the sentence about water is still `f929eq9`, the corrected cell is still `nkm3xn5`, and the sentence that lost its superscript is still `e4fkseg`.
The sentence about water is still `f929eq9` after twelve corrections, the corrected cell is still `nkm3xn5`, and the sentence that lost its superscript is still `e4fkseg`.
The retired IDs are recorded in [`ids.jsonl`](../fixtures/packages/hale2026harmonic/ids.jsonl), so anything that pointed at them can follow the trail.
So is a legacy alias: `b-p01-text-003`, the ID an older tool gave the water sentence, still leads to `f929eq9`.
Legacy aliases are recorded there too: `b-p01-text-003`, the ID an older tool gave the water sentence, still leads to `f929eq9`.
If a better converter comes along next year, its reading is aligned with this document sentence by sentence; every sentence it reads the same way keeps its ID, and every difference becomes a suspect rather than a silent change.
A fresh conversion of the same input mints the same IDs.
A reading by a different converter is aligned with this document sentence by sentence ([spec/05 §5.6](../spec/05-hashes.md), pinned by [vectors/alignment.jsonl](../vectors/alignment.jsonl)).
Sentences the alignment pairs keep their IDs, and every difference becomes a suspect rather than a silent change.
Sentences it cannot pair are recorded as dropped or added.
## 5. What happened to the notes
Before any correction, at revision 0, the team had already made seventeen anchors: notes, extracts and citations pointing into the first reading.
Here is how each resolves at revision 12 ([vectors/resolve.jsonl](../vectors/resolve.jsonl) records them all).
An anchor is the stored pointer behind a note or citation.
It holds a node's ID, the node's hashes when the anchor was made, the revision, and, for part of a sentence, the quoted words with context ([spec/06](../spec/06-anchors.md)).
The team had already made seventeen anchors pointing into the first reading at revision 0, before any correction.
Resolving an anchor gives one of five results:
- `current`: nothing changed.
- `changed-below`: the node is unchanged, and something under it changed.
- `moved`: the node was merged into a successor that still holds the quote.
- `reattached`: the node changed, and the quote or the cell's headers now find exactly one place.
- `stale`: nothing is found, so a person must look.
A `changed-below` result carries a drift, the share of the leaves under the node, counting retired ones, that corrections touched.
Above a quarter the drift is `high`.
The table shows how each anchor resolves at revision 12 ([vectors/resolve.jsonl](../vectors/resolve.jsonl) records them all).
| Anchor on | Result | Why |
| --- | --- | --- |
| "H$_2$O" in the water sentence | `current` | Nothing in that sentence changed. |
| The cell `1.5260` | `current` | Untouched, though its table changed around it. |
| The water sentence, through the alias `b-p01-text-003` | `current` | The alias leads to the same node. |
| The cell that read `3l0.0` | `stale` | Its text changed and "3l0.0" is no longer in it; a person must look again, which is right, because the number changed. |
| "treated as zero" | `reattached` | The sentence changed twice, but the quote occurs in it exactly once, so the anchor is re-pinned. |
| The cell that read `3l0.0` | `stale` | Its text changed and "3l0.0" is no longer in it; a person must look again. |
| "treated as zero" | `reattached` | The sentence changed twice, and the quote occurs in it exactly once, so the anchor is re-pinned. |
| "fitted constants" | `stale` | After the split, those words are in the new sentence `r1gacps`, not in `e4fkseg`. |
| "least squares", in the half-sentence `e9rh611` | `moved` | That ID was merged into `eajq93t`, which still holds the quote. |
| The paragraph `g44k023` | `moved` | Merged into `zyqr0yc`, which holds all of its sentences. |
| The running head | `stale` | Deleted, with no successor. |
| The equation | `reattached` | Only its number changed. |
| The figure | `reattached` | Only its label changed, by a `relabel`. |
| The CT–HC cell `1.090` in the garbled row | `reattached` | Its row was deleted, but exactly one cell in Table 1 has the same row and column headers and the same text: the new `e0n07n0`. |
| Section 2 | `changed-below`, drift high | The section itself is unchanged, but 53% of what is in it was touched. |
| The CT–HC cell `1.090` in the garbled row | `reattached` | Its row was deleted, and exactly one cell in Table 1 has the same row and column headers and the same text: the new `e0n07n0`. |
| Section 2 | `changed-below`, drift high | The section itself is unchanged, and 53% of what it holds or held was touched. |
| Table 1 | `changed-below`, drift high | Rows were replaced and added. |
| The table body | `changed-below`, drift high | 75% of its cells were touched. |
| The table body | `changed-below`, drift high | 75% of the cells it holds or held were touched. |
| The paragraph whose broken sentence was rejoined | `changed-below`, drift high | Two of the three sentences it has held were touched. |
| The CT–CT row | `changed-below`, drift low | One of its five cells was corrected. |
A reader shows each note accordingly, and a Lode check, which is about exact words, accepts only `current`, `moved` and `reattached`.
A reader shows each note by its result.
A checker of exact words accepts only `current`, `moved` and `reattached`.
The [W3C Web Annotation Data Model](https://www.w3.org/TR/annotation-model/) is the general W3C Recommendation for annotating web resources. manuspec's annotation layers build on it, with a custom `AnchorSelector` followed by a standard `TextQuoteSelector` as a fallback.
It has no permanent IDs for sentences inside the source, no hashes of the target text, and no results such as `moved` or drift.
See [comparison.md](comparison.md) [CHECK: docs/comparison.md is absent from the repository snapshot; confirm the link target].
## 6. Review and signatures
Now people check what matters.
A receipt is a signed record that a person compared a set of nodes with the page.
It lifts a node to F3, the top fidelity level (F0 is one reading; F1, every doubtful place answered; F2, an independent witness agrees) ([spec/08](../spec/08-fidelity.md)).
One signature covers a Merkle tree, a hash over everything reviewed in a sitting, built as in RFC 6962 (a Request for Comments standard; replaced by [RFC 9162](https://www.rfc-editor.org/rfc/rfc9162), which defines the same tree).
The receipts are in [`receipts.jsonl`](../fixtures/packages/hale2026harmonic/receipts.jsonl), and the keys that signed them in [`roster.json`](../fixtures/packages/hale2026harmonic/roster.json).
- **Before the corrections**, `ana` had signed receipt `c5cx7n4tga6y9` for three cells at revision 0.
Two of them never changed, so the receipt still counts for them; for the corrected cell it no longer does, because its hash moved on.
- **After the corrections**, `ana` signs `eqgbbpwb6725v` at F3 for 23 nodes: the cells of Table 1's three data rows, its four column headers, the equation and the sentences around it.
`ben` spot-checks it with receipt `p38t0s91trb6j`: his signature over the receipt picks 10 nodes nobody could have chosen, and he finds no errors.
Two of them never changed, so the receipt still counts for them.
For the corrected cell it no longer counts, because the cell's hash moved on.
- After the corrections, `ana` signs `eqgbbpwb6725v` at F3 for 23 nodes: the cells of Table 1's three data rows (15), its four column headers, the equation, and three sentences (`e4fkseg`, `f929eq9` and `sxgcc9h`, the one holding the equation).
`ben` spot-checks it with receipt `h6yr2ddx6d3ef`.
His signature, made with his separate spot-check key `ben-spot`, picks 10 nodes nobody could have chosen, and he finds no errors.
- `ana` signs `zaxky8c1y12tc` for two sentences of section 1, including the new `r1gacps`.
- `cara` signs `adtrpq41np7sd` for three sentences on page 2, then revokes it herself (`q0yg0w6ygwwxh`): she had compared them with the crops of another paper.
- `cara` signs `adtrpq41np7sd` for three sentences on page 2, then revokes it herself (`q0yg0w6ygwwxh`) because she had compared them with the crops of another paper.
Those sentences fall back to their earlier levels, which is why `eajq93t` is still F0.
- `ana` signs `gena76nkb6cnc` for the figure's caption; `ben`'s spot-check `qab588b6xy94y` finds a missing period in `nmm4md0`, which suspends the receipt until she reviews the sitting again.
- `ana` signs `gena76nkb6cnc` for the figure's three caption sentences. `ben`'s spot-check `tejzs01eqn2ym` finds a missing period in `nmm4md0`, which suspends the receipt until she reviews the sitting again and signs a new one.
The document's report, in [`document.json`](../fixtures/packages/hale2026harmonic/document.json), counts the result: 28 leaves at F3, 12 at F2, 4 at F1 and 1 at F0.
The document's report, in [`document.json`](../fixtures/packages/hale2026harmonic/document.json), counts the result over 45 leaves: 28 at F3, 12 at F2, 4 at F1 and 1 at F0.
The 28 are the 23 nodes of `eqgbbpwb6725v`, the 2 of `zaxky8c1y12tc`, and the 3 caption sentences, which still count because Ben's own valid receipt lists them at F3 [CHECK: confirm this is intended; tools/oracle/receipts.py computes it this way].
## 7. Data out of the table
Table 1 becomes a dataset, `ds-hale-bond-params`, inside the package.
Table 1 becomes a dataset, `ds-hale-bond-params`, inside the package ([spec/15](../spec/15-datasets.md)).
It is a [Frictionless Data Package](https://datapackage.org/standard/data-package/) with units, uncertainty and an origin added, and the format is `provisional`.
Its CSV keeps every digit as printed (`1.526` and `1.5260` stay different), and every value has an anchor to the cell that prints it, so a claim that rests on a value can show the cell.
All twelve value anchors resolve as `current`; the checker confirms each CSV value equals its cell's text.
The descriptor types the fields as numbers; how a generic Data Package tool treats `1.5260` was not verified.
All twelve value anchors resolve as `current`.
The reference checker, [`tools/check.py`](../tools/check.py), confirms each CSV value equals its cell's text.
## 8. Sharing without the text
The team wants to share its notes with a collaborator's machine but not the paper.
[`hale2026harmonic.checks-only`](../fixtures/packages/hale2026harmonic.checks-only/) is the same document with every leaf's text withheld and every hash replaced by an HMAC under the team's key.
IDs, labels, pages and boxes survive, so annotations still land in the right place; only someone holding the key and the text can check the values.
The team wants to share its notes with another machine and keep the paper to itself.
[`hale2026harmonic.checks-only`](../fixtures/packages/hale2026harmonic.checks-only/) is the same document with every leaf's text withheld and every hash replaced by an HMAC under the team's key ([spec/12 §12.5](../spec/12-package.md), [spec/05 §5.8](../spec/05-hashes.md)).
An HMAC is a hash mixed with a secret key.
Anyone can recover a plain hash of a short text such as "310.0" by hashing guesses; a keyed hash resists that.
Corrections, receipts, the roster and the dataset are left out too.
IDs, labels, pages and boxes survive, so annotations still land in the right place.
Only someone holding the key and the text can check the values.
[CHECK: the author wrote "a collaborator's machine"; the fixture uses the team-key scheme (`hmac-team`), which spec/05 §5.8 assigns to exports between the team's own machines].
## 9. Your turn
## 9. Recompute, print and query the package
[The examples](examples.md) show how to recompute one of these hashes with `sha256sum`, print the outline with a few lines of Python, and load the whole package into SQLite to query it.