Fix #67: context export named parent uuids that are not in the network - #74
Merged
Merged
Conversation
append_regulators decomposes each catalyst and regulator to its terminal members and wires the edges from those member uuids. The context export wrote the uuid carried on the raw catalyst/regulator fetch row instead — the undecomposed parent — so it named nodes that do not exist. Measured on the ten-pathway catalog before the fix: input 3,641 rows 0 orphaned 0.0% output 3,491 rows 0 orphaned 0.0% catalyst 2,228 rows 2,228 orphaned 100.0% regulator 1,018 rows 1,018 orphaned 100.0% TOTAL 10,378 3,246 31.3% Every consumer asking "which node is this entity, at this reaction, in this role" got a wrong answer for exactly the two roles that carry the causality. Catalyst and regulator rows are now read from the emitted network rather than from the fetch rows, which makes the export correct by construction: it reports what was wired, not what was requested. Those edges carry no edge_reaction_id, but their target IS the virtual-reaction node, so the reaction is recovered through reaction_id_map exactly as the input/output rows already do. Two guards, because the quiet failure is what made this survive: - the export refuses to write when a row names a node absent from the network, and reports the count by role rather than shipping it; - omitting the network while catalyst rows exist now raises, instead of falling back to the fetch rows and silently restoring the bug. The bug was held in place by a test. test_export_node_reaction_context asserted that the parent uuid "c1" appears in the export — the exact wrong behaviour — so the suite was green while a third of the export was broken. That test is inverted and kept as the record, alongside a new one that fails when the network and the export disagree. Verified as a negative control: reintroducing the parent uuid makes the new test fail, restoring the fix makes it pass. 167 tests pass in the no-database tier. Found while specifying deltasignal's 005-node-identity-mapping, where the same 31.3% turns out to be 25 of the 42 diagram-glyph join misses. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This was referenced Sep 14, 2026
CI runs mypy on src/ and I only ran pytest locally, so this failed on all three Python versions with a green local run. The catalyst/regulator loop reused node_uuid and rid, both of which the registry loop above binds as str from Dict[tuple, str]. Rebinding them with DataFrame values, which are Any | None, is an assignment type error. Renamed to edge_node and edge_reaction, which also reads better — they are a different thing from the registry's entries. Verified locally with the full CI command set this time: mypy clean on 10 files, ruff clean, 167 tests passing at 46.79% coverage against the 40% floor. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #67.
append_regulatorsdecomposes each catalyst and regulator to its terminalmembers and wires the edges from those member uuids. The context export
wrote the uuid carried on the raw fetch row instead — the undecomposed
parent — so it named nodes that do not exist in the network.
Measured on a ten-pathway catalog before the fix:
Every consumer asking "which node is this entity, at this reaction, in this
role" got a wrong answer for exactly the two roles that carry the causality.
The fix
Catalyst and regulator rows are now read from the emitted network rather than
from the fetch rows, which makes the export correct by construction: it
reports what was wired, not what was requested. Those edges carry no
edge_reaction_id, but their target is the virtual-reaction node, so thereaction is recovered through
reaction_id_mapexactly as the input/outputrows already do.
Two guards, because the quiet failure is what let this survive:
network, and reports the count by role rather than shipping it;
falling back to the fetch rows and silently restoring the bug.
The bug was held in place by a test
test_export_node_reaction_contextasserted that the parent uuidc1appears in the export — the exact wrong behaviour — so the suite was green
while a third of the export was broken. That test is inverted and kept as the
record, alongside a new one that fails when the network and the export
disagree.
Verified as a negative control: reintroducing the parent uuid makes the new
test fail; restoring the fix makes it pass.
Verification
0 → 790 of 790 distinct
(node, reaction, role)triples. Zero orphansis trivially achievable by dropping rows, so coverage was checked
separately.
3,758 catalyst edges collapse to 790 distinct triples because the same
member catalyses the same reaction through many parallel virtual-reaction
instances. The old count enumerated parent × reaction, a different and
wrong population.
the originals — edge counts, node counts and the
edge_type/sign/and_or/stoichiometry signature match 10 of 10 — so no
benchmark number moves because of this.
Known, not fixed here
Catalyst and regulator edges carry no
edge_reaction_idat all (3,758 and1,124 of them). The export now routes around it via the target reaction node,
so nothing is blocked, but the column is empty where it should not be.
🤖 Generated with Claude Code