Read the arrows back before approving the diagram
Shipped
I added a reality gate to Ghostwriter’s generated architecture cards. In v0.20.1, the request must carry source-backed nodes, edges, and exceptions, and the finished image must be transcribed and compared with that model. Every edit triggers another check of the entire graph, not only the requested correction.
This is a different review from checking that the labels are spelled correctly. A perfectly legible arrow can point in the wrong direction.
Give each relationship a meaning
Use Python 3.11 or newer. The small checker below works on manually transcribed graphs; it does not read pixels or perform OCR. That boundary matters because copying the intended graph into the observed graph would produce a passing test without inspecting an image.
Save the first block as graph_check.py. Each edge is an ordered triple: source, destination, and meaning. The direction is part of its identity, just as directed and undirected edges are distinct in Graphviz’s DOT language.
def reconcile(expected_nodes, expected_edges, observed_nodes, observed_edges):
expected_nodes = set(expected_nodes)
observed_nodes = set(observed_nodes)
expected_edges = {tuple(edge) for edge in expected_edges}
observed_edges = {tuple(edge) for edge in observed_edges}
for edge in expected_edges | observed_edges:
if len(edge) != 3:
raise ValueError("edges need source, destination, and meaning")
for nodes, edges in ((expected_nodes, expected_edges),
(observed_nodes, observed_edges)):
if any(source not in nodes or target not in nodes for source, target, _ in edges):
raise ValueError("edge names an unknown node")
return {
"missing_nodes": sorted(expected_nodes - observed_nodes),
"extra_nodes": sorted(observed_nodes - expected_nodes),
"missing_edges": sorted(expected_edges - observed_edges),
"extra_edges": sorted(observed_edges - expected_edges),
}
def passes(result):
return not any(result.values())
Set difference makes both sides visible. Checking only that expected edges are present would miss an invented extra arrow. Checking only that observed edges are allowed would accept an image that omitted half the system.
This simple representation treats duplicate identical edges as one relationship. If parallel edges or repeated visual elements matter in your diagram, use counts instead of sets. Keep containment and exception branches explicit too, rather than assuming every line means a data transfer.
Keep the evidence separate from the transcription
Append this fixture. Its evidence is illustrative and local: the stated demo requirement is that a source supplies an adapter, which emits an artifact. In a real system, replace those requirement strings with file paths, symbols, and the source revision you inspected.
EVIDENCE = {
"E1": "Demo requirement: source supplies adapter input.",
"E2": "Demo requirement: adapter emits artifact.",
}
NODES = {"source", "adapter", "artifact"}
EDGES = {
("source", "adapter", "supplies"): "E1",
("adapter", "artifact", "emits"): "E2",
}
assert all(evidence_id in EVIDENCE for evidence_id in EDGES.values())
if __name__ == "__main__":
correct = list(EDGES)
reversed_arrow = [
("source", "adapter", "supplies"),
("artifact", "adapter", "emits"),
]
extra_arrow = correct + [("source", "artifact", "bypasses")]
for name, observed in (("correct", correct),
("reversed", reversed_arrow),
("invented", extra_arrow)):
result = reconcile(NODES, EDGES, NODES, observed)
assert passes(result) == (name == "correct")
print(name + ": " + ("accepted" if passes(result) else "rejected"))
Run:
python3 graph_check.py
Expected output:
correct: accepted
reversed: rejected
invented: rejected
For an actual card, open the rendered image and write down the visible nodes and relationships without looking at the intended edge list. Mark ambiguous connections as unresolved instead of assigning the direction the prompt asked for. Feed that transcription to the checker, then inspect its missing and extra lists.
This also improves the material available for an accessible description. The W3C guidance for complex images calls for a complete text equivalent of the information conveyed. The validated graph gives you a factual basis for that description, though the code above does not generate one.
Gotchas
A validation step is not necessarily a transformation. The release specifically prohibits inventing flow from proximity or turning a check into a transformation. The symptom is an arrow implying that a validator produces data it only examines. Name edge meanings and verify them against executable behavior before drawing.
Fixing one label can alter another relationship. The tagged contract requires full graph reconciliation after every image edit. Checking only the edited region can miss a shifted connector elsewhere. Re-transcribe the candidate as a whole before carrying forward any approval.
A checker can agree with its own input mistake. This is a reader-side limitation of the example. If the expected graph is wrong, or the transcription was copied from the prompt, equality proves little. Keep source verification and image observation as separate acts, and record uncertainty rather than forcing a match.
Sources
- Graphviz DOT language — directed relationships as distinct graph structure.
- Python set operations — finding missing and extra relationships.
- W3C complex images — text equivalents for diagrams.