The 4 tools (find_contradictions, find_anachronisms, find_orphans,
find_ontology_violations) now read pre-materialized violation nodes
from Neo4j, populated by seed.py:seed_violations. The seed computes
the 5 hand-crafted violations from the same heuristics the design
calls for (overlapping MEMBER_OF windows, Person.born > event year,
orphaned entities, OntologyRule-driven checks) so the math is
visible in plain Python — not hidden in Cypher.
* plugins/consistency.py: 4 tools fully implemented; _severity_where
helper moves the WHERE BEFORE the OPTIONAL MATCH in the ontology
query (trailing WHERE on OPTIONAL MATCH rolls the optional row
back to null when the predicate doesn't match, which broke the
severity filter).
* seed.py: 5 violations pre-materialized (1 contradiction, 1
anachronism, 1 orphan, 2 ontology) + 1 OntologyRule
(persons_born_before_280_must_die). Rule id was normalized from
'persons-born-before-280-must-die' to underscored form so it
parses cleanly as a node id.
* examples/test_consistency.sh: 10 assertions across 4 tools
(severity filter variants), exits 0.
* tests/test_consistency.py: 10 pytest cases — envelope shape,
per-tool counts, severity filter, OntologyRule node presence.
* README.md: T5 marked done.
Verification:
pytest tests/test_consistency.py 10/10 PASS
bash examples/test_consistency.sh 10/10 assertions, exit 0
bash test.sh no regressions, exit 0
Every read tool (entity_context, state_at, was_true_at, ancestors_of,
descendants_of, lineage_of, recall_images, search_images_by_caption,
register_image) now accepts an optional world_id parameter, defaulting
to 'default' so v1 callers see no change.
* Neo4j: every Person/Faction/Location/Item/Event/Lineage/Image node
carries a world_id string property. seed.py backfills existing nodes
to 'default' and writes a second minimal world 'arda_greyscale'
(2 people, 1 faction, 1 location, 4 relations, 1 event, 1 era).
* Cypher: every MATCH in the world/lineage/images plugins filters by
world_id on the right node type.
* New admin tool list_worlds() — runs
MATCH (n) WHERE n.world_id IS NOT NULL RETURN DISTINCT n.world_id
to expose the namespace.
* Postgres image_manifest gains a world_id column (init.sql).
* 4 placeholder images generated for the greyscale world (portraits of
Mael and Sira, Ashen Hall, the Ashen Oath).
* test.sh now calls every tool with world_id='default' to verify v1
behaviour, plus a new section 12 that calls list_worlds().
* tests/test_multi_world.py: 14 pytest cases covering list_worlds,
entity_context/world isolation, was_true_at, state_at, lineage
filter, image recall isolation, and the image_manifest schema.
Verification:
pytest tests/test_multi_world.py 14/14 pass
bash test.sh 12/12 sections green,
MinIO image bytes flow 200 OK
list_worlds() returns [{arda_greyscale}, {default}]