feat: trace annotation body for traced citations; paperspec.traces

A trace follows one citation of a sentence of a citing paper to a passage of the
cited paper (spec/18 section 18.5). Minor change: a new body kind, optional
properties and two rules, with no valid file made invalid.

- annotation schema branch (source, selector, reference, note, verdict, proposer;
  proposer required for a Software creator); body_kind in sql/store.sql; the note
  and proposer terms of the JSON-LD context; rules layer.trace.source and
  layer.trace.target.
- paperspec.traces: resolve(annotation, citing_state, cited_state, same_as) gives
  current, changed, stale, unlinked or misresolved with open and cites; problems,
  disputes, disputed.
- fixtures/records/traces/ on writer2027fixed-ynr7.v3 and vectors/traces.jsonl
  (29 cases); check.py runs the record checks over both layers; counts, glossary,
  examples and CHANGELOG updated.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uy4ZYbWRoCdUtWLEfigysY
This commit is contained in:
alexmandClaude committed 2026-10-17 20:12:04 -04:00
1 parent 44a8e3f07c
commit 7890fa1ad7
25 files changed
+966 -50

No files matched your search

+45
View File
@@ -52,6 +52,7 @@ Replies are annotations that target annotations, so discussions are threads anch
| `citation-context` | `TextualBody` | `reference`, `verdict`, `reason` | Why a sentence cites a reference, and whether it supports it. |
| `prerequisite` | `SpecificResource` | `source` | What to read or know first. |
| `crosslink` | `SpecificResource` | `source`, `selector` | That the span corresponds to a span in another document. |
| `trace` | `SpecificResource` | `source`, `selector`, `reference`, `note`, `verdict`, `proposer` | That the target's sentence cites `reference` on the strength of a passage of the cited document ([18.5](#185-traced-citations)). |
| `translation` | `TextualBody` | `language` | The span in another language. |
| `description` | `TextualBody` | `role` | A description of a figure, panel or table for readers who cannot see it: `alt`, a short text equivalent, or `long`, a full description. |
| `generated` | `TextualBody` | `generator` | Text a model wrote about the span; never mistaken for source. `generator` is `{model, run}` as a correction's model author is ([11.2](11-corrections.md#112-every-correction-records)). |
@@ -87,3 +88,47 @@ Nothing a model writes is ever stored as source text.
A model may propose a `description`, and like any annotation it stays `proposed` until a person confirms it; an accessible export uses confirmed descriptions only, and a figure with none has no description.
Other text a model writes about a span, such as a summary, is a `generated` annotation, which names its `generator`.
`fixtures/records/layer/` holds a layer with one annotation of each common kind, each comment, caveat and question with its category, a claim link and a symbol that reference a paperclaim claim and pin it, and the receipt that confirms some of them.
## 18.5 Traced citations
"As previously reported [12]": a reader who finds where the cited paper reports it marks the passage, and the `trace` body keeps the mark.
A `crosslink` says only that two spans correspond, in both directions, whereas a trace goes from one sentence's citation to one passage, and says which reference it follows, why, and who or what found it.
The annotation's target is a leaf or span anchor ([6.4](06-anchors.md#64-four-kinds-of-anchor)) on the citing sentence, which MUST be a leaf with runs, a sentence or a cell, and MUST cite the reference the body names: one of its `cite` runs lists `reference` ([3.5](03-nodes.md#35-inline-runs)).
`reference` is the ID of a `reference` node of the citing document, because a sentence may cite several references at once, as in `[3, 12]`, and the target says only which sentence.
`source` is the cited document, written as a target's source is, `tag:scient.ing,2026:doc:<store>/<key>`, and never the citing document itself.
`selector` selects the passage in it: an `AnchorSelector` whose anchor holds the passage's pin (its `node_hash`) and, for part of a leaf, its `span`, then a `TextQuoteSelector` when the anchor's quote is plain ([18.3](#183-the-annotation)).
The passage may be a container, such as a table, whose anchor has no quote.
| Property | Required | Holds |
| --- | --- | --- |
| `reference` | Yes | The reference node of the citing document that the sentence cites and the trace follows. |
| `source`, `selector` | Yes | The cited document and the passage in it. |
| `note` | No | What the passage reports, in a person's or a tool's words. |
| `verdict` | No | `supports`, `partly`, `does-not` or `cannot-tell`, as a `citation-context` body has it ([19.2](19-exercises.md#192-verdicts)). |
| `proposer` | When the creator is `Software` | `{model, run, score?, rank?}`: the model and run that found the passage as a candidate, as a `generated` body's `generator` names them, the candidate's `score` as a decimal string, and its `rank`, from 1, among the candidates the run proposed for this citation. |
The state of a trace is the annotation's `status`.
A trace a tool proposes is `proposed` until a person confirms it, and `rejected` when a person turns the candidate down; a rejected candidate is kept, so that the tool is not offered it again.
A trace is *disputed* while a reply to it, an annotation whose target is the trace and whose motivation is `assessing`, is `proposed` or `confirmed`.
The dispute is computed from the replies and never written on the trace, so a reply can be withdrawn.
Whether a trace still leads where it did is also computed, from the current state of both documents.
Resolve the target's anchor against the citing document and the body's anchor against the cited document ([6.5](06-anchors.md#65-resolution)).
The first row that applies gives the outcome:
| Outcome | When |
| --- | --- |
| `stale` | An anchor is stale, on the citing side, the cited side or both. |
| `unlinked` | The sentence no longer cites `reference`: a `relink` removed the citation ([11.7](11-corrections.md#117-citations-and-references)), or the reference was deleted. |
| `misresolved` | A `resolve` made the reference name a DOI or a document that is not the cited work. A resolution of `none` names nothing and contradicts nothing ([10](10-references.md)). |
| `changed` | Both anchors resolve and at least one is not `current`. |
| `current` | Both anchors are `current`, and the reference still resolves to the cited work or to nothing. |
A resolution names the cited work when its document is the cited document, another version of the same work (a key with the same work key, [2.4](02-model.md#24-work-and-document-keys)), or one the caller knows to be the work; and a `doi` resolution does when the work is known by that DOI (`same_as`, [2.4](02-model.md#24-work-and-document-keys)).
Besides the outcome, a resolution gives the passage's current address, `key#node@pin[start:end]`, while its anchor resolves, and the citations inside the passage.
A chain of "as previously reported" is then followed one trace at a time: the reader opens the passage, and a citation in it that has a trace of its own leads on.
A trace breaks `layer.trace.source` when its selector has no `AnchorSelector`, an anchor is in another document than `source` names, `source` is the citing document, or the `TextQuoteSelector` does not repeat the anchor's plain quote.
It breaks `layer.trace.target` when the target is not a leaf with runs of the citing document selected by an `AnchorSelector`, or the sentence does not cite `reference` ([22.4](22-conformance.md#224-rules)).
`vectors/traces.jsonl` resolves traces against the fixture packages after corrections, with every outcome and every refusal, and `fixtures/records/traces/` holds a layer of four annotations on `writer2027fixed-ynr7.v3` that cites `hale2026harmonic-damb`: a trace a model proposed to the paper's abstract and a person confirmed with a receipt, a candidate a person rejected, a person's trace to Table 1, and a reply that disputes the first.
+5 -1
View File
@@ -6,7 +6,7 @@
| --- | --- | --- |
| `vectors/*.jsonl` | Inputs and expected outputs, one case per line. | Computes every expected output from its input. |
| `fixtures/packages/` | Valid packages: a converted chemistry paper with corrections, receipts, a multi-level-header table and a dataset; its checks-only export; an open-access paper with two witnesses; a Typst draft; a tiny document, as a folder and as the zip of [12.2](12-package.md#122-paths). | Accepts each, and recomputes every value it contains. |
| `fixtures/records/` | A valid record of every other format that is a JSON file of its own; the stage requirements and stamp rules are in `registry/`, and a bundle is built by `paperspec load --bundle`. | Accepts each. |
| `fixtures/records/` | A valid record of every other format that is a JSON file of its own, and a second layer, of traced citations ([18.5](18-layers.md#185-traced-citations)), whose traces resolve as `vectors/traces.jsonl` says; the stage requirements and stamp rules are in `registry/`, and a bundle is built by `paperspec load --bundle`. | Accepts each. |
| `fixtures/invalid/` | Invalid packages, each with `expect.json` naming the rule it breaks. | Refuses each, and SHOULD name that rule. |
| `fixtures/keys/` | The test keys that signed the fixtures and the team secret of the checks-only export. | Never uses them for anything else. |
@@ -49,6 +49,10 @@ Rule IDs are stable: a rule's meaning never changes, and a retired rule's ID is
| `doc.receipt.`, `doc.qa.`, `doc.fidelity.` | Receipts, spot-checks and levels. |
| `doc.witness.`, `doc.page.`, `doc.location.`, `doc.reference.` | Witnesses and geometry. |
| `grade.` | Grading. |
| `layer.` | Annotations that refer to documents: `layer.trace.source` and `layer.trace.target` ([18.5](18-layers.md#185-traced-citations)). |
A package is validated by [22.2](#222-validating-a-package), but a layer is not part of one.
The `layer.` rules are reported for an annotation, in the form `annotation: rule: message`, by a validator that has the annotation and the states of the documents it names; the reference implementation does so for traces in `paperspec.traces.problems`.
## 22.5 Stability