Seventeen entries declare themselves part of a causal chain that does not exist, and the validator could not see it
also called dangling data refs · forward reference audit · causal chain gap
Entries here point at each other through formal links, which are checked, and through fields that merely name another entry, which were not - and 61 of those names point at entries that do not exist. Most are deliberate promises to write something later, but one is named by seventeen different entries as the backbone they belong to, and that backbone was never written.
The same thing, for a scientist
`validate-graph.mjs` checks edge targets and `data.analyses`. It does not check arbitrary `data.*` keys, so an id named in `data.chain`, `data.interfaces`, `data.affects` or `data.complex_partners` can rot silently. First audit: **61 dangling references naming 29 distinct missing nodes.** Ranked by demand, the head is **`mechanism-kmt2a-fusion-causal-chain`, named by 17 entries via `data.chain`** — a spine seventeen mechanisms declare membership of and which was never created. Found because the PRC1.1 beat noticed `gene-rnf2` listing a non-existent `gene-rybp`.
What the audit found
| missing node | entries naming it | data field |
|---|---|---|
mechanism-kmt2a-fusion-causal-chain | 17 | chain |
interface-mllt3-ahd-competition-hub | 6 | interfaces |
exp-noncoding-dependency-crispr-screen-kmt2a-aff1 | 5 | planned_experiments |
interface-aff4-ell2-occludin | 3 | interfaces |
gene-rybp | 2 | complex_partners |
| five named drugs (daunorubicin, doxorubicin, mitoxantrone, tisagenlecleucel, inotuzumab) | 1 each | affects |
The ranking is the finding, not the list. This project writes forward references on purpose: naming an id you intend to create is a commitment, and the graph already tracks the backticked kind with check-prose-refs.py. So a target named once is usually a typo or a small gap; a target named seventeen times is a structural hole that seventeen entries have already declared they depend on.
The five drugs are the clearest small gaps. risk-anthracycline-cardiotoxicity-infant-leukemia names daunorubicin, doxorubicin and mitoxantrone in data.affects and none has an entry — in a graph that carries bite_back on every intervention, the three anthracyclines an infant actually receives are unwritten.
Why the validator could not see it, and why that is not a bug
validate-graph.mjs checks the fields it knows the schema for: edge sources and targets, and data.analyses. data.* is otherwise free-form, which is what makes it useful — beats invent keys like complex_partners, interfaces, chain, writers, member_fusions as the biology needs them — and also what makes it unauditable by a schema-driven checker.
So the fix is not to constrain data.*. A separate checker (scripts/check-dangling-data-refs.py) scans any id-shaped string under data against the real id set and ranks the misses by demand. It reports warnings, not errors, because making a forward reference an error would punish the practice it exists to support.
This is the third instrument gap this month and they share a shape: the named-but-unwritten queue could not see antigens that are not genes; the contradiction checker could not see disagreements spanning two entries and two papers; and the validator could not see references in fields it has no schema for. Each instrument is blind precisely where the graph is most freely expressive.
- Nothing here reaches a patient, but one gap is clinical in character: risk-anthracycline-cardiotoxicity-infant-leukemia names daunorubicin, doxorubicin and mitoxantrone as the drugs it concerns and none is written. A reader following the risk to the agent finds nothing.
- Write mechanism-kmt2a-fusion-causal-chain, or remove data.chain from the 17 entries that name it. Seventeen entries asserting membership of a node that does not exist is the single largest structural gap this audit found.
- Did seventeen entries independently commit to that id, or did one copy propagate? The audit cannot distinguish consensus from propagation.
- The three anthracyclines an infant actually receives are named in data.affects and have no entries, in a graph that requires bite_back on every intervention.
- E1strong confidence 4 of 5: Replicated in peer-reviewed work by more than one group.analysis
Across 1,552 entries, 61 id-shaped strings in data.* fields name nodes that do not exist, covering 29 distinct missing nodes; the most-demanded is mechanism-kmt2a-fusion-causal-chain, named by 17 entries in data.chain, followed by interface-mllt3-ahd-competition-hub at 6 and exp-noncoding-dependency-crispr-screen-kmt2a-aff1 at 5.
check-dangling-data-refs.py: id-shaped references under data.* resolved against the real id set and ranked by demand · Oncofusion knowledge graph (internal) 2026What this evidence cannot show
Run 2026-09-20. **The id-prefix pattern is a heuristic**: a free-text data value that happens to look like an id is scanned, and an id using a prefix outside the list is missed, so 61 is neither a floor nor a ceiling. **Most of the 61 are deliberate forward references rather than defects**, which is why the checker reports warnings and why the demand ranking - not the count - is the usable output. Strong for the measurement; it is the interpretation that is graded by reading.
Evidence that argues against this (1)
- A forward reference that is never fulfilled is indistinguishable from a typo after enough cycles, and this audit cannot tell them apart either - it infers intent from how many entries share the reference. Seventeen entries could as easily have copied a wrong id from one another as independently committed to a real one, and nothing here distinguishes propagation from consensus. Shi J et al. 2013
THE CASE THAT STARTED THIS: it lists gene-rybp in data.complex_partners and no such node exists. Not a validator error, because data.* is free-form and unaudited.
THREE NAMED DRUGS, NO ENTRIES: daunorubicin, doxorubicin and mitoxantrone are named in data.affects and none is written.
- supports →The named-but-unwritten queue is keyed on gene symbols, so CD15 is invisible to it at 23 entries
THIRD INSTRUMENT GAP OF THE SAME SHAPE: each checker is blind precisely where the graph is most freely expressive - non-gene antigens, cross-entry contradictions, free-form data keys.
SAME FAMILY: a checker anchored on the fields it has a schema for cannot see the ones beats invent as the biology needs them.
ANOTHER GAP VISIBLE ONLY ACROSS ENTRIES rather than within one.
the audit that found seventeen entries naming this node before it existed
answers that entry's open question: propagation, not consensus, in all nine cases