scripts/relations.py holds the vocabulary once — which relations exist, what each asserts, which pairs are oppositions — after four scripts each kept their own copy of the list and the four disagreed. check_relations.py runs that vocabulary over the corpus and is the gate make check enforces.
The semantics were the open part, and they were settled on 2026-09-13. The zmainen/elife-claim-trees#125 ruling split the old dissociates-with in two, because it was carrying both readings at once: the neutral contrast keeps the name and moved to GAPS, where it opposes nothing and exports under no MIRA predicate, and the oppositional reading became in-tension-with — two claims the paper asserts whose implications pull against each other. in-tension-with exports under mira:opposes but is kept out of CONTRARY, so the support-plus-oppose lint leaves it alone; that is what made the twelve pairs that carried both a support and an opposition coherent rather than errors. zmainen/elife-claim-trees#28 supplied the other missing half, refutes, so a failed prediction can be written down.
Both rulings are written into scripts/relations.py and gated by check_relations.py. What remains open is not the vocabulary but what a consumer can do with it: zmainen/claim-graphs#7 asks MIRA for a non-polar relation mechanism, because 59% of this corpus's relations still reach it with no interpretable predicate.