Status: draft 3 (HypGraph 0.3). Licence: CC BY 4.0. This page is enough to write another implementation, a verifier or a viewer.
1. Model
A record is a set of nodes (typed, versioned), edges (typed links between two nodes), withdrawals, retractions (of edges), leases (who is working on a node) and events. Every table is append-only: a correction is a new row, never an edited one.
- A node has an
id, atype, a displaycode(H1, CL3 …, unique per type within its investigation), the
investigation it belongs to (inv, empty for an investigation itself), created_at and created_by.
- Its content lives in versions
(node, v, at, by, reason, body).bodyis the canonical JSON (§3) of the
node's fields. The current version is the highest v.
- An edge records the version of each end when it was made (
src_v,dst_v). A review or decision is about that
version.
2. Vocabulary
| type | code | fields (* required) | what it is |
|---|---|---|---|
investigation | INV | title\, objective\, success_criteria, scope, state, reviewers, constants, strict_claims, keywords, projects, doi, note, legacy_case | The study: an objective, its scope, who reviews it. Every other node belongs to one. |
question | Q | text*, why | Something you want to know. |
hypothesis | H | statement\, assumptions, predictions\, distinguishing, open_questions | A possible answer, with the predictions that would show it wrong. |
claim | CL | statement*, scope, limitations, kind, measurements, evidence_note, exposure, exposure_unit, legacy | What you assert is known, with its scope and its numbers bound to results. |
experiment | X | aim\, protocol\, controls, success_criteria*, method, budget, power_check, cost_minutes, seed | A planned test: aim, protocol, controls, success criteria, and what each hypothesis expects. |
run | RUN | kind*, job_id, name, state, code, inputs, outputs, args, config, seed, env, started, finished, exit_code, log_sha256, note | One execution: code, inputs and outputs hashed, seed, state. kind=lab only from an instrument. |
result | RS | summary*, kind, outcome, values, expected, limitations, power, evidence_note, legacy | What a run (or an observation, or a citation) found: values, outcome, power. |
artifact | A | sha256*, path, size, format | Exact bytes, by sha256 — data, code, a model, a figure. |
source | S | citation*, doi, url, passage, retrieved, says, kind, reliability, record, node, version, hash | Outside literature or data: citation, DOI, the passage that says it. Or another record's node, pinned: record (its URL), node, version and hash (sha256 of that version's canonical body) — all four or none. |
review | RV | verdict\, method\, notes*, official | A verdict on one version of a claim, result, run, experiment or hypothesis. |
decision | D | choice\, alternatives, reasons\, revisit_if, meeting | A choice made on the evidence; flagged when what it was based on changes. |
task | T | kanban_id*, title | A unit of work in your tracker, linked to what it works on. |
| relation | from | to | meaning |
|---|---|---|---|
part_of | any but investigation | investigation, question | belongs to an investigation or question |
answers | claim, hypothesis | question | offers an answer to a question |
alternative_to | hypothesis | hypothesis | competes with another hypothesis |
tests | experiment | claim, hypothesis | an experiment tests a hypothesis or claim (data.expected: what it should show if true) |
executes | run | experiment | a run carries out an experiment |
produced | run | artifact, result | a run produced a result or artifact |
uses | run, experiment | artifact | a run used an artifact (data.role: data / code / model / config); an experiment is designed on one |
derived_from | artifact, claim, result | artifact, result, run, source | computed or taken from |
supports | claim, result, source | claim, hypothesis | evidence for (why required) |
challenges | claim, result, source | claim, hypothesis | evidence against (why required) |
requires | claim, hypothesis | claim | rests on another claim as a premise |
replicates | run | run | a run repeats another (data.differs: same_code / new_seed / new_impl / new_data / new_env) |
reviews | review | claim, experiment, hypothesis, result, run | a review of a node (written by review()) |
supersedes | any but artifact, review, investigation | the same type | replaces an older node of the same type |
corrects | claim, decision, result | the same type | fixes an error in a node of the same type |
motivated_by | experiment, hypothesis, question | any | prompted by |
cites | claim, experiment, hypothesis, result | source | cites a source |
based_on | decision | claim, hypothesis, result | a decision rests on this evidence (pinned to its version) |
works_on | task | any but task, artifact, review | a task works on a node |
blocked_by | task | task | a task waits on another task |
Field kinds and lengths: hypgraph schema --json (or /schema/hypgraph-1.json on the site). A claim's measurements are {name, value | k & n | lo & hi, result: <result id>, key: <field of that result's values>}.
Verdicts: positive checked, pass, reproduced, replicated, separate_implementation, generalizes; negative fail, not_reproduced, not_replicated, does_not_generalize; and objection, resolved, inconclusive, comment.
3. Canonical JSON and the event chain
canon(x) is JSON with keys sorted at every level, separators , and : with no spaces, and non-ASCII characters written as-is (UTF-8). In Python: json.dumps(x, sort_keys=True, ensure_ascii=False, separators=(",", ":")).
Every write appends one or more events. An event's hash is
hash = sha256_hex( canon({"at", "actor", "kind", "ref", "payload", "prev"}) )where prev is the previous event's hash (64 zeros for the first). Kinds and payloads:
| kind | ref | payload |
|---|---|---|
create | the new node | {type, code, inv, v: 1, body_sha, reason_sha, as?}; body_sha = sha256 of the version's stored body string, reason_sha of its stored reason |
revise | the node | {v, reason, body_sha, reason_sha}; reason is the first 300 characters |
review | the reviewed node | {review: <review node id>, verdict, on_v, official, body_sha, code, inv, as?} |
link | the edge's source | {edge, rel, dst, src_v, dst_v, data_sha, why_sha}; data_sha = sha256 of the stored edge data string, why_sha of its stored reason |
retract | the edge's source | {edge, why_sha} |
withdraw | the node | {why, why_sha, as?}; why is the first 300 characters, why_sha covers the stored reason |
lease_take / lease_release | the node | {task, expires, note_sha, for?}; for = the agent holding the lease when it isn't the actor |
Any event may carry task in its payload: the unit of work that wrote it. as appears only when the importer credits a node or withdrawal to another author; from any other actor it is invalid.
Verifying a record means recomputing every hash in order and checking each prev, then checking the rows against the events both ways: every row was written by an event and matches it field by field, every row an event wrote is present, and no key appears twice. The fields each event fixes:
| table | key | must equal |
|---|---|---|
nodes | create.ref, review.payload.review | type, code, inv from the payload (a review: type review); created_at = the event's at; created_by = the actor, or as |
versions | node + v | at, by = the actor, sha256 of body = body_sha, sha256 of reason = reason_sha (a review's: empty) |
edges | payload.edge | src = ref, rel, dst, src_v, dst_v, at, by = the actor, sha256 of data = data_sha, of why = why_sha |
withdrawals | the node | at, by = the actor or as, sha256 of why = why_sha |
retractions | payload.edge | at, by = the actor, sha256 of why = why_sha |
leases | in id order, one per lease event in order | node = ref, agent = for or the actor, task, kind (take / release), at, expires, sha256 of note = note_sha |
Records written before these fields existed (before 0.7.13) carry events without reason_sha, why_sha, note_sha, as, or a review's code and inv. A verifier checks what those events do carry (a revision's or a withdrawal's first 300 characters, a lease note given in full, a review's investigation from its target) and reports every other non-empty field as not covered, by table and field, rather than passing it silently. Without as, a row the importer wrote may credit any author; that is reported too. A verifier states at the top level whether every row field was vouched for by its event (coverage: complete) or not (partial: fields not covered, or redacted events).
What a verifier trusts. Who the importer is decides which older rows may credit another author, so the importer belongs to what a verifier trusts, and the chain doesn't cover a bundle's policy. A verifier must not take the importer, or any other policy field, from the bundle: it uses a policy its caller supplies (from the record's publisher or host), and the default importer name importer otherwise. Every field the caller supplies must equal the bundle's stated one. When the bundle names a different importer, the verifier says so.
Sequence numbers (seq) label events but aren't hashed: the order is fixed by prev. A verifier requires them to run 1 to n, so a relabelled or gapped log fails.
4. Bundles — hypgraph/1-bundle
A portable record. Every event is carried as the exact string that was hashed (body), and every version body and edge data as the exact stored string. A verifier therefore never has to reproduce canonical JSON: it hashes strings and compares.
{"format": "hypgraph/1-bundle", "generator": "hypgraph 0.1.0", "exported_at": "…",
"complete": true, "redacted_events": 0, "scope": {"inv": "", "title": "whole graph"},
"policy": {"reviewers": [], "instrument": "lab", "operator": "operator", "importer": "importer", "system": "system"},
"head": "<hash of the last event>",
"events": [{"seq": 1, "hash": "…", "body": "{\"actor\":…,\"at\":…,\"kind\":…,\"payload\":{…},\"prev\":…,\"ref\":…}"}],
"nodes": [{"id": "…", "type": "…", "code": "…", "inv": "…", "created_at": "…", "created_by": "…"}],
"versions": [{"node": "…", "v": 1, "at": "…", "by": "…", "reason": "", "body": "<stored canonical string>"}],
"edges": [{"id": 1, "src": "…", "src_v": 1, "rel": "…", "dst": "…", "dst_v": 1, "data": "<stored string>", "…": "…"}],
"withdrawals": [], "retractions": [], "leases": [],
"derived": {"…": "views for viewers; their facts are checked against the rows, their standing is not"}}A scoped bundle (one investigation out of a shared record) replaces events outside it with {"seq", "hash", "prev", "redacted": true}. The chain still links, but the content of those events can't be checked, so a change hidden among them would not show. Verifiers must report complete: false. To publish a study, keep it in a record of its own.
scope isn't covered by the chain, so a verifier requires the one shape an export writes: scope.inv names a node of type investigation, and every node row is that investigation or belongs to it; a bundle without a scope has no redacted events. Otherwise any node could pose as the investigation, or a whole record pass as one investigation, and the views would hide the rest beside a verified chain. A redacted event keeps only its hash, so that it lies outside the investigation is the bundle's word: only the full record, verified against the published head, rules out a change hidden among them. scope.title is a label; a viewer names the scope from the verified investigation.
What a verifier vouches for. ok means the chain links and every row and event agree, both ways. coverage: complete means every row field was vouched for by its event and nothing is redacted (partial otherwise). views: checked means every fact the derived views show that the rows fix is present and agrees with the rows, and the scope is a real one; views: none means the bundle carries no views. views: historical and views: unconfirmed mean the views verify but some lifecycles in them couldn't be checked (below), and the warning names each one: a viewer must not present them as checked. None of them vouches for standing, the report, stages or work orders (as exported), for who an author really is, or that the head was published.
Anchoring. A bundle proves only its own consistency. To prove it wasn't rebuilt from scratch, publish its head hash somewhere independent at the time (a paper, a preregistration, a repository tag). A later bundle whose chain passes through that hash can't have rewritten anything before it. A verifier given a published head (the full hash, or a prefix of at least 16 hex characters) fails unless the chain ends there: hypgraph verify FILE --head HEX, verify_bundle(b, head=HEX). A bundle without a head field is checked for consistency only. How a head is published and signed, and what each check does and doesn't show: verification.md.
Derived views. derived is a convenience for viewers, and a viewer shows it beside a verified chain, so a verifier must check every fact in it that the rows fix: which investigations it covers (the scope's, or every investigation node), which investigations the list shows (each once), which nodes each investigation's graph shows (all of them, unless capped at 5,000) and which live (unretracted) links among them, each node's type, code, inv, by, at, v, title, kind and state.lifecycle, and each link's id, ends, versions, rel, data, at, by and why. Every one of these must be present, not only equal where present: a viewer fills a missing fact with a default, and a withdrawn claim without its lifecycle would read as active. The lifecycle is derived from the verified rows (withdrawn when a withdrawal exists; superseded when a live supersedes or corrects replacement is active or, since 0.4, itself superseded; an investigation's own state otherwise). A verifier checks the lifecycle against the current rule. A lifecycle that only 0.3's rule gives (it counted an active replacement only) verifies, since a 0.3 export shows it so, but the chain doesn't record which version exported a bundle, so the views are historical, not checked. A scoped bundle with redacted events leaves out links from other investigations, and one of them may replace a node: a node shown superseded with no replacement in the rows verifies only if its type can be replaced at all (every type but investigation and review) and it isn't withdrawn, and the views are unconfirmed, since that replacement is among the redacted events. A bundle with no redacted events holds every link, scoped or not, so any other lifecycle the rows don't give fails. scope isn't covered by the chain, so naming one never loosens a check on its own. A bundle exported without derived views verifies, and a verifier says it has none (views: none). Standing (review state, support, at-risk, the sentence) can't be checked from the rows alone: it is what the exporting engine derived, and so are the report, the workflow stages, completion, the question board and work-order status. A verifier reports them as not checked, or re-derives them with the same engine version (hypgraph verify FILE --standing, under the caller's policy with --policy) and compares. A viewer must label them as exported wherever it shows them from a bundle.
5. Derived standing (normative)
Implementations must derive these, never store them:
- lifecycle:
withdrawnif withdrawn;supersededif a live node of the same typesupersedes/corrects
it; an investigation's own state; else active.
- review: computed over reviews by the board (the policy's reviewers ∪ the investigation's
reviewers∪ the
operator and importer), excluding comments. An objection stays open until its author records resolved. The operator, the importer and the investigation's reviewers may resolve anyone's. Then, on the current version: any negative verdict or open objection → contested; else any positive verdict → checked; else a positive verdict only on an older version → needs_revalidation; else unchecked.
- fallen: withdrawn; or a contested claim / result / hypothesis; or a run whose state is failed / timeout /
cancelled / interrupted; or a result whose outcome is failed. Superseded is not fallen.
- at_risk: the fallen nodes reachable upstream through
requires,derived_from,based_onand the run that
produced it.
- independent lines: live supporters, grouped when their roots overlap. Roots are producing runs, the data /
model artifacts those runs use, and sources. Supporters with no provenance at all form one group between them.
- evidence: the best live supporter's profile, ranked `unverified < agent_reported < lab_recorded <
project_rerun < separate_implementation < new_data < external`.
- A source is external.
- A result produced by a
kind=labrun that the instrument filed is lab_recorded. - A replication edge on the run raises the profile according to its
differs. - A result of
kind=citedis external only if itcitesa source node. - basis_changed (decisions): a
based_onedge whose target's version has moved past the pinneddst_v. - written blind (results): a work order with
blind: trueopens a window at its creation event and closes it at
the first release (return or cancel) or withdrawal of any order in its rework chain. A result its assignee created inside the window, and has not revised since it closed, is written blind under that order, provided the assignee had created nothing in the record before the window opened. While the window is open the result is sealed. Once it is closed, earlier results whose numbers it repeats are agrees_blind, not restates.
- blind lines (claims, hypotheses): independent lines whose group contains a result written blind under a
closed window.
6. Write rules (normative for writers)
supports/challengesneed awhyof at least 10 characters.testsneedsdata.expected.replicates
needs data.differs. uses needs data.role.
- Only the instrument files a
kind=labrun, and only the instrument records what that runproduced. - Claims must pass the checks in §7, unless written by the operator or importer, or in an investigation with
strict_claims: false.
- Nobody officially reviews their own node.
- Writes into an
archivedorpublishedinvestigation are refused (reserved authors excepted). - Only the importer may set a write's time. Everyone else's comes from the writer's clock.
- A writer sealed from nodes (it holds an open blind work order, or a host passes its reader's view with
Graph.sealed_from) may link to a sealed node only to say its own work supports, challenges or tests a hypothesis. It may not bind a measurement to a sealed result, rest a claim on sealed work, review or revise a sealed node, or issue a work order on sealed work, and a seat holding a blind order issues no orders. These are refused with one message, before any check reads the sealed material, so that no answer depends on what is sealed.
7. Claim checks
A claim is checked before it enters the record. Each failed rule is reported with a stable code, a message that does the arithmetic, a suggested fix, and the words that triggered it. Checks are skipped for the operator and importer, and in an investigation with strict_claims: false. Leads and conventions skip rules 7–9.
Arithmetic. For a count of k in n trials, the 95% interval is the Wilson score interval. At k = 0 (or k = n) it is the exact one-sided bound 1 − 0.05^(1/n), the "rule of three" (≈ 3/n). "Never, in n trials" means at most that rate, and never zero. Two counts are compared with Newcombe's hybrid score interval and a pooled two-proportion z-test. The smallest detectable difference uses 80% power at two-sided α = 0.05.
| # | Code | Refused when |
|---|---|---|
| 1 | SCOPE_MISSING | a finding or exclusion has a scope shorter than 10 characters |
| 2 | EVIDENCE_MISSING | a finding, exclusion or inconclusive claim has no supporting node |
| 3 | MEASUREMENT_MALFORMED / MEASUREMENT_UNKNOWN_FIELD / MEASUREMENT_MISMATCH | a measurement doesn't name a result and a key present in its values, or states a value / k / n different from the result's at the precision written (a fraction ≤ 1 may be written as a percentage) |
| 4 | UNBOUND_NUMBER | a number in the statement (digits, or the words two to twenty; digits in quotes are names) is not one of: a bound measurement's numbers; for a count, its rate, complement or n − k; for any two measurements, their difference, ratios, relative change or sum; the scope's numbers; the investigation's constants; or the conventions 95 / 99 / 90 / 80 / 0.05 / 0.01 when the statement speaks of intervals, bounds, confidence, power or significance. Statistical quantities (a rate's 95% bounds; for two counts the difference interval, p-value and detectable difference) count only when written to at least one decimal place. |
| 5 | ABSOLUTE_WITHOUT_COUNT | an absolute word (never, always, all, every, none, impossible, cannot, proves, certainly, guaranteed, eliminates, entirely, completely, universally, consistently, perfect, solves, zero, 100%, …) with no bound count where k is 0 or n |
| 5a | ABSOLUTE_UNRELATED_COUNT | the counts with k ∈ {0, n} share no content word with the statement in their name or key |
| 5b | ABSOLUTE_UNDERPOWERED | the 95% bound on the opposite rate is above 5% and the statement doesn't say n ("never leaks" rather than "no leaks in 50 prompts" — a statement that gives its n is scoped to what was seen, and accepted). The message gives the honest wording and the trials needed for a 1-in-1,000 bound. |
| 5c | ABSOLUTE_HIDES_DENOMINATOR | the count is enough, but n appears in neither the statement nor the scope |
| 6 | CONVENTION_HEDGED | a convention hedges (could, might, suggests, consistent with, …) |
| 7 | COMPARATIVE_WITHOUT_COMPARISON | a comparative (better, beats, outperforms, higher than, improves, exceeds, …) with fewer than two measured sides, no bound p-value or interval, and no supporting claim that binds two sides |
| 8 | SIGNIFICANCE_WITHOUT_TEST / SIGNIFICANCE_NOT_SHOWN | "significantly" / "statistically" with no bound p-value and fewer than two counts, or when the bound p-value, or the test the record runs on the first two counts, is not below 0.05 |
| 9 | CAUSAL_WITHOUT_INTERVENTION | a causal word (causes, leads to, results in, drives, due to, because of, responsible for, …) when no supporting result was produced by a run that executes an experiment |
| 10 | EXCLUSION_WITHOUT_POWER | kind: exclusion, or wording that says an effect is absent (no effect, no difference, makes no difference, does not affect, rules out, ineffective, …) in a finding, without a supporting result with power: demonstrated or one produced by a run of an experiment with a power_check. With two counts, the message gives the smallest detectable difference. |
| 11 | UNCORRECTED_EFFECT | a finding asserts an effect (changes, lowers, differs, affects, significant, …) on an outcome, while a live result in the investigation holds that outcome's corrected p-value (a field named with holm / bonferroni / fdr / adjusted / corrected …) at or above 0.05, or a count of 0 outcomes surviving correction (n_holm_sig), and the effect word is neither qualified within 20 words as uncorrected (uncorrected, unadjusted, nominally, without correction, …) nor names the correction with a corrected value bound as a measurement. A family-wide value (min_adjusted_p) applies when the claim binds a nominal p < 0.05. Thresholds (bonferroni_alpha_threshold) are not evidence. Honest forms: bind the corrected p and say "not significant after correction", or file kind: lead. |
Exposure. A claim may set exposure (how many uses, users or units it will be applied to) and exposure_unit. Absolute refusals then state how many failures the bound leaves unruled out at that scale. An accepted claim's standing carries bounds (per count: k, n, rate, lo, hi and, with exposure, unruled_out), its sentence reads "'never' = at most 0.1% (0 of 3,000, 95%), up to 998 of 1,000,000 not ruled out", and a "significantly" claim carries test (the difference, its interval and p).
Blast radius. Withdrawing a node, or a verdict that makes it fall, returns blast_radius: every node resting on it by type, with claims and decisions named and, if a spend meter is connected, the cost of the work behind them.
8. The refusal log
A refused claim never enters the record, so its trace lives in a separate append-only table, refusal_log, with its own SHA-256 chain over canon({at, kind, author, inv, node, op, statement, codes, detail, ref, prev}). The record's own chain and verify() are unaffected.
kind: refused: the author, the investigation, the statement (≤ 1,500 characters; a result's summary), the
sorted codes, and the problems (code, message, fix) it was refused with; detail holds the whole body, the support it had and the node type (claim, result, or a typed node). op is create or revise; node is set for a revision.
- A refusal on shape — a field the type doesn't have (
SHAPE_UNKNOWN_FIELD), a value outside an enum
(SHAPE_NOT_AN_OPTION), a missing, too long or too short field, a wrong type, or a link the relation grammar refuses (LINK_NOT_ALLOWED, LINK_UNKNOWN_RELATION, LINK_NO_NODE) — is logged the same way with detail.type shape or link. Its message carries the fix: where the field goes (code on a result belongs to the run; an experiment's expected goes on its tests link) or what each option means. The report counts these apart from overclaims and the replay skips them.
kind: fixed: a later claim, result or revision by the same author in the same investigation, accepted within
24 hours, that says the same thing. "The same" means the same node for a revision; otherwise a Jaccard overlap ≥ 0.3 of the statements' content words, or, for a result, at least half of the refused result's value keys carried again. ref is the refusal it fixes.
A report aggregates per author (refused, fixed, open, codes) and per rule. A replay runs every logged refusal through the checker as it is now — claims through the claim and profile checks, results through the value and profile result checks as their author — and says for each whether it would be refused the same way, differently, or accepted; RESULT_RESTATES and typed-node refusals depend on the record's state at the time and are counted as skipped. A change to a rule is judged on that replay over the real corpus before it ships.
9. Checking a write-up against the record
A checker reads markdown sentence by sentence and checks it against the record. Code blocks, inline code, URLs, link targets and HTML comments are skipped, except a fenced block whose info string is answers: that is the report's machine-read answer (a JSON object, keyed by question code or flat), and every number or true / false in it is checked against the claims. Quoted passages and negated words ("cannot be proven") are not treated as the writer's assertion. Codes are matched within the investigation being reported on.
| level | code | when |
|---|---|---|
| error | REPORT_CITES_WITHDRAWN | a sentence cites a withdrawn node and doesn't say so (withdrawn, retracted, failed, superseded, earlier, …) |
| error | REPORT_OVERSTATES_STANDING | settled language (confirmed, established, shown, proven, holds, …) about a claim or hypothesis that is unchecked, stale, contested or at risk, or a result that is contested or at risk |
| error | REPORT_UNKNOWN_CODE | an INV / RUN / CL / RS / RV code the investigation doesn't have |
| error | REPORT_ABSOLUTE_UNBACKED | never / always / impossible / guaranteed / eliminates / proven / 100% asserted, not in an instruction, with no cited claim carrying a count with k ∈ {0, n} |
| error | REPORT_ABSENCE_UNBACKED | "no effect" / "rules out" / "doesn't matter" without citing a claim filed as exclusion or inconclusive |
| error | REPORT_SIGNIFICANCE_UNBACKED | "significantly" without a p-value or interval in the sentence and without citing a node that carries a test (a negated "not significant" is a warning) |
| error | REPORT_ANSWER_STALE | a value in the answers block that only stale, superseded or withdrawn claims (or stale results) on that question give, or that a current claim binds to a different value under the same field while an old one binds it to this; or a question whose every claim is old |
| error | REPORT_ANSWER_UNBACKED | a value in the answers block that no current claim on the question states or binds, at the precision the answer gives it and with its sign (a count k of n, bound or written as "k of n", backs its rate); or a question answered with no claim at all. A number a claim names only as a figure that no longer holds ("the earlier count of 789 is superseded") doesn't back it. URLs, paths and hashes in the block are provenance and aren't checked. |
| error | REPORT_ANSWERS_UNREADABLE | the answers block isn't valid JSON |
| warning | REPORT_UNSOURCED_NUMBER | a number not contained in or implied by the investigation's results, live claims' measurements, statements and scopes, the investigation's scope and constants, or experiment and run settings. Integers ≤ 12, years, dates, times and 7-digit+ ids are skipped. Statistical quantities need one decimal. |
| warning | REPORT_OMITS_DISPUTE | cites a contested or at-risk node without saying so |
| warning | REPORT_CITES_SUPERSEDED | cites a superseded node without saying so |
| warning | REPORT_CAUSAL_UNCITED | causal language with no node cited |
| note | REPORT_BOUND | an absolute backed by a cited count: what the count supports |
Calibrated on 24 real write-ups checked against the live record they report on.