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>
83 lines
7.0 KiB
Markdown
83 lines
7.0 KiB
Markdown
# 11. Corrections
|
|
|
|
A correction, `scienting-correction/1`, is an operation on an established document, with its evidence and its author.
|
|
Corrections are added and never edited, so a document's history is its original reading plus its corrections.
|
|
Each has an `index`, counted from 1 in the order corrections are applied, and the document's revision is the index of the last.
|
|
|
|
## 11.1 Operations
|
|
|
|
| `op` | Fields | Does | IDs |
|
|
| --- | --- | --- | --- |
|
|
| `replace` | `target`, `find`, `replace`, optional `run` | Replaces the one occurrence of `find` among the target's editable strings with `replace`, a string or, inside a text run, a list of runs. | Unchanged. |
|
|
| `insert` | `parent`, `place_after` (a sibling, or null for first), `node` | Adds a subtree: kinds, labels, attributes and content, with each equation run naming its child as `{"kind": "equation", "child": i}`. | New IDs, minted ([4.2](04-identity.md#42-minting-node-ids)). |
|
|
| `insert` | `target`, `run`, `offset`, `text` | Inserts text into one run of the target, at a code-point offset in that run's editable string. | Unchanged. |
|
|
| `delete` | `target` | Removes an artifact and its subtree, such as a running head read as text. | All retired, without successors. |
|
|
| `split` | `target`, `offset` | Splits a sentence at an offset in its plain projection, which falls between runs or inside a text run; whitespace at the split is removed from both parts, and each equation child goes with its run. | The first part keeps the ID; the second gets a new one. |
|
|
| `merge` | `target`, `with`, optional `joiner` | Joins a node with its next sibling of the same kind: the runs of two run-bearing nodes, with the joiner (default one space) between, or the children of two `paragraph`, `list`, `item`, `quote`, `caption`, `figure-note` or `table-note` nodes. | `with` is retired, with the target as successor. |
|
|
| `move` | `target`, `parent`, `place_after` | Changes reading order or containment. | Unchanged. |
|
|
| `reclassify` | `target`, `kind` | Changes a node's kind, such as a paragraph that is really a caption, to a kind with the same content shape. | Unchanged. |
|
|
| `relabel` | `target`, `label` | Changes a printed label. | Unchanged. |
|
|
| `set` | `target`, `attr`, `value` | Sets one structural attribute: a section's `level`, a table's `columns`, a cell's `column`, `rowspan`, `colspan` or `role`, a row's `header_path`, a list's `ordered`, an item's `marker` or a theorem's `variant`. | Unchanged. |
|
|
|
|
A leaf's editable strings are the `text` of its `text`, `link`, `cite`, `xref` and `fnmark` runs and the `source` of its `math` runs, each searched on its own; an equation's `source`; a verbatim node's `text`.
|
|
`set` exists because readings drop spans, roles and header paths; see the [rationale](../docs/rationale.md#corrections).
|
|
Runs are made canonical after every operation, and the result MUST satisfy every rule of [3](03-nodes.md).
|
|
|
|
## 11.2 Every correction records
|
|
|
|
| Property | Holds |
|
|
| --- | --- |
|
|
| `evidence` | At least one item: `crop`, `witness` or `source-file` by SHA-256, or `person`. |
|
|
| `author` | `{key}` for a person in the roster, or `{model, run}` for a model's session. |
|
|
| `reason`, `at` | Why, and when. |
|
|
|
|
A proposal, such as one a tool makes, carries these and `status: "proposed"`, and no applied fields.
|
|
|
|
## 11.3 Applied fields
|
|
|
|
When a correction is applied, its record gains:
|
|
|
|
| Property | Holds |
|
|
| --- | --- |
|
|
| `index`, `id` | Its position, and its record ID. |
|
|
| `before`, `after` | The target's `node_hash` before and after, or null; for an inserted subtree, `target` is its root. |
|
|
| `path` | The target's ancestors when it was applied, root first; for an insert, the new subtree's. |
|
|
| `to_path` | For a move, the new ancestors. |
|
|
| `touched` | The leaves whose content the correction changed, sorted: the target of a `replace`, text `insert`, `relabel` or `set` on a leaf; the new leaves of a node `insert`; the deleted leaves of a `delete`; both parts of a `split`; both nodes of a run `merge`, or the moved-in leaves of a container `merge`; the moved leaves of a `move`; the leaves of a reclassified node. |
|
|
| `created`, `retired` | IDs minted or retired. |
|
|
| `from` | The label, kind or attribute value before a `relabel`, `reclassify` or `set`. |
|
|
|
|
Retirements are also entered in `ids.jsonl` with the correction's index as `rev`.
|
|
Drift ([6.6](06-anchors.md#66-drift)) is computed from `path`, `to_path` and `touched` alone, so it needs no replay.
|
|
|
|
## 11.4 The gate
|
|
|
|
A correction is refused, naming the rule ([22](22-conformance.md)), when:
|
|
|
|
1. it does not match the correction schema, `schemas/correction.schema.json`;
|
|
2. a target, parent or sibling is not live, or the root would be deleted, moved, merged or reclassified;
|
|
3. `find` is empty or does not occur exactly once;
|
|
4. an `insert` has no crop evidence, or names a run or offset its target does not have;
|
|
5. a digit changes without crop evidence: in a `replace` whose `find` and replacement have different digits, a `delete` of leaves that hold digits, or a `relabel` whose labels have different digits;
|
|
6. any leaf outside `touched` has different digits afterwards;
|
|
7. a `split` falls outside the sentence, inside a run other than text, or leaves a part empty;
|
|
8. a `merge` joins nodes that differ in kind, are not mergeable, or are not adjacent siblings;
|
|
9. a `move` would put a node inside itself;
|
|
10. a `reclassify` changes the content shape, or a `relabel` or `set` targets a kind that does not take it; or
|
|
11. the result breaks a rule of [3](03-nodes.md).
|
|
|
|
The gate needs only the document's current state, so a tool can run it on a proposal before applying anything, and get back the record the correction would append or the rule it breaks.
|
|
`tools/validate.py --correction` does this against a package.
|
|
|
|
Digits are characters of General Category Nd in the plain projection.
|
|
A package's corrections are also checked as a whole: indexes run from 1 without gaps, each `id` derives from its record, `rev` equals their number, every retired ID is in `ids.jsonl`, and the last correction of each leaf ends at its current `node_hash`.
|
|
`corrections.jsonl` in the vectors applies each operation, and each refusal, to a small document.
|
|
|
|
## 11.5 The history root
|
|
|
|
`document.json` carries `history`: the root of the tree of [8.5](08-fidelity.md#85-merkle-roots) whose items are `["correction", index, id]` for each correction in order, or SHA-256 of the empty string when there are none, which is how RFC 9162 hashes the empty tree.
|
|
Because corrections are only appended, a consistency proof (RFC 9162 §2.1.4) between the roots at two revisions shows that the later history extends the earlier one rather than rewriting it.
|
|
The proof is a `scienting-proof/1` with `kind` `consistency`, holding the `doc`, `from_rev`, `to_rev`, `from_root`, `to_root` and the `path` as hex hashes; a verifier applies RFC 9162 §2.1.4.2.
|
|
A checks-only package carries no corrections, so its history is the empty root.
|
|
`proofs.jsonl` gives consistency proofs for every prefix of small trees and for the example paper between revisions 8 and 12; `fixtures/records/proof-consistency.json` is the latter.
|