Field infrastructure / schema
The Sniff schemas.
Sniff's machine-readable artifacts conform to small LinkML schemas that extend Biolink by mapping,
never by fork. The load-bearing one is the CoverageFrame: a typed vocabulary for reasoned absence,
the thing a knowledge graph's open-world assumption cannot say. Each schema's own id URL
resolves to the served YAML, and this page is generated from those same files at build, so what you
read here is what a machine fetches.
The frames themselves ship at /data/coverage-frames.json with an Ensembl-keyed cell index at /data/coverage-cell-index.json (the display symbol travels in the value; symbols collide). The KGX bundle for stewards lives at /federation.
Sniff CoverageFrame: reasoned absence over a bounded question space
- version
- 0.2.8
- source
- sniff-coverage-frame.yaml
- license
- CC BY 4.0
A small LinkML extension over Biolink for the one thing Biolink cannot express: a REASONED ABSENCE of assertion. Biolink's Association.negated says "we assert this relationship is false" (the absent cause below). It has no vocabulary for the other five darkness causes, and "nobody has looked" is both the most common state of biomedical knowledge and the most useful thing to know when deciding what to study next.
The modeling unit is deliberately NOT an absence edge (the ~447M non-edges of a bipartite space carry no information). It is a CoverageFrame: a bounded question over a defined population, where an empty cell is itself informative because an answer was expected. Kept to one class pair and two enums so it stays a contribution, not a fork: it aligns to Biolink by mapping (see the exact_mappings and meaning: bindings), rather than vendoring the full model.
Classes
CoverageFrame · tree root A bounded question space over a defined population, where the absence of an assertion is itself informative because an answer was expected. The unit of reasoned absence: it names the question, the populations, the expected cardinality, and how the expected answers resolve into answered vs dark cells.
slots: id, question, subject_population, object_population, predicate, expected_cardinality, answered_count, dark_count, dark_by_cause, cell_source, sources_checked, as_of
FrameCell One subject in a CoverageFrame. Its status is answered (a cited assertion exists), negated (an assertion that the relationship is false exists), or dark (no assertion exists, with a named cause). A dark cell is the payload: it turns a missing edge into a typed, actionable statement about what is not yet known and why.
slots: frame, subject, symbol, object, status, dark_cause, association, recoverability, n, basis, floor_id, as_of
DarkCauseTally One cause's share of a frame's dark cells. Serialized in the compact dict form, so a tally reads as {cause: count} keyed by DarkCauseEnum.
Enums
CellStatusEnumThe three resolutions of an expected answer cell.
-
answered· An assertion exists and is cited. -
negated· An assertion exists that the relationship is false. Corresponds to a Biolink association carrying negated = true; biolink:negated is a slot, not a concept, so it is a see_also rather than a meaning. -
dark· No assertion exists; see dark_cause.
DarkCauseEnumThe causes of darkness, modeled as they are actually computed. Only absent is expressible in Biolink today (as Association.negated). Prior art verified 2026-08-19 against Biolink v4.4.4, the ECO 2026-07-10 release, GO, and FHIR: the nearest existing vocabularies are single-cause and record-scoped. ECO:0000035 (no evidence data found; GO's ND code is its manual-assertion child ECO:0000307) is the closest cousin of unstudied (a literature or exhaustion search that found nothing) and is bound below as a close mapping. no_disease_anchor is a different claim: no ClinVar P/LP disease-gene anchor to bridge from, not that the gene is unstudied. FHIR data-absent-reason types why one field on one existing record is empty. No vocabulary we could find types the cause of absence per cell over a declared, bounded question space, and below_bar, unreachable, method_limited, and no_disease_anchor have no term in Biolink or any evidence ontology checked. Where no mapping appears below, that absence is verified and load-bearing, not an oversight.
-
absent· Studied, and the entity or relationship does not exist. The one cause Biolink can already express, as an association with negated = true. A cell whose cause is absent has status negated, never dark: absent is an evidenced no, not a darkness, and check-conformance refuses the pairing dark + absent. -
unstudied· A literature or exhaustion search addressing this question in this species found nothing. Not for "no Mendelian disease-gene anchor in ClinVar"; that is no_disease_anchor. -
no_disease_anchor· Dog ortholog exists but no human P/LP disease evidence to bridge from (ascertainment darkness). Not a claim that the gene is unstudied in the literature. -
below_bar· Human evidence exists but sits under our review bar (for example under a 3-star ClinVar threshold); distinct from method_limited. Relates to evidence strength (ECO) but no ECO term names "below a downstream bar". -
unreachable· No ortholog or entity exists for the assay to observe. -
method_limited· The method cannot resolve this class of entity even where the underlying data are present.
ClaimBasisEnumHow a present claim was produced. Companion to DarkCauseEnum, which names absence. Strength is n plus basis plus floor. A score float is forbidden.
-
measured· Instrument or lab-backed on this subject. -
estimated· Inferred or owner-sketched, never dressed as a measurement. -
cited_cohort· A published or atlas cohort statistic with a cited n. -
owner_register· An owner-contributed aggregate, cohort-of, never population-of. -
this_dog_lived· Repeated measures on one dog. Never a breed fact.
Slots (23)
id identifier · uriorcurie The CoverageFrame identifier. Carries biolink:id semantics.
question Human-readable statement of what the frame asks.
subject_population The subject set, for example GenCC or ClinVar human disease genes.
object_population The object set, for example their canine orthologs.
predicate uriorcurie biolink:predicate The Biolink predicate the frame asks about.
expected_cardinality integer How many answers the frame expects if the space were fully covered.
answered_count integer Cells with a cited assertion.
dark_count integer Cells with no assertion, each carrying a DarkCauseEnum.
dark_by_cause multivalued · DarkCauseTally Tally of dark cells keyed by DarkCauseEnum, as inlined DarkCauseTally entries (compact dict: {cause: count}). Until 0.2.4 this slot was declared as multivalued integers, a shape the shipped artifact never had; the correction is recorded rather than hidden.
cell_source uriorcurie The artifact holding the per-cell FrameCell records.
sources_checked multivalued · uriorcurie The infores CURIEs of the resources that were queried to build the frame.
as_of date The date the frame was computed. Renamed locally under the class-level implements binding: an as_of is the creation date of the snapshot object (biolink:creation_date).
frame CoverageFrame The CoverageFrame this cell belongs to.
subject uriorcurie The subject entity of the cell.
symbol string The human-readable label for the subject, beside its identifier. Optional, and never the identity: two sources may label one identifier differently, so a symbol is a convenience for a reader and is never matched on. Declared because the cells we publish have always carried it. It went undeclared while every count reconciled, which is the shape of defect a key-level check cannot see and instance validation catches immediately.
object uriorcurie The object entity of the cell, when one is expected.
status required · CellStatusEnum Whether the cell is answered, negated, or dark.
dark_cause DarkCauseEnum The reason no assertion exists. Required when status is dark, enforced by the FrameCell rule (not only this sentence).
association uriorcurie biolink:Association The Biolink Association backing the cell when status is answered.
recoverability Optional note for assay-limited or method-limited cells.
n integer Denominator that produced a present claim. Distinct from darkness. A cell may carry an n and still fall below its reporting floor.
basis ClaimBasisEnum How a present value was produced. Absent on a dark cell.
floor_id Which named reporting floor judged this cell, for example sniff:floor/reportable-n. Not a score. This schema does not carry a float confidence on a health-adjacent row.
Sniff CoverageFrame provenance module (an absence claim carries its search)
- version
- 0.3.14
- license
- CC BY 4.0
The provenance module for reasoned absence. A presence claim's warrant is a pointer (the citation); an absence claim's warrant is a PROCEDURE. This module makes that procedure a schema citizen: the SearchProtocol that produced a frame (pinned to runnable code, with a declared completeness and cause-precedence funnel), the versioned PopulationDefinitions it enumerated (whose counts double as drift checksums), and the positive controls that calibrate it (held-out known-answerable cells the protocol must find before its darkness counts; a dark cell not shown to LIGHT on a planted positive is worth zero). Importable separately from the core (sniff-coverage-frame), which stays frozen at one class pair and two enums. Conformance to the core alone is conformance; this module is how a producer PROVES the rigor of its dark cells. Two frames are comparable as knowledge flow only when their population DEFINITIONS and protocol are identical; a newer source_version under the same definitions is the world moving, and its membership changes are typed boundary events in the delta module. That rule is what makes world-moved vs frame-moved computable.
Classes
SearchProtocol The reproducible procedure that resolved a frame's cells. The strongest form of a methods section is a pointer to runnable open code at a pinned version. Determinism is the contract; the specific precedence order is a protocol parameter, so two parties running the same protocol version on the same populations must produce the same cells, and a disagreement is diagnosable rather than a shrug.
slots: id, protocol_version, pipeline, match_criteria, completeness, cause_precedence, dial_references, positive_controls, darkness_controls, executed_at, recall, end_to_end_recall, filter_independence, negative_controls
PopulationDefinition A versioned, counted population a frame enumerates over. Identity is the INTENSION: the id's version (an integer, as in sniff:population/x@1) bumps only when the selection procedure itself changes, never when a new source release changes the membership. The source_version and count are per-release measurements. The count doubles as a drift checksum: same definition version, same source_version, and a differing count means the pipeline drifted (a bug); a newer source_version under the same definition is the world moving, and the delta module enumerates the membership change as boundary events.
slots: id, source, source_version, selection, count
DarknessControl One held-out cell known to be DARK FOR A SPECIFIC CAUSE, independently of the pipeline: a primate-only gene for unreachable, a gene whose ClinVar records all sit under the declared bar for below_bar, a gene with a clean ortholog and no disease anchor for no_disease_anchor. The protocol must observe the expected cause on every darkness control before its dark cells may ship, and it must carry at least one for every cause its tallies claim. Positive controls prove the instrument finds what exists; darkness controls prove it names why it does not, which is the product. Added after both dark cells of a demonstration frame turned out to be mistyped while every positive control passed: an instrument calibrated only on the cells that light has proven nothing about the cells that do not.
slots: subject, expected_cause, observed_cause, observed_status, basis, basis_kind, receipt_source, receipt_as_of, outcome
PositiveControl One held-out cell known to be answerable, independent of the pipeline (for Sniff's reference frame: canine disease genes documented in OMIA before this pipeline existed). The protocol must observe the expected status on every control before its dark cells may ship. Calibration is the difference between measured ignorance and a parsing bug wearing ignorance's clothes.
slots: subject, expected_status, observed_status, basis, basis_kind, receipt_source, receipt_as_of, outcome
ProvenancedCoverageFrame is_a CoverageFrame A CoverageFrame that carries its warrant: the protocol that resolved it and the versioned populations it enumerated. The core class stays frozen; adopters who cannot yet publish protocols emit plain CoverageFrames and graduate to this class when the procedure is publishable.
slots: search_protocol, subject_population_ref, object_population_ref, release_id, conforms_to, scope, constituents
FrameScope What this frame measured, and what it deliberately did not. A question that outruns its sources is an overclaim every dark cell inherits. sources_queried must match the frame's sources_checked. sources_not_queried names known authorities left out, with a reason and a named path to close each gap, so an unqueried source is a typed limitation rather than silence.
slots: measures, sources_queried, sources_not_queried
SourceOmission One authority this frame did not query. The reason is why, for this question. close_path is how the gap becomes queried rather than remaining an implicit hole in sources_checked.
slots: source, reason, close_path
Recall A measured recall figure bound to the identity of the control set that produced it. A recall number whose denominator can silently change is the failure this class exists to prevent.
slots: value, control_set, control_set_hash, control_set_size, dial, measured_at
FilterIndependence How much two candidate filters fail on the same items. When the observed shared-miss count materially exceeds the independence expectation, stacking further filters of that family will not recover recall.
slots: filters_compared, shared_miss_jaccard, observed_over_expected, marginal_gain
Enums
CompletenessEnumHow completely the protocol enumerated the population, defined operationally so the term is checkable rather than a vibe.
-
exhaustive· The protocol evaluated every cell in the declared population. -
sampled· The protocol evaluated a declared fraction under a declared sampling method. Dark tallies extrapolate; absent is not assertable. -
heuristic· Enumeration is not guaranteed (for example keyword search). The weakest honest claim; absent is not assertable.
BasisKindEnumThe kind of evidence a control's basis is, ranked by independence from the protocol's own source.
-
independent_literature· A published paper. Independent of every database the protocol queries; immutable, so a mismatch is always a pipeline defect. -
independent_source· A database other than the one the protocol queries for this cell (for example OMIA for a ClinVar-anchored protocol). Independent of the source, not of the world. -
same_source_hand_verified· The protocol's own source, read by hand outside the pipeline, with a dated receipt. Catches builder bugs, parsing errors and wrong filters; catches no error in the source. A control of this kind on a mutable database is a world-movement tripwire and routes toworld_moved, never to a build failure on its own.
ControlOutcomeEnumWhat the protocol run did to a control.
-
held· Observed equals expected. The only outcome that counts toward calibration. -
world_moved· Observed differs from expected and the receipt facts no longer hold in the ingested source. The world moved; re-plant a control for the cause. -
failed· Observed differs from expected while the receipt facts still hold. The funnel is wrong. Nothing ships.
IntersectionStatusEnumHow a cell resolves across the constituent frames of an intersection. These tokens are scoped to the named stack. They are not claims about sources outside that stack. A third frame must use these values; it must not invent a parallel convention.
-
lit_all· Answered in every constituent frame of this stack. -
lit_any· Answered in at least one constituent frame of this stack, and not in all of them. -
dark_in_all_frames· Dark in every constituent frame of this stack. Not a claim that nobody has looked, and not a claim about sources that are not in the stack. -
unresolved· Not evaluated by this stack. The subject is off the shared axis, or a constituent did not emit a cell.
Slots (47)
protocol_version required The protocol's own version, bumped on ANY behavior change to the emitting pipeline. Producers should hold this with a mechanism that notices silent drift (Sniff's gate hashes the emitting script and fails the build when the code changes without a version bump).
pipeline required · uriorcurie Identifies the code that emitted this frame. A URI that resolves to the runnable source where that code is public; otherwise a CURIE naming the pipeline, or a URI resolving to a public description of it. The field says WHAT produced the frame and promises the identifier is stable and resolvable; it does not promise a reader can execute what they find there. Stated this way because the earlier wording ("resolvable to the runnable source") was violated by every value a closed-source pipeline can honestly supply, which makes the contract unmeetable rather than strict.
match_criteria required Human-readable statement of what counts as "answered" for a cell.
completeness required · CompletenessEnum Operational completeness of the enumeration. This is a property of the protocol, not the frame. absent cells are only assertable under an exhaustive protocol; a sampled or heuristic search may emit unstudied, never absent.
cause_precedence multivalued · DarkCauseEnum The declared funnel order for assigning the single binding dark_cause when several causes could apply. Instrument-side blockers before world-side by default (unreachable, method_limited, no_disease_anchor, unstudied, below_bar, absent). The funnel makes cause assignment an algorithm, which keeps dark_by_cause tallies additive and cross-party comparable. Bindings are per-frame: no_disease_anchor is a ClinVar-anchor fact; unstudied is a literature-search fact.
dial_references multivalued · uriorcurie Named, versioned criteria behind any subjective cause (for example sniff:dial/clinvar-review-floor@1 for below_bar). The enum stays closed and interoperable; every adopter's bar is explicit and diffable. A dial change is a criteria_change transition in the delta module, never a silent redefinition of darkness.
positive_controls multivalued · PositiveControl The calibration set, lit side. A frame whose protocol fails any positive control cannot ship its dark cells.
darkness_controls multivalued · DarknessControl The calibration set, dark side. One per cause the frame's tallies claim, each independently known dark for that cause. A frame whose protocol assigns a cause it has never demonstrated on a known case cannot ship.
executed_at date When the protocol run that produced the frame executed.
reason Why this source was not queried for this question. Not a hedge on the cells; a statement of the search that was not run.
source required · uriorcurie The infores CURIE of the resource the population was selected from.
source_version The release or version of the source the selection ran against.
selection required The criteria or query that produced the population.
count required · integer The resulting cardinality; the population's drift checksum.
expected_status required · CellStatusEnum The status the control cell is independently known to have.
expected_cause required · DarkCauseEnum The cause a darkness control is independently known to carry.
basis_kind required · BasisKindEnum How much assurance the basis carries, said out loud so nobody reads more into a row than it holds. A paper is independent of every database the protocol queries. A different database is independent of the protocol's source but not of the world. The protocol's own source, read by hand, catches builder bugs, parsing errors and wrong filters, which is most of the value, and catches no error in the source itself.
receipt_source uriorcurie Where the basis was read: an infores CURIE for a database, a PMID for a paper. Required when basis_kind is same_source_hand_verified, because that is the receipt the outcome logic re-checks.
receipt_as_of date The UTC calendar date the receipt was read. Every date in this schema family is a UTC calendar date; a reader in a western timezone may see a receipt dated tomorrow, and that is the convention, not a future-dated read.
outcome required · ControlOutcomeEnum What happened when the protocol ran against this control. held is the only outcome that counts toward calibration. world_moved is the routing for a control on a mutable source whose receipt facts no longer hold: the day an expert panel reviews a below_bar control's gene, that control fails correctly, the cell should leave below_bar, and the frame still ships if another held control covers the cause; the delta carries the movement. A control that fails while its receipt facts still hold is failed, the funnel is wrong, and nothing ships.
observed_cause required · DarkCauseEnum The cause the protocol actually assigned to the darkness control's cell.
observed_status required · CellStatusEnum The status the protocol actually produced for the control cell.
release_id uriorcurie The release this frame snapshot belongs to. Declared in 0.3.3; the shipped artifact had carried it undeclared, which the reference validation loop now makes impossible to repeat silently.
conforms_to uriorcurie dcterms:conformsTo The schema class this frame instance claims conformance to.
scope FrameScope The frame's measurement boundary. Rendered on every cell so a dark answer cannot be read as a census of sources this frame did not search.
measures One sentence naming what the frame measures, and what a reader must not take it to have measured.
sources_queried multivalued · uriorcurie The infores CURIEs actually searched. Must equal the parent frame's sources_checked. Repeated here so a cell view can render the boundary without joining.
sources_not_queried multivalued · SourceOmission Known authorities for this question that were not searched, each with a reason and a named path to close the gap.
close_path How this omission becomes a queried source rather than remaining a silent hole. A named next protocol, join, or parallel frame, not a vague intention.
constituents multivalued · uriorcurie When this frame is an intersection, the CoverageFrame ids it stacks. Constituents MUST share one subject axis (same population definition, same cell keys, same expected cardinality). The intersection records only lit or dark per constituent; dark causes stay on those frames. sources_checked on an intersection is the union of the leaves' sources. Recursive: a constituent may itself be an intersection.
search_protocol required · uriorcurie The SearchProtocol (by id) that resolved this frame's cells.
subject_population_ref required · uriorcurie The PopulationDefinition (by id) enumerated on the subject side.
object_population_ref uriorcurie The PopulationDefinition (by id) resolved against on the object side.
recall Recall Measured sensitivity of this protocol against its positive controls. A protocol may carry passing controls while concealing how blind it was; this slot is what makes that impossible.
end_to_end_recall Recall Recall measured after EVERY stage, not just retrieval. A multi-stage protocol has more than one recall and they answer different questions: recall is whether the search returned candidate documents for a control, and this is whether the control's cell actually ended up answered. The second is bounded above by the first, and the gap between them is a map of whatever sits in between. Declared 0.3.5 after a producing pipeline emitted it as an undeclared key, which the schema now forbids: a producer may not carry a field the schema never promised.
filter_independence FilterIndependence Whether candidate filters fail on the same items. Stored as a measurement, not an observation, so the conclusion stays re-derivable.
negative_controls multivalued · PositiveControl Cells known NOT to be answerable, on which the protocol must stay silent. Positive controls alone cannot detect an inflated instrument: a recall figure rises when automated annotations are counted as literature, and only a negative control detects it.
value required · float Measured recall as a fraction.
control_set required · uriorcurie Versioned id of the control set, for example sniff:controls/frame-one-positives@1.
control_set_hash required SHA-256 over the sorted control member ids. Binds the figure to its denominator's identity.
control_set_size required · integer Number of controls the figure was measured over.
dial required · uriorcurie The named versioned dial the measurement was taken under.
measured_at required · date When the measurement ran.
filters_compared required · multivalued Names of the filters compared.
shared_miss_jaccard float Jaccard index of the filters' miss sets.
observed_over_expected float Observed shared misses divided by the count independence predicts.
marginal_gain integer Additional controls recovered by adding the second filter to the best single filter.
Sniff CoverageFrame delta module (frames are snapshots, change is typed)
- version
- 0.3.4
- license
- CC BY 4.0
The delta module for reasoned absence. A frame is a snapshot and stale darkness silently lies, so change is a first-class typed object rather than a regenerated file's diff. A FrameDelta separates the two things that can move: the WORLD (identical populations and protocol, cells flipped, knowledge changed) and the FRAME (populations, protocol, or dials changed, so the question changed). Blending them produces a confident falsehood; the kind field makes the blend inexpressible. Every changed cell is enumerated as a CellTransition with a typed trigger and a reference. The correction trigger is the designed repair path: a dark cell that was our bug becomes a typed, public, cited correction instead of a silent overwrite. Population definitions are intensional (see the provenance module), so a new source release can legitimately change the MEMBERSHIP of an unchanged definition: the world does not only answer questions, it also mints and retires them. Those membership changes are first-class CellBoundaryEvents (entered, left), never silent. For world-moved deltas the conservation identity binds the frame's DECLARED counts to the enumerated flows: answered(t1) = answered(t0) + darkToAnswered - answeredToDark + enteredAnswered - leftAnswered, and cardinality(t1) = cardinality(t0) + entered - left, every cell in exactly one status per release. A regenerated frame that does not reconcile against its predecessor plus the delta is a build failure, and the state machine audits itself.
Classes
FrameDelta The typed change between two releases of one frame. Comparable only when kind is world_moved; a frame_moved delta documents that the question itself changed and its transitions are annotations, not knowledge flow.
slots: id, previous_frame, current_frame, kind, transitions, entered, left
CellTransition One cell's move between statuses or causes across a release, with a typed trigger and a citation for what moved it. The dark-to-answered transition is the product's core loop made schema: the record updates itself and the owner is told, with a citation.
slots: subject, symbol, from_status, from_cause, to_status, to_cause, trigger, reference
CellBoundaryEvent One cell entering or leaving the frame's universe across a release. Under world_moved this is knowledge flow (a new source release minted or retired the question cell itself, for example a gene newly carrying a pathogenic classification); under frame_moved it is an annotation of the changed question. Either way it is enumerated, because a silently appearing or vanishing cell is the delta equivalent of an untyped gap.
slots: subject, symbol, status, dark_cause, trigger, reference
Enums
DeltaKindEnumWhat changed between two releases. Never blended. Held two values until a release corrected a source identifier without the world or the method moving: identical definitions over an identical upstream snapshot, and not one cell flipped. That is not the world moving, and world_moved was acting as the residual bucket rather than as a claim about the world.
-
world_moved· Identical populations and protocol; cells flipped because knowledge changed. The conservation identity binds. -
frame_moved· Populations, protocol, or dials changed; the question changed. Not comparable to the predecessor as knowledge flow. -
correction· Nothing moved. Identical populations, protocol and dials over an identical upstream snapshot; the release exists to repair our own record. Counts are expected to be unchanged, and any cell that does flip flips because the previous release was wrong about it.
TransitionTriggerEnumThe typed causes of a cell transition.
-
source_release· An upstream source shipped a new release. -
new_publication· A new primary publication answered or negated the cell. -
method_change· The protocol or pipeline changed how it resolves cells. -
criteria_change· A named dial changed (the bar moved, visibly). -
population_change· The population definition or its source version changed. -
correction· The previous release was our error. The typed, public, cited repair path; never a silent overwrite.
Slots (12)
previous_frame required · uriorcurie The prior frame release (by id plus release).
current_frame required · uriorcurie The new frame release (by id plus release).
kind required · DeltaKindEnum world_moved, frame_moved, or correction, never blended. Populations and protocol identical AND the upstream snapshot unchanged means the record was corrected, not that anything moved. Identical definitions over a NEW upstream snapshot means the world moved. Anything else means the frame moved.
transitions multivalued · CellTransition Every changed cell, enumerated.
entered multivalued · CellBoundaryEvent Every cell that entered the universe, enumerated with its arrival status.
left multivalued · CellBoundaryEvent Every cell that left the universe, enumerated with its departure status.
from_status required · CellStatusEnum The cell's status in the previous release.
from_cause DarkCauseEnum The cell's dark cause in the previous release, when it was dark.
to_status required · CellStatusEnum The cell's status in the current release.
to_cause DarkCauseEnum The cell's dark cause in the current release, when it is dark.
trigger required · TransitionTriggerEnum What moved the cell, typed.
reference uriorcurie The citation or artifact behind the trigger (the new publication, the source release, the correction notice).
Sniff Citation: every rendered attribution names exactly the authors the record names
- version
- 0.1.0
- source
- sniff-citation.yaml
- license
- CC BY 4.0
LinkML root for the citation membrane. A page that renders "Surname et al. 2026" makes a claim about real people: that Surname wrote it first and that others wrote it with them. Sniff rendered that claim about a single-author preprint on thousands of pages, attached a cohort paper to two people who did not write it, and named a senior author as if he were second. None of it was caught, because the figures registry governed the numbers beside each citation and nothing governed the words. This schema makes the cited works a governed catalog. A CitedWork carries its DOI and the author list, years and title as the DOI record states them, with the date the record was read. The catalog is refreshed from Crossref or DataCite by a script and never typed by hand; the build reads it (web/scripts/validate- citations.cjs) and refuses any author-year form on a page that the catalog does not produce: a wrong first author, a co-author suffix on a sole author, a wrong initial, a wrong year, or a DOI that does not resolve.
Classes
CitationCatalog · tree root Every work a page cites by author and year or by DOI. An empty catalog is a defect, not a pass.
slots: works, authored_planes
CitedWork One cited work as its DOI record states it. Only id, doi and note are authored; every other slot is written by the refresh script from the record and carries the date it was read.
slots: id, doi, note, record_source, record_verified_on, title, container, authors, n_authors, year_online, year_print
Author One author as the record lists them, in list order.
slots: family, given
Enums
RecordSourceEnumThe registries a DOI record may come from.
-
crossref· Crossref, for journal articles and most preprints. -
datacite· DataCite, for datasets and repository deposits.
Slots (15)
works required · multivalued · CitedWork The cited works.
authored_planes multivalued Data planes under src/data whose prose Sniff wrote and which therefore carry Sniff's own attributions, listed by path. Pages, components and library code are always read. Planes not listed hold references received from a source (OMIA's reference lists, an upstream dataset's DOI) and are the source's claim, not ours; they belong to a currency check, not this one.
id identifier A stable slug for the work, conventionally first-author-year.
doi The DOI, without a resolver prefix and without a version suffix.
note Why this work is in the catalog, or what a page cites it for.
record_source RecordSourceEnum Which registry answered for this DOI.
record_verified_on date The date the record was read from the registry.
title The title as the record states it.
container The journal, server or repository as the record states it.
authors multivalued · Author The author list in record order.
n_authors integer The length of the author list; a co-author suffix requires it to exceed the names given.
year_online integer The year the record says the work was issued.
year_print integer The print year where the record gives one, which citations conventionally use.
family required Family name as the record lists it.
given Given names or initials as the record lists them.
Sniff OwnerFact: per-field privacy born on the living Record
- version
- 0.2.2
- source
- sniff-owner-fact.yaml
- license
- CC BY 4.0
LinkML root for owner-contributed facts on user dogs (the living-dog layer). Where CoverageFrame models reasoned ABSENCE over the genetics substrate, OwnerFact models privacy-born PRESENCE on the living dog: the same immune grammar, opposite polarity.
A fact is born with a FactVisibility intent that is refuse-null (never a database default), and its effective exposure is COMPOSED under the dog's visibility tier and the owner's trust tier, never read from raw intent alone. science here means eligible to COUNT in an anonymized cohort, not safe to RENDER a cohort rate: any aggregate still passes the cohort-frame and apoptosis rules before it can be shown.
Instances live in Turso (dog_facts); the pure composition logic lives in web/src/lib/consumer/fact-core.mjs. This schema is the contract those obey.
Classes
OwnerFact · tree root One owner-contributed fact about a dog, born with a FactVisibility intent (refuse-null). The value is carried under a stable typed key; provenance records whether it was declared, measured, or imported.
slots: id, dog_id, kind, key, value, visibility, provenance, created_at, n, basis, floor_id
FactExposure The result of composing a fact's visibility intent with the dog's visibility tier and the owner's trust tier. science here is an eligibility gate to count, not a permission to render a cohort rate.
slots: owner, science, showcase
Enums
FactKindEnumThe category of an owner-contributed fact.
-
answer· A response to a dog-questions item. -
lifestyle· A structured lifestyle seed such as acquisition window or diet class. -
note· A free-text owner note; born private. -
vet_doc· A clinical document handle; born private, handling deferred.
FactVisibilityEnumThe owner's per-field visibility intent. The same three-value vocabulary is shared across the record (see INV-116); it is refuse-null and fails closed to private.
-
private· The owner's own lens only. -
science· Eligible for an anonymized cohort count when trust clears; never shown publicly. -
public· Eligible for showcase when the dog is public and trust clears.
FactProvenanceEnumHow a value was obtained. ECO evidence-code bindings are a planned Phase 2 extension; they are omitted here rather than asserted unverified.
-
declared· Owner-reported, carried in estimated language. -
measured· Instrument or lab-backed. -
imported· Brought in from an external source with a provenance trail.
Slots (14)
id identifier The fact identifier.
dog_id required The user_dogs id this fact belongs to; never the Founder dogs table.
kind required · FactKindEnum The category of fact.
key required A stable key such as acquisition_window, diet_class, free_note, or a dog-questions id.
value required A JSON string for structured keys, or raw text for a free note.
visibility required · FactVisibilityEnum The owner's intent only, never a database default; refused if null at write time. Effective exposure is composed downstream, never read from here alone.
provenance required · FactProvenanceEnum How the value was obtained.
created_at datetime When the fact was written.
n integer Denominator when this fact is later aggregated. A single owner fact is n=1. Cohort renders still pass the cohort-frame floor.
basis How the value was produced when distinct from provenance. Typical values are declared, measured, owner_register, or this_dog_lived. No score.
floor_id Which par an aggregate of this fact must clear before it may render.
owner boolean Visible to the owner in their own record.
science boolean Eligible to count in an anonymized cohort. The cohort-frame and apoptosis rules still gate any aggregate before it can be rendered.
showcase boolean Eligible for public showcase when the dog is public and trust clears.
Sniff Sighting: one observed mention of an entity in one document
- version
- 0.2.0
- source
- sniff-sighting.yaml
- license
- CC BY 4.0
A sighting is one observation that a document mentions an entity, recorded before anyone decides what it means. Literature never enters a coverage frame directly: it enters this ledger, and only an adjudicated sighting may support a frame cell. The separation is the point. Recall comes from a precomputed index, precision comes from adjudication, and a sighting records which stage it has reached. Sentence text is deliberately LOCAL-ONLY: it exists for adjudication and is never emitted into a published artifact. Published sightings carry the document id and character offsets instead, so any claim stays checkable against the source without redistributing copyrighted text.
Classes
Sighting · tree root One entity mention in one document, with the offsets that locate it and the adjudication that judged it.
slots: id, subject, taxon, document, passage_offset, annotation_offset, annotation_length, extractor, extractor_version, retrieved_date, archive_hash, filter_flags, proposal, adjudication, adjudication_basis, adjudication_note, sentence_text
Proposal A machine's opinion about a sighting, recorded before any human judged it. Held as its own class rather than as fields alongside adjudication on purpose: a proposal and a verdict are different kinds of thing, and a flat proposed sitting beside adjudication is one mis-read away from a compiler typing a cell on a machine's say-so. INV-145 forbids exactly that, so the shape is built to make the mistake awkward rather than merely detectable. Nothing here may promote a cell; only a merged human adjudication may.
A proposal is what the adjudication ladder measures. Blind review withholds it from the reviewer, and blind agreement between this and the human verdict is the only agreement figure that means anything.
Enums
AdjudicationStatusEnumThe three states of a sighting.
-
pending· Observed, not yet judged. May not support a frame cell. -
confirmed· Judged to support a claim about this entity in this taxon. -
rejected· Judged not to support such a claim.
AdjudicationBasisEnumWhy an adjudication went the way it did. Turns the species-assignment problem from a caveat into a measured quantity.
-
confirmed_canine· The document studies this entity in this taxon. -
species_ambiguous· Entity and taxon both present, but which species the claim attaches to is not resolvable from the text. -
wrong_species_context· The entity is studied in another species and this taxon is mentioned incidentally, as in a comparative genomics survey. -
dog_as_comparator· The taxon appears as a comparison or control group, not the subject. -
gene_mismatch· The annotation resolves to a different entity, an alias or symbol collision. -
insufficient_text· Undecidable without full text.
Slots (17)
proposal Proposal The machine proposal for this sighting, if one has been made. Absent until propose runs. Its presence never advances adjudication.
id identifier Stable sighting id, a hash of subject, document, offsets, extractor and version.
subject required · uriorcurie The entity sighted, as a CURIE.
taxon required · uriorcurie The taxon the sighting is scoped to, for example NCBITaxon:9615.
document required · uriorcurie The source document, for example PMID:35277861.
passage_offset required · integer Character offset of the passage within the document.
annotation_offset required · integer Character offset of the mention within the document. A published receipt.
annotation_length required · integer Length of the mention. With the offset, this is the checkable span.
extractor required The tool that produced the annotation, for example pubtator3.
extractor_version required Pinned version or release date of the extractor.
retrieved_date required · date When the response that carried this sighting was retrieved.
archive_hash required SHA-256 of the archived response this sighting was derived from. The corpus-of-record pointer: re-derivation replays the archive, never the live service.
filter_flags multivalued Which candidate filters fired on this sighting, for example sentence_cooccurrence or dog_gene_id. Annotation only. A filter never types a cell.
adjudication required · AdjudicationStatusEnum How far this sighting has progressed. A pending sighting cannot support a cell.
adjudication_basis AdjudicationBasisEnum Why the adjudication went the way it did. Required once adjudicated (see the class rule). The share of species_ambiguous plus insufficient_text is the irreducible-human-judgment fraction.
adjudication_note One sentence from the adjudicator, citing what was judged.
sentence_text LOCAL-ONLY. The sentence that was judged, kept for adjudication and never emitted into a published artifact. Published sightings carry offsets.
Sniff TaxonScope: every identifier field declares the species it is scoped to
- version
- 0.1.0
- source
- sniff-taxon-scope.yaml
- license
- CC BY 4.0
LinkML root for the species membrane over the data planes. A knowledge graph that spans dog, human, mouse, cat and horse moves identifiers across species at every join, and an identifier that crosses without its taxon becomes a claim about the wrong animal. Three Drosophila annotation ids once sat in the gene field of canine disease rows and rendered on breed pages as the gene dogs carry a change in; no check refused them because no field had ever said which species it belonged to. This schema makes that declaration explicit. A TaxonScopeManifest names every species-scoped field in the shipped data planes, the taxon it is scoped to, the kind of identifier it holds, and optionally the catalog the value must resolve in. IdentifierNamespace rows give each (kind, taxon) pair the shape its identifiers take, so a value that matches another species' namespace is refused as a leak, which is a louder failure than an unknown value. The build reads the manifest (web/scripts/validate-taxon-scope.cjs); this file is the contract the manifest and the gate obey. Same habit as the CoverageFrame family: the schema is what the build reads, never documentation about it.
Classes
TaxonScopeManifest · tree root The whole membrane: the namespaces identifiers may take, the catalogs a value may be required to resolve in, and every species-scoped field in the shipped data planes. An empty manifest is a defect, not a pass.
slots: namespaces, catalogs, fields
IdentifierNamespace The shape an identifier of one kind takes for one taxon. Used two ways: a value in a field of this kind and taxon must match it, and a value in a field of any other taxon that matches it is a cross-species leak.
slots: kind, taxon, pattern, ignore_case, example
Catalog A place a value may be required to resolve in: an artifact and the path within it whose selected values form the allowed set. Declared only where the catalog is complete for its taxon and kind; a partial catalog would refuse true values, and a field with no complete catalog declares none and relies on the namespace arms alone.
slots: name, artifact, path, taxon, kind
TaxonScopedField One field in one shipped artifact whose values are identifiers scoped to a species. The taxon is either a constant or read per row from a sibling field named by taxon_from, for artifacts that carry several species.
slots: artifact, path, kind, taxon, taxon_from, split, resolves_in, note
Enums
IdentifierKindEnumThe kinds of species-scoped identifier the data planes carry.
-
gene_symbol· A gene symbol as the field's species names it (HGNC-style for human, Ensembl or NCBI for dog). -
ensembl_gene· A stable Ensembl gene identifier, whose prefix encodes the species. -
omia_phene· An OMIA phene identifier, whose suffix after the hyphen is the taxon id. -
annotation_gene· A species-specific annotation id that is neither a symbol nor an Ensembl id, such as a FlyBase-style gene id.
Slots (15)
namespaces required · multivalued · IdentifierNamespace The identifier namespaces known to the membrane.
catalogs multivalued · Catalog The catalogs a field may be required to resolve in.
fields required · multivalued · TaxonScopedField Every species-scoped field in the shipped data planes.
kind required · IdentifierKindEnum What sort of identifier the values are.
taxon uriorcurie The NCBI Taxonomy CURIE of the species the values belong to.
taxon_from Name of a sibling field on the same row that carries the row's taxon CURIE, for artifacts holding more than one species.
pattern A regular expression every identifier of this kind and taxon matches. The token {taxid} is replaced with the numeric taxon id at check time, so one row can describe a taxon-suffixed namespace.
ignore_case boolean Whether the pattern is matched case-insensitively.
example One real identifier from this namespace, for the reader.
name identifier A short handle a field can name in resolves_in.
artifact required Path of the shipped JSON artifact, relative to web/.
path required Where the values sit inside the artifact. Dotted segments; a bare * selects every value of an object, [] selects every element of an array, and @key selects an object's keys instead of its values.
split A separator on which a single string value is split into several identifiers before checking, for fields that store a joined list.
resolves_in multivalued · Catalog Catalog names whose union every value must resolve in, case-insensitively. Absent means the namespace arms are the only check, and the manifest says why in the note.
note Why this field is declared as it is, in one or two sentences.