kg: invalidate() should symmetrically reject ended < valid_from for the target row
Background
#1214 added an inverted-interval guard to `KnowledgeGraph.add_triple()` so a row with `valid_to < valid_from` cannot be persisted. This closes the foot-gun on the write path.
`KnowledgeGraph.invalidate()` (knowledge_graph.py:223 at the time of #1214) writes `valid_to` against an existing row without comparing the new `ended` value to that row's existing `valid_from`. A caller passing `ended="2020-01-01"` against a triple `valid_from="2026-01-01"` silently produces the same inverted state #1214 was preventing on the create path — invisible to every `query_entity` call, durable in SQLite forever.
Proposal
In `invalidate()`, look up the target row's `valid_from`, and reject the call with a clear `ValueError` if `ended < valid_from`. Mirrors the message format from `add_triple()`. The row may legitimately have multiple matches (subject/predicate/object can repeat across time); the guard should fire if any matched row would become inverted.
Acceptance
- `invalidate(s, p, o, ended="2020-01-01")` against a triple `valid_from="2026-01-01"` raises ValueError naming both bounds.
- `ended == valid_from` is allowed (point-in-time fact).
- Open intervals (target row had no `valid_from`) pass through.
- Test in `tests/test_knowledge_graph.py` exercising the rejection + non-regression for the legitimate paths.
Surfaced during review of #1214.
Source: MemPalace/mempalace