Per docs/plan/exec/07-harness.md sub-slice 7.1:
- tests/harness/questions.yaml — the human-friendly
YAML source. 50 questions across the 5 design-doc
types (10 each): identity, time_fact, world_state,
causal, narrative. Each question pins id, type,
query, expected_tools, expected_answer_shape, and
expected_citations. Targets the Mardonari codex
(the slice 0 fixture) so the harness can run
end-to-end against the real graph.
- tests/harness/questions.json — the compiled JSON
(committed so the runner reads it without rebuilding).
- scripts/harness/build_questions.py — the strict
compiler. Validates the YAML schema, counts questions
per type, enforces uniqueness, writes the JSON.
Validation errors fail loudly with field paths.
- tests/harness/test_questions.py — 6 tests pinning the
contract: schema, 50 total, 10 per type, expected_tools
non-empty, ids unique, version set.
Track A only (no API key needed). Track B (executing
against the live LLM) is gated on $OLLAMA_API_KEY.
Suite: 761 → 767 (+6).
Co-Authored-By: Claude <noreply@anthropic.com>
Adds the slice 6.6 migration as a library helper
(lore_engine_poc.migration: scan_codex_for_planes /
apply_plane_migration) plus a thin CLI wrapper at
scripts/05_migrate_planes.py.
Discriminator: frontmatter signals — plane: true,
tags contains plane, OR type: plane — promote an
entry to :Plane. Default Material Plane convention
remains mardonari.material (added by the 6.4
backfill). Idempotent: re-running on the same codex
produces the same graph state.
LAYER_OF edges are created only between co-referenced
Planes (markdown body [[X]] → Plane node X). Direction
follows docs/17-planes.md: (:Plane A)-[:LAYER_OF]->
(:Plane B) where A is the layer and B is the parent.
In the voldramir fixture, Voldramir(demiplane) LAYER_OF
Underdark(plane).
The script supports --dry-run (print planned changes,
exit 0) and --codex / --setting overrides.
Suite: 747 → 756 tests (+9). No regressions.
Co-Authored-By: Claude <noreply@anthropic.com>
Both MCP entry scripts (05_mcp_server.py for stdio and
06_mcp_http_server.py for Streamable HTTP) now select their
graph backend at startup through a shared loader
(scripts/mcp_server_entry.load_graph):
* LORE_GRAPH_BACKEND=pickle (default) — load the
.graph.pkl built by 01_ingest.py.
* LORE_GRAPH_BACKEND=neo4j — connect to Neo4j at
$LORE_NEO4J_URI (default bolt://127.0.0.1:7687) and
load the mirrored graph.
* Anything else — clear error and exit 4.
Exit codes:
* 0: graph loaded (only happens if the caller ignores
the sys.exit() call below and treats load_graph() as
non-throwing — for the supported backends, load_graph
returns normally).
* 1: pickle path missing.
* 2: neo4j_graph not importable.
* 3: neo4j unreachable.
* 4: unknown backend value.
Neo4jGraph.__init__ now eagerly calls verify_connectivity()
so the loader fails loudly at startup rather than on the
first query — the driver pool opens sockets lazily otherwise,
and the first session.run would be too late for the
entry scripts to log a clear error.
Refactors:
* scripts/05_mcp_server.py: removed inline _load_graph(),
now imports from scripts.mcp_server_entry.
* scripts/06_mcp_http_server.py: same.
* lore_engine_poc/neo4j_graph.py: Neo4jGraph.__init__
eagerly verifies connectivity.
Tests:
* tests/test_mcp/test_backend_switch.py — 5 docker-gated
tests (pickle default, neo4j up, neo4j down exits 3,
garbage backend exits 4, trivial registry works with
both backends).
Suite: 624 -> 629 passed (+5 backend-switch tests, all 559
baseline + 38 Neo4j + consistency + ingest + backend-switch
tests preserved).
After the in-memory graph + pickle are written, the new flag
mirrors the full graph into the Neo4j 5 container at
$LORE_NEO4J_URI (default bolt://127.0.0.1:7687). The flag
is opt-in (default off) so the existing test suite's
invocations of 01_ingest.py without Docker still work.
The mirror logic:
* Pre-pass for LoreSource nodes (full metadata via
add_lore_source so SOURCED_FROM links find them with
name, source_type, reliability, source_confidence).
* Pre-pass for bare names (entities registered without
any edge participation — keeps :Entity count in sync
with in-memory all_names()).
* Then the edges, add()-ed one by one.
Failure semantics:
* Neo4j unreachable at startup → log + exit 3.
* neo4j_graph not importable → log + exit 2.
* Pickle is always written before the mirror attempt, so
a flaky Neo4j container never loses the in-memory state.
Consistency runner stability:
_detect_contradictions Pattern 2 (same object, different
subjects) now sorts the two claims alphabetically so
claim_a / claim_b are stable across runs. The
graph.all_names() set iteration order is otherwise
non-deterministic across Python processes and across
the in-memory / Neo4j backends, and the original
dict-iteration insertion order broke when slice 5.4
migrated to all_names().
Tests:
* tests/test_scripts/test_ingest_neo4j.py — 5 docker-gated
tests (exits zero, entity count, relation count,
default-off untouched, fails loud on unreachable URI).
* tests/test_consistency/test_runner_categories.py — one
test updated to assert claim_a/claim_b as a set rather
than a specific order (matches the runner's new
lexicographic-sort contract).
Suite: 619 -> 624 passed (+5 ingest-neo4j tests, all 559
baseline + 32 Neo4j + consistency + ingest tests preserved).
* scripts/06_mcp_http_server.py: uvicorn entry. Mirrors
05_mcp_server.py's _load_graph() shape. CLI flags --host,
--port, --log-level. Env overrides LORE_GRAPH_PATH,
LORE_HTTP_HOST, LORE_HTTP_PORT. Single-process only; multi-worker
is intentionally not exposed (graph is in-memory per-process and
write tools do not persist).
* tests/test_mcp/test_scripts_06.py: 7 subprocess tests booting
on LORE_HTTP_PORT=0 (OS-assigned) and parsing the bound port
from the 'Uvicorn running on' line. Tests 1-5: initialize,
tools/list (36), was_true_at, SSE, notification 202. Test 6:
SIGTERM exits 0 or -SIGTERM (no traceback). Test 7: missing
graph exits non-zero with the expected error message.
543 -> 550 green.
- 01_ingest.py: LORE_INGEST_LLM=1 enables LLM extraction after the
deterministic path; build_graph is now called AFTER LLM triples
merge in (the 3.4 ordering fix).
- LORE_INGEST_FAKE_LLM=1 + LORE_INGEST_FAKE_LLM_SCRIPT=path selects
FakeProvider for offline/CI runs.
- Missing OLLAMA_API_KEY degrades gracefully: stderr warning, rc=0,
deterministic graph still built (no crash, no LLM triples).
- scripts/06_llm_smoke.py: one-shot manual smoke for the real
Ollama Cloud provider; loads one NPC, runs extractor, prints
triples. Skips (rc=0, helpful message) when OLLAMA_API_KEY unset.
- FakeProvider gains dict-style {match_any, response} / {match_any,
raise} entries so tests can skip exact-prompt matching when the
body is large.
- tests/test_extraction/test_ingest_wiring.py: 8 subprocess tests
covering default-off, enabled, idempotency (x2), adds-fact,
provider-failure tolerance, bad-JSON tolerance, and missing-key
fallback.
- tests/fixtures/llm_empty_script.json: [] (used by the enabled-
path test where no triples are expected).
435/435 tests pass (was 382 pre-slice; +53). End-to-end ingest with
--skip-cognee runs cleanly on default-off path.
- scripts/04_consistency.py: standalone on-demand run of the consistency
engine over the seed codex; prints summary + per-category detail
- scripts/02_demo.py: append consistency section using the singleton
consistency_tools path so 'latest_run()' agrees with the run summary
- tests/test_consistency/test_consistency_script.py: 2 tests (end-to-end
run + --codex flag)
- tests/test_consistency/test_demo.py: 2 tests (end-to-end run + --query
flag exercises the consistency section)
- 249/249 tests pass