File size: 7,893 Bytes
1605cbb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
"""
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"],
    }