""" FALSIFY belief-graph data models. Every node is a Cognee ``DataPoint`` subclass, so the same object serializes into the graph DB (Ladybug) and, via its ``Embeddable`` field, into the vector DB (LanceDB). The vector collection auto-created for a class is ``"{ClassName}_{field}"`` (e.g. ``Evidence_claim`` — the collection the contradiction prefilter searches). Lifecycle / truth-state note ---------------------------- The *authoritative, persistent* belief state of a node lives on the graph node as ``truth_alignment`` (a list) + ``truth_epoch`` (int), written through the graph engine (``set_node_truth_state``), not as pydantic model fields. Those props are what ``recall()`` filters on and what survives a process restart. The ``truth_state`` model field below is a convenience mirror of the node's initial state at ingest time (defaults to ``ALIVE``). Do not treat it as the source of truth after a memify run — always read back with ``get_node_truth_state``. Dedup across sessions --------------------- ``Hypothesis``, ``Evidence`` and ``Assertion`` mark identity fields with ``Dedup()``. DataPoint derives a stable UUID5 id from those fields (``DataPoint.id_for``), so re-adding the same belief in a later session updates the existing node instead of creating a duplicate — essential for a Session-2 contradiction to land on the exact node built in Session 1. """ from datetime import datetime, timezone from enum import Enum from typing import Annotated, List, Optional from pydantic import Field from cognee.infrastructure.engine import DataPoint, Dedup, Embeddable, LLMContext def _now_iso() -> str: """Return the current UTC time as an ISO-8601 string (used for node timestamps).""" return datetime.now(timezone.utc).isoformat() class TruthState(str, Enum): """Canonical belief-lifecycle states for a FALSIFY graph node. The values are the exact strings written into the node's ``truth_alignment`` list, so a state can be compared directly against what ``get_node_truth_state`` returns (e.g. ``get_node_truth_state([n])[n]["truth_alignment"] == [TruthState.REFUTED]``). States: - ``ALIVE``: default; the node participates in recall context. - ``REFUTED``: Evidence directly contradicted by a newer fact — the entry point of a forward cascade. - ``SUPERSEDED``: a node replaced/demoted by a newer competing node; retained as provenance, excluded from recall. - ``INVALIDATED``: a Conclusion whose critical supporting Evidence chain was refuted (forward-cascade victim). - ``FORGOTTEN``: an orphaned dead-end scheduled for surgical delete (transient; the node is then hard-removed from both graph and vector stores). Note: the FALSIFY spec's minimal enum names ALIVE/REFUTED/SUPERSEDED/FORGOTTEN. ``INVALIDATED`` is added here because the forward-propagation algorithm (REQUIREMENTS §1.2/§1.4) needs a distinct state for cascade-victim Conclusions versus directly-refuted Evidence. """ ALIVE = "alive" REFUTED = "refuted" SUPERSEDED = "superseded" INVALIDATED = "invalidated" FORGOTTEN = "forgotten" # --------------------------------------------------------------------------- # # Edge relationship-name constants (passed as ``relationship_name`` to add_edge) # --------------------------------------------------------------------------- # # Conclusion -> Evidence. THE forward-propagation rail. edge_properties={"critical": bool} DEPENDS_ON = "depends_on" # Evidence -> Hypothesis. Evidence corroborates a hypothesis. edge_properties={"weight": float} SUPPORTS = "supports" # Evidence -> Hypothesis. Evidence contradicts a hypothesis. edge_properties={"weight": float} # (REQUIREMENTS §1.1 names this edge "refutes"; ``REFUTES`` is provided as an alias.) CONTRADICTS = "refutes" REFUTES = CONTRADICTS # Evidence(new) -> Evidence(old). New fact overrides an old evidence node. # edge_properties={"confidence": float} SUPERSEDES = "supersedes" class InvestigationQuestion(DataPoint): """Root node of a belief graph: the research question under investigation. Example: "Did Company X know about the defect before the recall?" Everything else (hypotheses, evidence, conclusions) hangs off this question via ``question_id``. """ question: Annotated[str, Embeddable(), LLMContext()] truth_state: TruthState = TruthState.ALIVE confidence: float = 1.0 timestamp: str = Field(default_factory=_now_iso) source_id: Optional[str] = None metadata: dict = { "index_fields": ["question"], "identity_fields": ["question"], } class Hypothesis(DataPoint): """A candidate explanation competing to answer the InvestigationQuestion. Hypotheses gain/lose standing through ``supports``/``refutes`` Evidence edges. When a hypothesis' only supporting Evidence is refuted, it is demoted to ``SUPERSEDED`` and the rival with the strongest surviving support is promoted. """ statement: Annotated[str, Embeddable(), Dedup(), LLMContext()] question_id: str status: str = "alive" prior: float = 0.5 truth_state: TruthState = TruthState.ALIVE confidence: float = 0.5 timestamp: str = Field(default_factory=_now_iso) source_id: Optional[str] = None metadata: dict = { "index_fields": ["statement"], "identity_fields": ["question_id", "statement"], } class Evidence(DataPoint): """A factual claim bearing on one or more hypotheses. Evidence is the contradiction entry point: the ``Evidence_claim`` vector collection is what the two-gate detector prefilters, and a ``REFUTED`` Evidence node is the seed of every forward cascade. ``asserted_at`` is used as the tie-break when deciding which of two competing claims supersedes the other (newer wins). """ claim: Annotated[str, Embeddable(), Dedup(), LLMContext()] source_id: str quote: str = "" stance: str = "supports" # "supports" | "refutes" asserted_at: str = Field(default_factory=_now_iso) truth_state: TruthState = TruthState.ALIVE confidence: float = 0.5 timestamp: str = Field(default_factory=_now_iso) metadata: dict = { "index_fields": ["claim"], "identity_fields": ["source_id", "claim"], } class Conclusion(DataPoint): """A derived finding that rests on one or more Evidence nodes. A Conclusion ``depends_on`` the Evidence it is built from (edge carries ``critical: bool``). When a *critical* dependency is refuted and no alive critical alternative remains, the Conclusion is ``INVALIDATED`` by the forward cascade; if it then has no surviving consumer it is ``FORGOTTEN`` (hard-deleted). """ statement: Annotated[str, Embeddable(), LLMContext()] confidence: float = 0.5 depends_on_ids: List[str] = Field(default_factory=list) truth_state: TruthState = TruthState.ALIVE timestamp: str = Field(default_factory=_now_iso) source_id: Optional[str] = None metadata: dict = { "index_fields": ["statement"], "identity_fields": ["statement"], } class Assertion(DataPoint): """A raw, unclassified incoming claim — e.g. the new fact pasted in Session 2. An Assertion is the pre-belief form of an incoming statement before the detector decides whether it contradicts/supersedes existing Evidence and materializes a proper ``Evidence`` node. It carries the same lifecycle scaffolding as the other nodes so it can be reasoned over uniformly. """ text: Annotated[str, Embeddable(), Dedup(), LLMContext()] truth_state: TruthState = TruthState.ALIVE confidence: float = 0.5 timestamp: str = Field(default_factory=_now_iso) source_id: Optional[str] = None metadata: dict = { "index_fields": ["text"], "identity_fields": ["text"], }