Keys (spec/02 §2.4), the rule paperstack settled on:
- A work key is a Scholar-style citation key, a hyphen and the first four
Crockford base32 characters of SHA-256("scienting-key/1:" + identity),
where the identity is the first of doi, arxiv, isbn, pmid, pmcid and
openalex in same_as, else the folded title, year and family name. A
collision lengthens the later key by two characters; a key is minted
once and never recomputed. A document key adds .si, .v<seq> or .<type>
for versions other than the version of record.
- Every fixture key is now minted (hale2026harmonic-damb,
okafor2025solvent-acth, writer2027fixed-ynr7.v3, tiny2026tiny-j4qm), so
every node and record ID was re-minted and the docs rewritten to match.
The validator checks the document key against the work key and version
(doc.key.work) and the hash part against the identifier (doc.key.suffix).
vectors/keys.jsonl pins the cases paperstack's tests pin.
Review:
- Spot-check seeds are ECVRF-EDWARDS25519-SHA512-TAI proofs (RFC 9381),
implemented in tools/oracle/vrf.py in plain Python and reproduced
against the RFC's Example 16; the sample ranks by the VRF output. The
earlier Ed25519 seed could be ground by a dishonest checker.
- RFC 9162 inclusion and consistency proofs (crypto.inclusion_path,
verify_inclusion, consistency_proof, verify_consistency) with
scienting-proof/1, vectors/proofs.jsonl and two fixture records.
- document.json carries `history`, the root over the corrections, with
the empty-tree hash for none (spec/11 §11.5, rule doc.history).
- A provisional transparency log (spec/23): log entries, signed-note
checkpoints with key hashes as transparency logs share them, the rule
that a logged revocation beats a self-asserted time, and vectors.
- Reserved signature schemes cose and ml-dsa-65 (spec/08 §8.4).
Identifiers and text:
- urn:scienting: becomes tag:scient.ing,2026: (RFC 4151), since the URN
namespace was never registered.
- Unicode 16.0 is pinned for normalization and categories (spec/01 §1.4);
the checker refuses another version.
Checking:
- check.py: keys, proofs, VRF and checkpoint vectors; the proof and
checkpoint records; a check that every ID and hash the prose quotes
exists in the fixtures or vectors; three more broken packages
(history-wrong, key-suffix, key-work).
- stress.py: key, VRF, proof and checkpoint properties.
- Docs: a keys section in the concepts and FAQ, rationale entries for
keys, VRF, proofs, tag URIs and the Unicode pin, a "Looking ahead"
section, media-type registration and OCI distribution notes.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
13 KiB
8. Fidelity, receipts and spot-checks
8.1 Levels of fidelity
| Level | Meaning |
|---|---|
| F0 converted | One reading, not corroborated. |
| F1 resolved | Every suspect in the node was answered against the page image and passed the gate. |
| F2 corroborated | An independent witness agrees, by match_hash and complex keys, or the text is born digital, as a Typst or LaTeX source is. |
| F3 proofread | A named person compared the node with the page image, and a valid receipt they signed lists the node at its current node_hash. |
Levels apply to leaves.
The pipeline records F0, F1 or F2 in a leaf's fidelity; F3 comes only from receipts.
A correction that changes a leaf drops it back: the gate's crop evidence makes it F1 at most, until it is corroborated or proofread again.
8.2 Levels
A leaf's level is the highest of its recorded fidelity and the levels of every valid receipt (8.6) that lists it at its current node_hash.
A receipt at a lower level never lowers a node; only a revocation, a failed spot-check or a correction does.
A spot-check's qa: receipt (8.8) raises the nodes it sampled, except those its errors name.
A document reports, in document.json, the lowest level among its leaves and the count at each level.
8.3 Receipts
A paper has thousands of nodes, and nobody signs thousands of times, so people sign receipts: one signature over a Merkle root of everything reviewed in a sitting.
| Property | Required | Holds |
|---|---|---|
format |
Yes | scienting-receipt/1. |
id |
Yes | The record ID (4.6). |
doc |
Yes | The document key. |
rev |
Yes | The revision reviewed. |
level |
Yes | F1, F2 or F3. |
scope |
Yes | all, section:<id>, page:<n>, targeted:<name> (such as targeted:lode), or qa:<receipt id> for a spot-check. |
nodes |
Yes | [[id, node_hash], …], sorted by ID without repeats. |
records |
No | [[kind, id, hash], …] for other things signed in the sitting: annotation, digitization, correction, layer or checklist. |
root |
Yes | The Merkle root (8.5). |
evidence |
Yes | What the reviewer looked at: {kind, sha256, node?, page_fp?, box?, note?} with kind crop, witness or source-file, or {kind: "person", by}. A regenerated crop is different evidence. |
by |
Yes | The signer's roster key. |
at |
Yes | When it was signed. |
qa |
For qa: scopes |
The spot-check (8.8). |
export_of |
No | The receipt this one re-issues under a keyed scheme. |
keyed |
No | The scheme its values are keyed under, when it leaves the team. |
signature |
Yes | {scheme, key, value} (8.4). |
A receipt lists nodes, records, or both.
A correction invalidates a receipt for the corrected node only, because the node's node_hash no longer matches; the rest still counts.
A receipt shared outside the team cannot carry plain node hashes, which would leak short texts, and its signature covers them.
So an export re-issues it: a new receipt with export_of, values under the export's key, signed by the original reviewer or a team key with the export purpose.
8.4 Record hashes and signatures
A signed record's record hash is SHA-256 of the canonical JSON of the record without id and signature.
Its id is the 13-character letter-first encoding of that hash.
Its signing payload is the canonical JSON of {"format": <the record's format>, "hash": <the record hash>}.
| Scheme | value |
|---|---|
ed25519 |
The RFC 8032 signature of the payload, in standard Base64 with padding. |
ssh |
An SSH signature of the payload in the namespace scienting, armored. |
gpg |
A detached, armored OpenPGP signature of the payload. |
sigstore |
A Sigstore bundle for the payload, as JSON text. |
cose |
Reserved: a COSE_Sign1 message over the payload, in Base64, as the IETF SCITT work shapes signed statements, so other transparency services can verify a receipt. No vector yet. |
ml-dsa-65 |
Reserved: an ML-DSA-65 signature (FIPS 204) of the payload, in Base64, for the time Ed25519 is no longer considered safe. No vector yet. |
A consumer treats a record signed with a scheme it does not implement as invalid, so a reserved scheme can be adopted by a new minor version without any format changing.
signature.key is the signer's roster key and equals by.
Self-hosted deployments sign with team keys or a private Sigstore deployment; the public Sigstore instance, whose log permanently records each signer's identity, is for public layers only.
signatures.jsonl verifies one receipt and the RFC 8032 test vector.
8.5 Merkle roots
A receipt's items are ["node", id, node_hash] for each listed node, in order, followed by its records.
The root follows RFC 6962: a leaf hash is SHA-256 of the byte 0x00 and the item's canonical JSON; an interior hash is SHA-256 of 0x01 and the two child hashes; a list of n > 1 items splits after the largest power of two less than n.
RFC 6962 has since been replaced by RFC 9162, which defines the same tree.
A root over one item is that item's leaf hash; a receipt with no items is invalid.
merkle.jsonl gives roots for one to seven items.
The tree allows a proof that one node was covered without revealing the others.
An inclusion proof, scienting-proof/1 with kind inclusion, holds the receipt, the item, its index among the receipt's items, the size of the tree, the root and the path: the audit path of RFC 9162 §2.1.3.1, as hex hashes, leaf-most first.
A verifier recomputes the root from the item's leaf hash and the path as RFC 9162 §2.1.3.2 describes and compares it with the receipt's root.
proofs.jsonl gives the path for every item of trees of one to seven items, and fixtures/records/proof-inclusion.json proves one node of the example paper's first receipt.
8.6 Receipt status
A receipt is:
- invalid when its root, ID or signature does not verify, or its signer's key was not valid when it was signed (
registered_at≤at<revoked_at); - otherwise revoked when a valid revocation names it, or names its signer with a period that contains its
at; - otherwise suspended when a valid spot-check of it found an error;
- otherwise valid.
Only valid receipts set levels. A suspended receipt stays suspended; its reviewer reviews the sitting again and signs a new receipt, which needs its own spot-check. A spot-check is itself a receipt, so its status may depend on its own spot-check; statuses are settled from the end of each chain, and the receipts of a chain that loops back on itself are invalid.
8.7 Revocations and the roster
A revocation, scienting-revocation/1, names one receipt, or a signer and a period {from, to}, with a reason, and is signed like a receipt.
It is valid when its signature verifies and its signer is the revoked receipt's signer, the signer it names, or a key whose role in the roster is owner.
The roster, scienting-roster/1, lists the keys that may sign: each with an id, a person, a role, a scheme, its public key (Base64 for Ed25519), its purposes (sign, spot-check, export), registered_at and revoked_at.
The role is the holder's role in the team that keeps the roster, owner, editor, reader or guest, as in a project (20).
Because the roster carries it, a package that carries its roster carries everything needed to judge its revocations.
A package MAY carry the roster its receipts need as roster.json, so its receipts verify without the store.
The roster is meant to be the one a store verifies against; see the rationale.
8.8 Spot-checks
For each F3 receipt, a second reviewer checks a sample of its nodes and signs a qa: receipt.
A seed the reviewer controls could be ground, by varying the receipt until the sample falls on careful work, and a seed the checker controls could be ground by the checker, so the seed is a value neither can steer:
- The seed is the checker's VRF proof, under RFC 9381's ECVRF-EDWARDS25519-SHA512-TAI, for the input that is the canonical JSON of
{"format": "scienting-spot-check/1", "receipt": <the original's record hash>}, made with an Ed25519 key registered in the roster with thespot-checkpurpose before the original was signed. A verifiable random function has exactly one valid proof for a key and an input, where an Ed25519 signature has many, so the checker cannot try proofs until the sample suits, and the reviewer cannot predict the proof without the checker's private key. - The sample size is the smaller of n and the larger of
minimumand ⌈rate× n⌉, for the original's n nodes; the defaults are a rate of0.05and a minimum of 10. - Rank the original's node IDs by the hexadecimal SHA-256 of the proof's VRF output, the 64 bytes
ECVRF_proof_to_hashgives, followed by the ID's UTF-8, then by ID, and take the first ones.
The qa object records of, key, seed (the 80-byte proof, Base64), rate, minimum, the sample sorted by ID, and the errors found, each {node, kind?, note?}.
A verifier checks the proof against the checker's public key and the input before it recomputes the sample.
vrf.jsonl pins the VRF on the RFC's own example and on the example paper's seed.
The qa: receipt's nodes are the sampled nodes at the hashes the original lists, its scope is qa: and the original's ID, every error names a sampled node, and it is signed at or after the original.
Anyone can recompute the sample; spotcheck.jsonl does.
The payload's identifier, scienting-spot-check/1, is registered with the other formats so that no file ever carries it by mistake; it names a signing payload, never a file.
8.9 Reviewer error bounds
One sample proves little; what someone relying on the work needs is a bound on each reviewer's error rate across all their samples.
With k errors in n sampled nodes, the bound is the one-sided Clopper–Pearson upper confidence bound at confidence c, by default 0.95: the rate p at which the probability of at most k errors in n is 1 − c, or 1 when k = n.
It is reported to six decimal places, rounded up.
With no errors the bound is about 3/n, so zero errors in 300 sampled nodes bounds a rate below 1%; bounds.jsonl gives cases.
A QA report, scienting-qa-report/1, states for each reviewer who signed a delivery the nodes sampled, the errors found, the confidence, the method clopper-pearson and the bound.
It is the record a buyer keeps, so it is signed like a receipt (8.4): by names the roster key of the team that made the delivery, id derives from the record hash, and signature.key equals by.
8.10 Targeted F3
Most of a paper is never relied on, so a team proofreads the target set: the nodes its claims, data and quotations rest on.
- The anchored nodes: every live node that an extract, a dataset value, a confirmed
claim-link, a draft quotation or a pinned citation anchors, each anchor resolved first (6.5). An anchored leaf is a target; an anchored container contributes its leaves. - Every
equationandequation-line. - Under the project's digit rule (20): with
all, everysentenceandcellwith a digit in atextormathrun; withanchored-sections, only those in a section that holds an anchored node; withnone, none. Digits are characters of General Category Nd. Digits incite,xrefandfnmarkruns do not count, so citation marks, "Figure 3" and footnote marks pull nothing in. A node's section is its nearest ancestor-or-self of kindsection, or thefrontorbackmatter when it has none.
The set is reported in document order, each node with the first reason that applies: anchored, equation or digits.
Neighbors shown for context are not in the set, and nothing new comes to rest on a node below F3 without the node joining the set.
targets.jsonl gives the set of fixtures/packages/hale2026harmonic-damb under each digit rule.
8.11 Checklists
An export of a document, such as accessible HTML, can be checked against a standard, and that check is a record a buyer keeps.
A checklist, scienting-checklist/1, holds the doc and rev checked; the standard, {name, level?, url?}, such as WCAG 2.1 at level AA; the export checked, {kind, sha256, size?}, so the record binds to one file; its items, each a criterion, its result (pass, fail or not-applicable), the method (manual, or automated with the tool and its version) and an optional note; and by and at.
Its content hash is SHA-256 of the canonical JSON of the record without id, and its id is the record ID of that hash (4.6).
A checklist carries no signature of its own: the person who checked signs a receipt that lists it as ["checklist", id, hash] (8.3), so one sitting covers a checklist together with the nodes read for it.
The receipt covers the checklist only at that hash, so a checklist changed afterwards is uncovered.
fixtures/records/checklist/ holds a checklist for an accessible HTML export of the example paper and the receipt that covers it.
The format is provisional (0.4).