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

+32
View File
@@ -212,6 +212,38 @@ Receipts by `ana` raise `ec5jc93` and `a4jrz2t` to F3; `xjns57z` stays at F0 bec
To compute the final level, check each receipt's signature, revocations and spot-checks, as [chapter 8](../spec/08-fidelity.md#82-levels) describes.
The `scope` column of the third query shows the value `targeted:paperclaim`, which is data in the fixture, copied here exactly as the query prints it.
## Follow a traced citation
This shows how a citation leads to the passage it rests on, and how to tell that it still does.
A trace is an annotation from the sentence that cites a paper to a passage of that paper, with the reference it follows ([chapter 18](../spec/18-layers.md#185-traced-citations)).
The draft in the fixtures cites the main example once, and its layer of traces holds three: a model's, a person's, and a candidate a person rejected.
The Python package `paperspec.traces` judges each against the current state of both papers:
```python
import json
from paperspec import package, traces
draft = package.read_state("fixtures/packages/writer2027fixed-ynr7.v3")
paper = package.read_state("fixtures/packages/hale2026harmonic-damb")
same_as = json.load(open("fixtures/packages/hale2026harmonic-damb/document.json"))["work"]["same_as"]
annotations = [json.loads(line) for line in open("fixtures/records/traces/annotations.jsonl", encoding="utf-8")]
for a in annotations:
if a["body"]["kind"] == "trace":
r = traces.resolve(a, draft, paper, same_as)
print(a["status"], r["result"], r["open"], traces.disputed(a["id"], annotations))
```
```text
confirmed current smithlab/hale2026harmonic-damb#mhp0at8@ee42c4[48:60] True
rejected current smithlab/hale2026harmonic-damb#kea5026@b836d5[81:105] False
confirmed current smithlab/hale2026harmonic-damb#hj4s7pb@6c6fba False
```
Each line gives the trace's status, its outcome, the current address of the passage it leads to, and whether a reply disputes it.
The first trace is disputed by a reply that says the table is the better passage, which is where the third trace leads.
The outcomes here are all `current`; [vectors/traces.jsonl](../vectors/traces.jsonl) corrects the draft or the paper and shows the others: `changed` when the passage was reworded but is still found, `stale` when it is gone, `unlinked` when the sentence no longer cites the reference, and `misresolved` when the reference now resolves to another paper.
Resolving a trace also lists the citations inside the passage, so a chain of "as previously reported" is followed one trace at a time.
## Test your implementation against a vector
A test vector is a recorded input together with the output every correct implementation must produce.