Files
paperspec/docs/rationale.md
T
alexm 78101dc0db
check / check (push) Successful in 52s
site / site (push) Successful in 1m45s
Reorganize paperspec commands and pin dependency versions (#2)
Reviewed-on: #2
2026-10-15 09:53:05 -04:00

321 lines
22 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Rationale
Each choice below is one a reader is likely to question.
The specification states the rule, and this page gives the reason.
This page is for newcomers, implementers and experts.
Newcomers find each term defined where it first appears.
Implementers find the chapter that defines each rule.
Experts find the cost of each choice and, where a neighboring standard chose differently, how it differs (the full comparison is in [comparison.md](comparison.md)).
The formats are at version 0.1.0.
Of the 29 in `registry/formats.json`, 19 are `candidate` and 13 are `provisional`, and the provisional ones are named where they appear.
The repository is paperspec, at <https://git.scient.ing/infra/paperspec>.
The `scienting-` prefix in format names such as `scienting-anchor/1` is the formats' namespace, separate from the repository's name.
## Identity
### IDs carry no meaning
Every sentence, cell and other node has an ID, a short name that other records use to point at it.
An ID that encoded a page, an ordinal or a kind would change when a converter orders blocks differently, or when a paragraph turns out to be a caption.
An ID is minted once from the document key, the node's kind, the hash of its text and an occurrence count that separates identical texts, and is then frozen ([spec/04 §4.2](../spec/04-identity.md#42-minting-node-ids)).
A reclassification leaves it as it was, and a fresh conversion of the same input mints the same IDs.
A different converter mints different ones for some sentences: those the alignment pairs with the established reading keep their IDs, and every difference becomes a suspect for review ([spec/05 §5.6](../spec/05-hashes.md#56-alignment)).
A content address such as an [IPFS content identifier](https://docs.ipfs.tech/concepts/content-addressing/) or a [Software Heritage identifier](https://www.swhid.org/specification/v1.2/5.Core_identifiers/) is a function of content at all times.
That suits immutable data and archived source code.
Any correction would rename the sentence.
Here the ID is derived from content once and kept apart from the content hashes, so the pair of ID and hash says whether the text changed.
The cost is that two conversions that read a sentence differently by one character mint different IDs for it.
### IDs never move, and retired IDs leave a trail
Everything a team builds points at IDs: notes, claims, dataset values, citations in drafts.
A merge records its successor, a deletion records that there is none, and legacy IDs live on as aliases, all in `ids.jsonl` ([spec/04 §4.3](../spec/04-identity.md#43-permanence-retirement-and-aliases)).
### Work keys carry a hash
A citation key such as `vaswani2017attention` is what readers say, and it collides: a preprint and its published version, or two papers by one first author in one year with the same first title word, would share it.
Four characters of a hash of the work's identity, its DOI when it has one, settle the collision and let any tool holding the DOI recompute the key.
The key is minted once and never recomputed, so a corrected title cannot change a name someone has cited; when two works still share four characters, the later one gets six ([spec/02 §2.4](../spec/02-model.md#24-work-and-document-keys)).
The rule is the one the studio's reading-list tool already uses, so a paper keeps its key from the reading list to the store.
### Identifiers are tag URIs
Layers, annotations and documents name themselves with `tag:scient.ing,2026:` URIs (RFC 4151) rather than `urn:` names, because a URN namespace must be registered with IANA and this one is not.
A tag URI needs only a domain and a date, and these identifiers sit inside hashed annotations, so the cheap time to choose was before release.
### Revisions are counted, not timed
Anchors are made on different machines and packages are imported later, and clocks disagree.
A correction index does not disagree with itself.
A document's revision is the index of its last correction ([spec/06 §6.2](../spec/06-anchors.md#62-the-anchor), [spec/11](../spec/11-corrections.md)).
## Hashes and projections
A hash is a fixed-length fingerprint of data: equal data gives equal hashes, and any change gives a different one.
Every hash here is SHA-256 ([spec/01 §1.3](../spec/01-conventions.md#13-hashes)).
A projection is a rewritten form of a text, made only to be hashed.
### Canonical JSON
Canonical JSON is JSON (JavaScript Object Notation, a text format for structured data) written in exactly one way, so equal data always hashes the same.
The formats use the JSON Canonicalization Scheme, [RFC 8785](https://www.rfc-editor.org/rfc/rfc8785) (an RFC, or Request for Comments, is a numbered Internet standards document), which requires numbers to be IEEE 754 doubles (IEEE is the Institute of Electrical and Electronics Engineers) (the standard binary floating-point numbers), and add one rule: a decimal that carries meaning is a string, so `310.0` and `1.5260` keep their printed digits ([spec/01 §1.1 and §1.2](../spec/01-conventions.md)).
The cost is that a reader must treat those strings as decimals itself.
### Five values, not one
A text node carries five values ([spec/05 §5.1](../spec/05-hashes.md#51-the-five-values)), because no single hash does all of their jobs.
- Quotations need every character, so `text_hash` covers the normalized text.
- Certifying that two witnesses agree needs notation folded but structure kept, which is `match_hash`.
A witness is one file the text was read from.
- Proposing that two sentences are the same needs something looser still, which is `align_key`.
- Containers need a hash of their own content, `own_hash`, apart from the `node_hash` that also covers what they hold.
Table columns and figure-text items have IDs but are not nodes, so they carry no hashes ([spec/09 §9.1](../spec/09-figures-tables-equations.md)).
### Only the strict projection certifies
OCR (optical character recognition, which reads text from page images) drops superscripts.
A projection loose enough to match "103" with 10³ would certify an error, because the printed value is 1000 and the reading is 103.
The lossy key may propose that match.
Only the strict hash may accept it, and then only when the complex-math keys agree too ([spec/05 §5.3](../spec/05-hashes.md#53-the-strict-projection)).
### Citation marks stay marked in the strict projection
"10" followed by citation 3 would agree with the text "103" without the marks.
### Decimal points and commas count as simple notation
Simple math is rewritten to one linear form that can match plain text; anything else is complex and is compared by its source. `$-1.5$` in a LaTeX source would be complex math without the two characters, and could never agree with "−1.5" in the PDF ([spec/05 §5.4](../spec/05-hashes.md#54-simple-notation)).
### A hyphen or en dash before a digit is a minus sign in the strict projection
Text layers and OCR disagree about dashes in "10–20" and "−5".
Folding them before digits makes witnesses agree without touching a dash inside a name such as "CT–CT".
### The lossy key deletes whitespace and dashes
OCR merges and splits words and line-break hyphens, and a key that only proposes alignments should forgive that.
It also folds case ([spec/05 §5.5](../spec/05-hashes.md#55-the-lossy-projection)).
### Offsets index the stored text, and structural hashes cover it exactly
An offset counts Unicode code points, the numbered characters of Unicode, into the node's stored plain text ([spec/01 §1.5](../spec/01-conventions.md#15-character-offsets)), and own content is hashed as stored.
Every stored offset is still exact when `node_hash` is unchanged, and a renderer can slice the stored text without normalizing it first.
Only the three text values, `text_hash`, `match_hash` and `align_key`, normalize.
### The character data is pinned to one Unicode version
Normalization and General Categories change between Unicode versions, and both enter every hash.
The chapters pin Unicode 16.0, the reference implementation refuses to import on another version, and moving to a later one is a change to every text-hashing format ([spec/01 §1.4](../spec/01-conventions.md#14-text-normalization-for-hashing)).
### Hashes that leave the team are keyed
A plain hash of "310.0" is recovered by hashing guesses, and overlapping anchors can rebuild passages.
Inside a team, plain hashes are fine.
An export to another machine of the team uses an HMAC (hash-based message authentication code), a hash that needs a secret key, under the team key.
A public layer, or any export of a work whose text is withheld, uses a key derived from the paper's own page fingerprints, which only someone holding the paper can compute ([spec/05 §5.8](../spec/05-hashes.md#58-keyed-hashes)).
## Anchors
An anchor is a stored pointer to a node or to a quoted span inside it, with enough context to find it again after the text changes ([spec/06](../spec/06-anchors.md)).
The [World Wide Web Consortium (W3C) Web Annotation model](https://www.w3.org/TR/annotation-model/) also anchors with quotations: its `TextQuoteSelector` takes `exact`, `prefix` and `suffix`, and its `TextPositionSelector` is described there as very brittle.
An anchor here keeps the quote with up to 32 code points of prefix and suffix and adds hashes, so resolution has exact outcomes (`current`, `changed-below`, `moved`, `reattached`, `stale`).
Hypothesis, in [its description of its own anchoring](https://web.hypothes.is/blog/fuzzy-anchoring/), matches fuzzily.
Annotation layers use a custom `AnchorSelector` followed by a standard `TextQuoteSelector` fallback for tools that do not know ours ([spec/18 §18.3](../spec/18-layers.md)).
The cost is a selector type no generic tool defines.
For annotation in general, the W3C model is the better choice.
### Every anchor carries `own_hash`, and anchors on containers without content carry their leaves
Telling a relabel from a real change needs the anchored `own_hash`.
Deciding whether a merged paragraph still holds "at least half" of what was anchored needs the original leaves.
### Long quotations become a hash and a length
Quotations over 200 code points are replaced by a hash and a length, which keeps anchors small and limits how much text an anchor carries.
A hashed window can still be found again in the node.
### Prefix and suffix pick between repeated quotes
"The value" may occur twice in a sentence.
The context around it decides which one was meant.
### `changed-below` reports drift
A note on a section stays valid when a typo inside it is fixed.
By default the drift is `high` once the leaves touched by corrections since the anchor was made exceed a quarter of the leaves involved, and the note is then flagged for review ([spec/06 §6.6](../spec/06-anchors.md#66-drift)).
### A row's header path is not hashed
Rows have no content of their own, and a row's `header_path` is not part of it.
Inserting a dropped row-group header does not stale the anchors on every cell beneath it ([spec/09 §9.2](../spec/09-figures-tables-equations.md)).
## Corrections
### Corrections are appended, never edited
A document's history is its first reading plus its corrections, so anyone can see what changed, when and why, and on what evidence ([spec/11](../spec/11-corrections.md)).
### Corrections include a `set` operation
Readings drop spans, roles and header paths, not only words, and those need a correction too.
### Applied corrections record what they touched
Drift is computed from the records alone, with no need to replay history, because each record carries `path`, `to_path` and `touched` ([spec/11 §11.3](../spec/11-corrections.md#113-applied-fields)).
### The history has a root
Corrections are append-only in principle, but nothing in a package proved that a later revision extends an earlier one rather than rewriting it.
A Merkle root over the corrections in `document.json`, with RFC 9162 consistency proofs between revisions, does ([spec/11 §11.5](../spec/11-corrections.md#115-the-history-root)).
### Digits need image evidence
A wrong digit changes a result without looking wrong, so no digit changes without a crop of the page as evidence, and no leaf outside the correction's `touched` list may end up with different digits ([spec/11 §11.4](../spec/11-corrections.md#114-the-gate)).
## Geometry
### Pages have fingerprints
Publishers stamp each download, so file hashes cannot identify a paper across teammates' copies.
Fingerprints with the stamps removed can ([spec/07 §7.1](../spec/07-geometry.md#71-page-fingerprints)).
### Fingerprint text deletes whitespace entirely
PDF text extractors disagree about where to insert spaces, and the fingerprint must come out the same in any two implementations.
## Review
### People sign receipts
Nobody signs three thousand times, so one signature covers a Merkle root of everything reviewed in a sitting.
A Merkle tree hashes items in pairs, then those hashes in pairs, up to one root, and allows a proof that one node was covered without revealing the others.
The receipt tree follows [RFC 6962](https://www.rfc-editor.org/rfc/rfc6962), whose replacement [RFC 9162](https://www.rfc-editor.org/rfc/rfc9162) defines the same tree.
The specification makes a receipt with no items invalid, where the RFC hashes the empty list ([spec/08 §8.5](../spec/08-fidelity.md#85-merkle-roots)).
The tree is not the one behind `node_hash`, which hashes each node's `own_hash` with its children's `node_hash` values.
### Spot-check seeds are VRF proofs
A seed the reviewer could influence could be ground until the sample fell on careful work, and a seed the checker could vary could be ground by the checker.
An earlier draft used the checker's Ed25519 signature and called it unchoosable because signing is deterministic; that holds only for an honest checker, since a verifier accepts any valid signature and a dishonest one can sign with any nonce.
A verifiable random function (RFC 9381) has exactly one valid proof for a key and an input, which is the property the rule needs, and its keys are ordinary Ed25519 keys, so the roster needs nothing new.
Anyone can verify the proof and recompute the sample ([spec/08 §8.8](../spec/08-fidelity.md#88-spot-checks)).
### A receipt can be proven without being shown
The receipt tree follows RFC 9162 so that an inclusion proof, a dozen hashes, shows a buyer that one cell was proofread without handing over the receipt or the other nodes ([spec/08 §8.5](../spec/08-fidelity.md#85-merkle-roots)).
A transparency log of receipts, revocations and rosters ([spec/23](../spec/23-log.md)) uses the same tree and the checkpoint format transparency logs share, so that a receipt's time and the roster that governed it stop being self-asserted.
### Error rates are reported as bounds
One clean sample of ten proves little.
A one-sided Clopper–Pearson upper bound, by default at confidence 0.95, across all of a reviewer's samples says what can be claimed.
With no errors it is about 3 divided by the number sampled ([spec/08 §8.9](../spec/08-fidelity.md#89-reviewer-error-bounds)).
### Levels fall back
A node counts as proofread only through a valid receipt that lists it at its current `node_hash`, so a correction never leaves a stale "proofread" behind ([spec/08 §8.1](../spec/08-fidelity.md#81-levels-of-fidelity)).
## Rights
### Rights are recorded per witness
What a team may do depends on how it obtained the paper: a subscription, a mining license, an open license, its own work.
### AI uses have two axes
Reviewers ask whether a model may see the text, where it runs, and whether the text is kept there.
The record names a purpose, `internal` or `external` depending on whether results leave the organization, and a processor: `local`, `tenancy`, `outside-no-retention` or `outside` ([spec/14 §14.1](../spec/14-rights.md#141-the-rights-record)).
### Unknown means no
A stage whose rights are unknown does not run.
A project owner settles the question with a signed policy, so the decision is the team's and is on the record.
A policy fills in unknowns and never overrides an explicit answer.
A project setting can turn the refusal into a warning.
The refusal is the default, and the project format is `provisional` ([spec/20](../spec/20-projects.md)).
### Backend licenses are part of the gate
A converter's code and its model weights can be licensed differently, and the weights may exclude commercial or competing use.
A condition whose answer is unknown counts as not met ([spec/14 §14.4](../spec/14-rights.md#144-backends-and-profiles)).
## Packages and databases
### Canonical JSON, one record per line
A corrected sentence is a one-line diff, and every file has exactly one valid byte sequence, so a hash of the manifest pins the whole package ([spec/01 §1.9](../spec/01-conventions.md#19-files), [spec/12 §12.3](../spec/12-package.md#123-the-manifest)).
A [Frictionless Data Package](https://datapackage.org/standard/data-package/) lists resources with an optional `hash` that defaults to MD5 (an older hash function).
The package manifest here lists every other file with SHA-256 and size.
The dataset format, which is `provisional`, is a Data Package with units, uncertainty, an origin and anchors to the printed values added ([spec/15](../spec/15-datasets.md)).
Two neighboring standards cover other parts of this ground, RO-Crate and the PROV Ontology.
[RO-Crate](https://www.researchobject.org/ro-crate/) describes research data with schema.org annotations in JSON-LD, and it is the better choice for general description and discovery.
No mapping between it and these packages is defined.
Provenance is a plain `provenance.json` of tools, backends, cached readings and stages.
It is not the [PROV Ontology](https://www.w3.org/TR/prov-o/), which other systems can query generically.
### Checks-only packages drop everything that carries text
Corrections carry their find and replace strings, datasets carry values, suspects carry the readings compared, and figure text and reference records carry text.
A checks-only export omits them all, withholds the content of every leaf, and keys its hashes ([spec/12 §12.5](../spec/12-package.md#125-text-policies)).
### SQL avoids reserved words in every engine
The same tables run on SQLite, Dolt (MySQL-compatible) and PostgreSQL, and no name is a word reserved in any of the three.
The formats' `key`, `index`, `at`, `by`, `before` and `after` become `doc_key`, `idx`, `made_at`, `signer`, `before_hash` and `after_hash` ([spec/13 §13.1](../spec/13-database.md#131-portability)).
### Loading and unloading are exact inverses
A team can keep its documents in a database and still hand anyone the package, byte for byte.
The conformance suite checks this for every fixture ([spec/13 §13.3](../spec/13-database.md#133-load-and-unload)).
## Questions the formats leave open
These are settled for now, and a future version may revisit them.
| Question | Current answer |
| --- | --- |
| Should node IDs carry a kind prefix, such as `s-k3m9q2a`, to help people debugging? | No: a reclassified node keeps its ID, so a prefix would become wrong. |
| Should `text_hash` normalize Unicode, or only collapse whitespace? | Both: Unicode Normalization Form C, then whitespace collapsed to single spaces and trimmed ([spec/01 §1.4](../spec/01-conventions.md#14-text-normalization-for-hashing)). |
| How long may a quotation in an anchor be? | 200 code points. |
| How is complex math compared? | By its source, run by run; comparing canonical MathML is a possible later addition. |
| What extension and media type do zipped packages use? | `.sct.zip` and `application/vnd.scienting.doc+zip`. |
| Which drift threshold, spot-check rate and digit rule should teams use? | By default, drift is high above a quarter of the leaves involved, and spot-checks sample 5% of a receipt's nodes, at least ten. The digit rule is `all`, `anchored-sections` or `none`; each is set per project. |
| How should rights combine when a document comes from witnesses held on different bases? | The most restrictive witness governs each use. |
## Looking ahead
Choices made so that the formats still serve in the 2030s, and the ones deliberately left open.
- `ml-dsa-65` (FIPS 204) is a signature scheme a receipt may carry today, with a pure-Python reference implementation that reproduces NIST's test vectors, so a team can sign with a post-quantum key before Ed25519 is deprecated and verify such receipts decades on.
`cose` is reserved for the IETF's SCITT transparency services; a consumer treats an unimplemented scheme as invalid, so adopting it is a minor version ([spec/08 §8.4](../spec/08-fidelity.md#84-record-hashes-and-signatures)).
- Every hash is SHA-256, and `document.json` names the function, so a later algorithm is a new major version that can share a store with this one rather than a silent reinterpretation ([spec/01 §1.3](../spec/01-conventions.md#13-hashes)).
- A roster key may carry its `did:key`, which the public key determines, so a signer has one name inside and outside the store ([spec/08 §8.7](../spec/08-fidelity.md#87-revocations-and-the-roster)).
- A model that proposes a correction or an annotation is named by its weights, instructions and parameters by hash, and by where it ran ([spec/11 §11.2](../spec/11-corrections.md#112-every-correction-records)).
- The transparency log has a static tile layout, witnesses in the roster, and an acceptance rule in the project's settings; what remains is to run one ([spec/23](../spec/23-log.md)).
- The VRF reference implementation proves in about 5 ms and verifies in about 4 ms in plain Python, which is fine for fixtures and small logs; a production verifier can use any RFC 9381 library, since the proofs are the standard's bytes.
## Known limits
- Notation drops spaces and text keeps them, so `$T = 300$` from a LaTeX source and "T = 300" from a PDF disagree, and become a `word` suspect for review.
- Page fingerprints are computed from extracted text lines.
Two implementations that should agree on fingerprints must extract lines the same way.
- The formats are built for a team-scale corpus of hundreds of documents.
They do not replace publisher interchange formats, and they do not fix what a converter reads wrongly.
They record it and make it checkable.