"""Read-only readiness of versioned content and the configured answer model.""" from __future__ import annotations import hashlib import json from functools import lru_cache from pathlib import Path from neo4j import Query from tools.answers.model_client import generation_status from tools.graphrag.graph_db import create_neo4j_driver, get_neo4j_settings from tools.knowledge.publication import read_state, state_matches_graph from tools.knowledge.store import SCOPE from tools.runtime.tool_health import PROBE_TIMEOUT_SECONDS, readiness from tools.sync.index_state import IndexUnavailableError, index_snapshot @lru_cache(maxsize=2) def _checkpoint(path: str, inode: int, size: int, modified_ns: int, changed_ns: int): content = Path(path).read_bytes() index = json.loads(content) return {"extraction_schema":index.get("extraction_schema"),"extraction_policy":index.get("extraction_policy"), "version": index.get("version"), "index_version": index.get("index_version"), "sha256": hashlib.sha256(content).hexdigest(), "bytes": len(content)} def content_readiness(db_file: Path, version: dict | None, *, graph_available: bool) -> dict: try: # tools_readiness released its lock: recheck the generation before reading. with index_snapshot(db_file.parent) as current: if not version or current != version: return readiness(False, "snapshot_changed") state = read_state(db_file.parent) path = db_file.parent / "knowledge/index.json" if not path.is_file(): return readiness(False, "content_index_missing") if not isinstance(state, dict) or state.get("schema") != 1: return readiness(False, "content_publication_required") st = path.stat() actual = _checkpoint(str(path), st.st_ino, st.st_size, st.st_mtime_ns, st.st_ctime_ns) if state.get("index_version") != version or actual["index_version"] != version: return readiness(False, "content_version_mismatch") if (actual["version"] != state.get("version") or actual["sha256"] != state.get("checkpoint_sha256") or actual["bytes"] != state.get("checkpoint_bytes")): return readiness(False, "content_checkpoint_invalid") from tools.knowledge.evidence import EXTRACTION_SCHEMA, EXTRACTION_POLICY if actual["extraction_schema"] != EXTRACTION_SCHEMA or actual["extraction_policy"] != EXTRACTION_POLICY: return readiness(False,"content_extraction_policy_mismatch") details = {"extraction_schema":EXTRACTION_SCHEMA,"extraction_policy":EXTRACTION_POLICY, "index_version": version, "content_version": state["version"], "checkpoint_sha256": actual["sha256"], "profiles": state.get("profiles"), "units": state.get("units")} graph = readiness(False, "content_graph_disabled") if state.get("graph_ready"): graph = _graph_readiness(state) if graph_available else readiness(False, "neo4j_unavailable") return {**readiness(True, **details), "graph": graph} except IndexUnavailableError: return readiness(False, "content_publication_incomplete") except (OSError, ValueError, TypeError, KeyError): return readiness(False, "content_checkpoint_invalid") def _graph_readiness(state): try: settings = get_neo4j_settings() with create_neo4j_driver(settings, connection_timeout=PROBE_TIMEOUT_SECONDS, connection_acquisition_timeout=PROBE_TIMEOUT_SECONDS, connection_write_timeout=PROBE_TIMEOUT_SECONDS, max_transaction_retry_time=0, max_connection_pool_size=1) as driver: with driver.session(database=settings.database) as session: row = session.run(Query( "MATCH (s:KnowledgeState {scope:$scope}) RETURN properties(s) AS state", timeout=PROBE_TIMEOUT_SECONDS), scope=SCOPE).single() actual = dict(row["state"]) if row else None if not state_matches_graph(state, actual): return readiness(False, "content_graph_version_mismatch") return readiness(True, content_version=state["version"]) except Exception: return readiness(False, "content_graph_unavailable") def answer_capabilities(db_file, tools, version): content = content_readiness(db_file, version, graph_available=tools["neo4j"]["ready"]) generation = generation_status() return {"text_search": tools["rag"], "metadata_graph": tools["graphrag"], "knowledge": content, "generation": generation}