"The meaning is never in one place" and "the documents contradict each other across versions" is the exact problem we hit with a house instead of a codebase. ML Systems builds an ontology for residential deconstruction, and the same building is described by a town assessor record, a homeowner's memory, satellite and street imagery, and seven AI agents that each write their own claims. They disagree constantly, and the disagreements are usually about the same node.
Two choices we made that might transfer back to your legacy-system case:
The ontology types claims, not facts. Your "PKG_INVOICE is described by specification v3" is true for one release and false for another; our "roof re-shingled in 2016" is STATED by the homeowner, absent from the assessor's RECORD, and MODELED by vision. Instead of resolving that at ingestion, every edge carries its source and an evidence grade (MEASURED > STATED > RECORD > MODELED), authority is scoped to a domain (the assessor is authoritative on legal and valuation facts, vision on the visible envelope, the homeowner on intent and recent work), and where two credible sources still collide the node resolves to a conflict state and stays there. The LLM is allowed to see the conflict. For a 20-year document trail I suspect the contradictions are the most valuable edges in the graph, not noise to clean out.
Your "the model never describes what an artifact does internally" rule matches ours exactly. The Collective Ontology stores what a member is and what it touches (a rafter sits on the plate, ties to the ridge, is fastened through by the sheathing) and nothing about how; the disassembly sequence is derived from the edges as a DAG, not stored. The ontology grows by extending codes, not storage, which is your PR-on-Turtle-files loop with the reasoner swapped for a scheduler.
The territory article is the one I'd like to read. Extracting a reliable graph from contradictory sources is where our work sits too.
github.com/MLSystemsRI/ml-systems-public