Evidence-linked models¶
A stated value is a claim. longeron.evidence lets the model carry the
citation that backs it: which document, which exact version, which words.
The design is docs/design/provenance.md; this guide covers the shipped
layers – the vocabulary, the toolchain, the lint codes, and the two
storage patterns.
The vocabulary¶
A citation is an ordinary metadata annotation, typed by
Evidence::SourceEvidence (examples/evidence.sysml, a longeron
convention package):
attribute mass : Real = 0.015 [SI::kg] {
@Evidence::SourceEvidence {
document = "https://www.apcprop.com/product/10x4-5mr/";
sha256 = "dcb33746a711...";
quote = "Product Weight 0.53 oz.";
retrieved = "2026-08-29";
}
}
Because the citation lives in the model, it parses, exports, survives JSON, and projects to RDF like any other content. There is no sidecar index to maintain.
The quote is the primary anchor: it survives re-rendering and OCR
variance, while page geometry does not. page and bbox are optional
visual locators. sha256 pins the exact document version, so anyone who
fetches the document can detect drift. retrieved records the ISO date
the document was read.
Attaching a citation¶
attach() writes the annotation through
longeron.edit, so a registered tracker records the change:
import longeron
from longeron import evidence
model = longeron.load("examples/deepscout", cache=False)
evidence.attach(
model,
"ScoutParts::F450Kit::Propeller::mass",
"https://www.apcprop.com/product/10x4-5mr/",
"Product Weight 0.53 oz.",
)
attach reads the document, computes its sha256, and extracts its text.
If the text does not contain the quote, attach raises
EditError, names the document, and shows the
closest text it found. A refusal mutates nothing, and the tracker
records nothing. Quote matching normalizes whitespace on both sides and
is otherwise exact.
Documents are repo-relative paths or URLs. A URL document is fetched
once, into a local cache (~/.cache/longeron/evidence, honoring
$LONGERON_CACHE_DIR and $XDG_CACHE_HOME). The cache only speeds
verification up. The sha256 on the citation stays the authority.
PDF text extraction needs the [evidence] extra (pypdf with a
pdfminer.six fallback, both license-clean). HTML documents reduce to
their visible text with the standard library. Plain-text documents need
no extra.
attach(..., verify=False) is the escape hatch for offline authoring:
it writes exactly what you pass, without reading the document.
verify() later reports what such a citation is
worth.
Verifying citations¶
verify() re-checks every citation and returns
one verdict each:
Verdict |
Meaning |
|---|---|
|
The document hashes to the citation’s sha256, and its text still contains the quote. |
|
The document reads, but its sha256 changed. |
|
The sha256 matches (or the citation never pinned one), but the text no longer contains the quote. |
|
The file or URL cannot be read at all. |
From the command line:
$ longeron evidence verify examples/deepscout
status element document detail
------ ------------------------------------- ----------------------------------------- ------
intact ScoutParts::F450Kit::Propeller::mass https://www.apcprop.com/product/10x4-5mr/
1 citation(s): 0 drifted or lost
The exit code is the count of drifted and lost citations, so a CI step
can gate on it directly. Pass --no-fetch to stay offline: URL
documents then verify against the local cache only.
Coverage¶
coverage() reports which stated values carry a
citation. A stated value is an attribute whose value expression is a
literal fact: a number, string, or boolean, optionally with a unit or a
sign. Derived values state no independent fact and are not counted.
>>> print(evidence.coverage(model, "ScoutParts::F450Kit::Propeller"))
element value evidence
----------------------------------------- --------------- -----------------------------------------
ScoutParts::F450Kit::Propeller::diameter 0.254 [SI::m] https://www.apcprop.com/product/10x4-5mr/
...
4 of 5 stated value(s) cited
The counts are never inflated: only stated values enter the denominator, and only citations on those values enter the numerator.
The lint codes¶
evidence-drift warns, in default mode, when a citation exists but
verification says the document drifted or the quote is lost. The lint
never touches the network: URL documents check against the local cache
only, and a model without citations is not affected at all.
unevidenced-value is opt-in (validate(evidence_coverage=True) /
longeron lint --evidence-coverage): one warning per stated value with
no citation. It is off by default because most models legitimately carry
derived and assumed values. Coverage is a report; absence is not a
defect. The validation guide
documents both codes.
The two storage patterns¶
Where the documents live is a copyright decision, made per document:
Owned or redistributable documents (internal specs, test reports, standards your organization licenses): commit them under
evidence/via git LFS. Runlongeron evidence initonce per repository; it writes the.gitattributesstanza (evidence/** filter=lfs ...) so the binary pollution never starts.Third-party documents (manufacturer datasheets and product pages): do not commit them – redistribution is usually not licensed. Cite by URL + sha256 + quote. The hash still pins the exact version, the quote still anchors the fact, and drift is still detectable by anyone who fetches the document.
The longeron repository itself follows pattern 2: the DeepScout parts catalog cites manufacturer pages by URL, and no vendor document is committed.