rootmemory 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- rootmemory/__init__.py +84 -0
- rootmemory/api/__init__.py +25 -0
- rootmemory/api/agents.py +38 -0
- rootmemory/api/beliefs.py +66 -0
- rootmemory/api/claims.py +87 -0
- rootmemory/api/deps.py +21 -0
- rootmemory/api/errors.py +36 -0
- rootmemory/api/graph.py +32 -0
- rootmemory/api/invalidation.py +47 -0
- rootmemory/api/observations.py +46 -0
- rootmemory/api/promotion.py +37 -0
- rootmemory/api/provenance.py +88 -0
- rootmemory/api/security.py +53 -0
- rootmemory/cli.py +96 -0
- rootmemory/client.py +219 -0
- rootmemory/config.py +64 -0
- rootmemory/db/__init__.py +0 -0
- rootmemory/db/base.py +42 -0
- rootmemory/db/migrations/env.py +59 -0
- rootmemory/db/migrations/script.py.mako +26 -0
- rootmemory/db/migrations/versions/d336d5cf7fb9_initial_schema.py +260 -0
- rootmemory/db/session.py +138 -0
- rootmemory/errors.py +27 -0
- rootmemory/integrations/__init__.py +16 -0
- rootmemory/integrations/async_client.py +268 -0
- rootmemory/integrations/langchain_tools.py +237 -0
- rootmemory/integrations/langgraph_memory.py +179 -0
- rootmemory/integrations/langgraph_store.py +402 -0
- rootmemory/logging_config.py +30 -0
- rootmemory/main.py +133 -0
- rootmemory/models/__init__.py +22 -0
- rootmemory/models/agent.py +30 -0
- rootmemory/models/audit.py +51 -0
- rootmemory/models/belief.py +39 -0
- rootmemory/models/claim.py +52 -0
- rootmemory/models/decision.py +38 -0
- rootmemory/models/edge.py +43 -0
- rootmemory/models/enums.py +104 -0
- rootmemory/models/node_ref.py +42 -0
- rootmemory/models/observation.py +51 -0
- rootmemory/py.typed +0 -0
- rootmemory/repositories/__init__.py +19 -0
- rootmemory/repositories/agent_repo.py +44 -0
- rootmemory/repositories/audit_repo.py +43 -0
- rootmemory/repositories/belief_repo.py +63 -0
- rootmemory/repositories/claim_repo.py +102 -0
- rootmemory/repositories/decision_repo.py +76 -0
- rootmemory/repositories/edge_repo.py +77 -0
- rootmemory/repositories/observation_repo.py +76 -0
- rootmemory/schemas/__init__.py +52 -0
- rootmemory/schemas/agent.py +26 -0
- rootmemory/schemas/belief.py +49 -0
- rootmemory/schemas/claim.py +54 -0
- rootmemory/schemas/common.py +27 -0
- rootmemory/schemas/observation.py +69 -0
- rootmemory/schemas/promotion.py +50 -0
- rootmemory/schemas/provenance.py +51 -0
- rootmemory/services/__init__.py +35 -0
- rootmemory/services/belief_service.py +161 -0
- rootmemory/services/contradiction_service.py +148 -0
- rootmemory/services/decision_service.py +65 -0
- rootmemory/services/graph_service.py +316 -0
- rootmemory/services/independence_service.py +159 -0
- rootmemory/services/invalidation_service.py +164 -0
- rootmemory/services/normalization_service.py +234 -0
- rootmemory/services/promotion_service.py +318 -0
- rootmemory/services/provenance_service.py +282 -0
- rootmemory/services/scoring_service.py +53 -0
- rootmemory/static/index.html +866 -0
- rootmemory-0.1.0.dist-info/METADATA +266 -0
- rootmemory-0.1.0.dist-info/RECORD +75 -0
- rootmemory-0.1.0.dist-info/WHEEL +5 -0
- rootmemory-0.1.0.dist-info/entry_points.txt +2 -0
- rootmemory-0.1.0.dist-info/licenses/LICENSE +21 -0
- rootmemory-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""Data access for decisions and their claim dependencies."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import uuid
|
|
6
|
+
from collections.abc import Sequence
|
|
7
|
+
|
|
8
|
+
from sqlalchemy import select
|
|
9
|
+
from sqlalchemy.orm import Session
|
|
10
|
+
|
|
11
|
+
from rootmemory.models.decision import Decision, DecisionDependency
|
|
12
|
+
from rootmemory.models.enums import DecisionStatus
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class DecisionRepository:
|
|
16
|
+
def __init__(self, session: Session) -> None:
|
|
17
|
+
self.session = session
|
|
18
|
+
|
|
19
|
+
def create(
|
|
20
|
+
self,
|
|
21
|
+
agent_id: uuid.UUID,
|
|
22
|
+
decision_type: str,
|
|
23
|
+
content: str,
|
|
24
|
+
meta: dict | None = None,
|
|
25
|
+
) -> Decision:
|
|
26
|
+
decision = Decision(
|
|
27
|
+
agent_id=agent_id,
|
|
28
|
+
decision_type=decision_type,
|
|
29
|
+
content=content,
|
|
30
|
+
meta=meta or {},
|
|
31
|
+
)
|
|
32
|
+
self.session.add(decision)
|
|
33
|
+
self.session.flush()
|
|
34
|
+
return decision
|
|
35
|
+
|
|
36
|
+
def get(self, decision_id: uuid.UUID) -> Decision | None:
|
|
37
|
+
return self.session.get(Decision, decision_id)
|
|
38
|
+
|
|
39
|
+
def list(self) -> Sequence[Decision]:
|
|
40
|
+
return self.session.execute(select(Decision).order_by(Decision.created_at)).scalars().all()
|
|
41
|
+
|
|
42
|
+
def add_dependency(self, decision_id: uuid.UUID, claim_id: uuid.UUID) -> DecisionDependency:
|
|
43
|
+
existing = self.session.execute(
|
|
44
|
+
select(DecisionDependency).where(
|
|
45
|
+
DecisionDependency.decision_id == decision_id,
|
|
46
|
+
DecisionDependency.claim_id == claim_id,
|
|
47
|
+
)
|
|
48
|
+
).scalar_one_or_none()
|
|
49
|
+
if existing is not None:
|
|
50
|
+
return existing
|
|
51
|
+
dependency = DecisionDependency(decision_id=decision_id, claim_id=claim_id)
|
|
52
|
+
self.session.add(dependency)
|
|
53
|
+
self.session.flush()
|
|
54
|
+
return dependency
|
|
55
|
+
|
|
56
|
+
def decisions_for_claims(self, claim_ids: Sequence[uuid.UUID]) -> Sequence[Decision]:
|
|
57
|
+
if not claim_ids:
|
|
58
|
+
return []
|
|
59
|
+
stmt = (
|
|
60
|
+
select(Decision)
|
|
61
|
+
.join(DecisionDependency, DecisionDependency.decision_id == Decision.id)
|
|
62
|
+
.where(DecisionDependency.claim_id.in_(list(claim_ids)))
|
|
63
|
+
.distinct()
|
|
64
|
+
)
|
|
65
|
+
return self.session.execute(stmt).scalars().all()
|
|
66
|
+
|
|
67
|
+
def claims_for_decision(self, decision_id: uuid.UUID) -> Sequence[uuid.UUID]:
|
|
68
|
+
stmt = select(DecisionDependency.claim_id).where(
|
|
69
|
+
DecisionDependency.decision_id == decision_id
|
|
70
|
+
)
|
|
71
|
+
return list(self.session.execute(stmt).scalars().all())
|
|
72
|
+
|
|
73
|
+
def set_status(self, decision: Decision, status: DecisionStatus) -> Decision:
|
|
74
|
+
decision.status = str(status)
|
|
75
|
+
self.session.flush()
|
|
76
|
+
return decision
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""Data access for provenance edges."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import uuid
|
|
6
|
+
from collections.abc import Iterable, Sequence
|
|
7
|
+
|
|
8
|
+
from sqlalchemy import select
|
|
9
|
+
from sqlalchemy.orm import Session
|
|
10
|
+
|
|
11
|
+
from rootmemory.models.edge import ProvenanceEdge
|
|
12
|
+
from rootmemory.models.enums import EdgeType
|
|
13
|
+
from rootmemory.models.node_ref import NodeRef
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class EdgeRepository:
|
|
17
|
+
"""Append-only access to the provenance edge table."""
|
|
18
|
+
|
|
19
|
+
def __init__(self, session: Session) -> None:
|
|
20
|
+
self.session = session
|
|
21
|
+
|
|
22
|
+
def create(
|
|
23
|
+
self,
|
|
24
|
+
source: NodeRef,
|
|
25
|
+
target: NodeRef,
|
|
26
|
+
edge_type: EdgeType,
|
|
27
|
+
weight: float = 1.0,
|
|
28
|
+
meta: dict | None = None,
|
|
29
|
+
) -> ProvenanceEdge:
|
|
30
|
+
"""Create an edge pointing from ``source`` to ``target``."""
|
|
31
|
+
edge = ProvenanceEdge(
|
|
32
|
+
from_node_id=source.id,
|
|
33
|
+
from_node_type=str(source.type),
|
|
34
|
+
to_node_id=target.id,
|
|
35
|
+
to_node_type=str(target.type),
|
|
36
|
+
edge_type=str(edge_type),
|
|
37
|
+
weight=weight,
|
|
38
|
+
meta=meta or {},
|
|
39
|
+
)
|
|
40
|
+
self.session.add(edge)
|
|
41
|
+
self.session.flush()
|
|
42
|
+
return edge
|
|
43
|
+
|
|
44
|
+
def exists(self, source: NodeRef, target: NodeRef, edge_type: EdgeType) -> bool:
|
|
45
|
+
stmt = select(ProvenanceEdge.id).where(
|
|
46
|
+
ProvenanceEdge.from_node_id == source.id,
|
|
47
|
+
ProvenanceEdge.to_node_id == target.id,
|
|
48
|
+
ProvenanceEdge.edge_type == str(edge_type),
|
|
49
|
+
)
|
|
50
|
+
return self.session.execute(stmt).first() is not None
|
|
51
|
+
|
|
52
|
+
def outgoing(
|
|
53
|
+
self, node_ids: Iterable[uuid.UUID], edge_types: Iterable[str] | None = None
|
|
54
|
+
) -> Sequence[ProvenanceEdge]:
|
|
55
|
+
"""Edges whose ``from_node_id`` is one of ``node_ids`` (node -> parents)."""
|
|
56
|
+
ids = list(node_ids)
|
|
57
|
+
if not ids:
|
|
58
|
+
return []
|
|
59
|
+
stmt = select(ProvenanceEdge).where(ProvenanceEdge.from_node_id.in_(ids))
|
|
60
|
+
if edge_types is not None:
|
|
61
|
+
stmt = stmt.where(ProvenanceEdge.edge_type.in_([str(e) for e in edge_types]))
|
|
62
|
+
return self.session.execute(stmt).scalars().all()
|
|
63
|
+
|
|
64
|
+
def incoming(
|
|
65
|
+
self, node_ids: Iterable[uuid.UUID], edge_types: Iterable[str] | None = None
|
|
66
|
+
) -> Sequence[ProvenanceEdge]:
|
|
67
|
+
"""Edges whose ``to_node_id`` is one of ``node_ids`` (node -> children)."""
|
|
68
|
+
ids = list(node_ids)
|
|
69
|
+
if not ids:
|
|
70
|
+
return []
|
|
71
|
+
stmt = select(ProvenanceEdge).where(ProvenanceEdge.to_node_id.in_(ids))
|
|
72
|
+
if edge_types is not None:
|
|
73
|
+
stmt = stmt.where(ProvenanceEdge.edge_type.in_([str(e) for e in edge_types]))
|
|
74
|
+
return self.session.execute(stmt).scalars().all()
|
|
75
|
+
|
|
76
|
+
def all_edges(self) -> Sequence[ProvenanceEdge]:
|
|
77
|
+
return self.session.execute(select(ProvenanceEdge)).scalars().all()
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""Data access for observations."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import hashlib
|
|
6
|
+
import uuid
|
|
7
|
+
from collections.abc import Iterable, Sequence
|
|
8
|
+
|
|
9
|
+
from sqlalchemy import select
|
|
10
|
+
from sqlalchemy.orm import Session
|
|
11
|
+
|
|
12
|
+
from rootmemory.models.enums import ValidityStatus
|
|
13
|
+
from rootmemory.models.observation import Observation
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def content_hash(content: str) -> str:
|
|
17
|
+
"""Stable hash of observation content, used to spot literal duplicates."""
|
|
18
|
+
return hashlib.sha256(content.encode("utf-8")).hexdigest()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ObservationRepository:
|
|
22
|
+
"""Observations are append-only: there is deliberately no delete method."""
|
|
23
|
+
|
|
24
|
+
def __init__(self, session: Session) -> None:
|
|
25
|
+
self.session = session
|
|
26
|
+
|
|
27
|
+
def create(
|
|
28
|
+
self,
|
|
29
|
+
source_type: str,
|
|
30
|
+
content: str,
|
|
31
|
+
source_uri: str | None = None,
|
|
32
|
+
source_actor: str | None = None,
|
|
33
|
+
created_by_agent_id: uuid.UUID | None = None,
|
|
34
|
+
reliability_score: float = 1.0,
|
|
35
|
+
source_family_id: str | None = None,
|
|
36
|
+
validity_status: ValidityStatus = ValidityStatus.VALID,
|
|
37
|
+
meta: dict | None = None,
|
|
38
|
+
) -> Observation:
|
|
39
|
+
observation = Observation(
|
|
40
|
+
source_type=source_type,
|
|
41
|
+
source_uri=source_uri,
|
|
42
|
+
source_actor=source_actor,
|
|
43
|
+
content=content,
|
|
44
|
+
content_hash=content_hash(content),
|
|
45
|
+
created_by_agent_id=created_by_agent_id,
|
|
46
|
+
reliability_score=reliability_score,
|
|
47
|
+
source_family_id=source_family_id,
|
|
48
|
+
validity_status=str(validity_status),
|
|
49
|
+
meta=meta or {},
|
|
50
|
+
)
|
|
51
|
+
self.session.add(observation)
|
|
52
|
+
self.session.flush()
|
|
53
|
+
return observation
|
|
54
|
+
|
|
55
|
+
def get(self, observation_id: uuid.UUID) -> Observation | None:
|
|
56
|
+
return self.session.get(Observation, observation_id)
|
|
57
|
+
|
|
58
|
+
def get_many(self, observation_ids: Iterable[uuid.UUID]) -> Sequence[Observation]:
|
|
59
|
+
ids = list(observation_ids)
|
|
60
|
+
if not ids:
|
|
61
|
+
return []
|
|
62
|
+
stmt = select(Observation).where(Observation.id.in_(ids))
|
|
63
|
+
return self.session.execute(stmt).scalars().all()
|
|
64
|
+
|
|
65
|
+
def list(self) -> Sequence[Observation]:
|
|
66
|
+
stmt = select(Observation).order_by(Observation.created_at)
|
|
67
|
+
return self.session.execute(stmt).scalars().all()
|
|
68
|
+
|
|
69
|
+
def set_validity(
|
|
70
|
+
self, observation: Observation, status: ValidityStatus, reason: str | None = None
|
|
71
|
+
) -> Observation:
|
|
72
|
+
"""Change validity only. The content of an observation is never edited."""
|
|
73
|
+
observation.validity_status = str(status)
|
|
74
|
+
observation.validity_reason = reason
|
|
75
|
+
self.session.flush()
|
|
76
|
+
return observation
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""Pydantic request/response models. No business logic lives here."""
|
|
2
|
+
|
|
3
|
+
from rootmemory.schemas.agent import AgentCreate, AgentRead
|
|
4
|
+
from rootmemory.schemas.belief import BeliefCreate, BeliefRead, BeliefWithProvenance
|
|
5
|
+
from rootmemory.schemas.claim import (
|
|
6
|
+
ClaimContextResponse,
|
|
7
|
+
ClaimCreate,
|
|
8
|
+
ClaimLinkRequest,
|
|
9
|
+
ClaimRead,
|
|
10
|
+
EvidenceSide,
|
|
11
|
+
)
|
|
12
|
+
from rootmemory.schemas.common import ErrorResponse, NodeReference
|
|
13
|
+
from rootmemory.schemas.observation import (
|
|
14
|
+
InvalidationRequest,
|
|
15
|
+
InvalidationResponse,
|
|
16
|
+
ObservationCreate,
|
|
17
|
+
ObservationRead,
|
|
18
|
+
RepairReportRead,
|
|
19
|
+
)
|
|
20
|
+
from rootmemory.schemas.promotion import PromotionAuditRead, PromotionEvaluationResponse
|
|
21
|
+
from rootmemory.schemas.provenance import (
|
|
22
|
+
DecisionCreate,
|
|
23
|
+
DecisionRead,
|
|
24
|
+
EvidenceRootRead,
|
|
25
|
+
ProvenanceResponse,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
__all__ = [
|
|
29
|
+
"AgentCreate",
|
|
30
|
+
"AgentRead",
|
|
31
|
+
"BeliefCreate",
|
|
32
|
+
"BeliefRead",
|
|
33
|
+
"BeliefWithProvenance",
|
|
34
|
+
"ClaimContextResponse",
|
|
35
|
+
"ClaimCreate",
|
|
36
|
+
"ClaimLinkRequest",
|
|
37
|
+
"ClaimRead",
|
|
38
|
+
"DecisionCreate",
|
|
39
|
+
"DecisionRead",
|
|
40
|
+
"ErrorResponse",
|
|
41
|
+
"EvidenceRootRead",
|
|
42
|
+
"EvidenceSide",
|
|
43
|
+
"InvalidationRequest",
|
|
44
|
+
"InvalidationResponse",
|
|
45
|
+
"NodeReference",
|
|
46
|
+
"ObservationCreate",
|
|
47
|
+
"ObservationRead",
|
|
48
|
+
"PromotionAuditRead",
|
|
49
|
+
"PromotionEvaluationResponse",
|
|
50
|
+
"ProvenanceResponse",
|
|
51
|
+
"RepairReportRead",
|
|
52
|
+
]
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""Agent request/response schemas."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import uuid
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
|
|
8
|
+
from pydantic import BaseModel, Field
|
|
9
|
+
|
|
10
|
+
from rootmemory.schemas.common import ORMModel
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class AgentCreate(BaseModel):
|
|
14
|
+
name: str = Field(min_length=1, max_length=200)
|
|
15
|
+
role: str = ""
|
|
16
|
+
description: str = ""
|
|
17
|
+
trust_score: float = Field(default=0.5, ge=0.0, le=1.0)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class AgentRead(ORMModel):
|
|
21
|
+
id: uuid.UUID
|
|
22
|
+
name: str
|
|
23
|
+
role: str
|
|
24
|
+
description: str
|
|
25
|
+
trust_score: float
|
|
26
|
+
created_at: datetime
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""Belief request/response schemas."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import uuid
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
|
|
8
|
+
from pydantic import BaseModel, Field
|
|
9
|
+
|
|
10
|
+
from rootmemory.models.enums import EpistemicStatus, Visibility
|
|
11
|
+
from rootmemory.schemas.common import NodeReference, ORMModel
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class BeliefCreate(BaseModel):
|
|
15
|
+
agent_id: uuid.UUID
|
|
16
|
+
claim_text: str = Field(min_length=1)
|
|
17
|
+
normalized_claim_key: str | None = Field(
|
|
18
|
+
default=None,
|
|
19
|
+
max_length=300,
|
|
20
|
+
description=(
|
|
21
|
+
"Which proposition this belief is about. Omit it to let the "
|
|
22
|
+
"configured normalizer decide (see CLAIM_NORMALIZATION)."
|
|
23
|
+
),
|
|
24
|
+
)
|
|
25
|
+
confidence: float = Field(ge=0.0, le=1.0)
|
|
26
|
+
parent_nodes: list[NodeReference] = Field(
|
|
27
|
+
min_length=1,
|
|
28
|
+
description="At least one observation or belief this was derived from.",
|
|
29
|
+
)
|
|
30
|
+
llm_generated: bool = False
|
|
31
|
+
metadata: dict = Field(default_factory=dict)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class BeliefRead(ORMModel):
|
|
35
|
+
id: uuid.UUID
|
|
36
|
+
agent_id: uuid.UUID
|
|
37
|
+
claim_text: str
|
|
38
|
+
normalized_claim_key: str
|
|
39
|
+
confidence: float
|
|
40
|
+
epistemic_status: EpistemicStatus
|
|
41
|
+
visibility: Visibility
|
|
42
|
+
llm_generated: bool
|
|
43
|
+
created_at: datetime
|
|
44
|
+
updated_at: datetime
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class BeliefWithProvenance(BeliefRead):
|
|
48
|
+
evidence_roots: list[uuid.UUID]
|
|
49
|
+
independent_source_count: int
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""Claim request/response schemas."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import uuid
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
|
|
8
|
+
from pydantic import BaseModel, Field
|
|
9
|
+
|
|
10
|
+
from rootmemory.models.enums import ClaimStatus
|
|
11
|
+
from rootmemory.schemas.common import ORMModel
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ClaimCreate(BaseModel):
|
|
15
|
+
claim_key: str = Field(min_length=1, max_length=300)
|
|
16
|
+
canonical_text: str = Field(min_length=1)
|
|
17
|
+
metadata: dict = Field(default_factory=dict)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ClaimRead(ORMModel):
|
|
21
|
+
id: uuid.UUID
|
|
22
|
+
claim_key: str
|
|
23
|
+
canonical_text: str
|
|
24
|
+
status: ClaimStatus
|
|
25
|
+
confidence: float
|
|
26
|
+
independent_support_count: int
|
|
27
|
+
independent_contradiction_count: int
|
|
28
|
+
promoted_at: datetime | None
|
|
29
|
+
created_at: datetime
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class ClaimLinkRequest(BaseModel):
|
|
33
|
+
belief_id: uuid.UUID
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class EvidenceSide(BaseModel):
|
|
37
|
+
"""One side of a claim, with both counts kept separate on purpose."""
|
|
38
|
+
|
|
39
|
+
belief_count: int
|
|
40
|
+
agreeing_agents: int
|
|
41
|
+
independent_sources: int
|
|
42
|
+
confidence: float
|
|
43
|
+
root_observation_ids: list[uuid.UUID] = Field(default_factory=list)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class ClaimContextResponse(BaseModel):
|
|
47
|
+
"""What an agent receives instead of a bare true/false (spec section 49)."""
|
|
48
|
+
|
|
49
|
+
claim_id: uuid.UUID
|
|
50
|
+
claim_key: str
|
|
51
|
+
claim: str
|
|
52
|
+
status: ClaimStatus
|
|
53
|
+
support: EvidenceSide
|
|
54
|
+
contradictions: EvidenceSide
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Shared response pieces."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import uuid
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
8
|
+
|
|
9
|
+
from rootmemory.models.enums import NodeType
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ORMModel(BaseModel):
|
|
13
|
+
"""Base for schemas read straight off an ORM row."""
|
|
14
|
+
|
|
15
|
+
model_config = ConfigDict(from_attributes=True)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class NodeReference(BaseModel):
|
|
19
|
+
"""A typed pointer to a node in the provenance graph."""
|
|
20
|
+
|
|
21
|
+
id: uuid.UUID
|
|
22
|
+
type: NodeType = Field(description="observation | belief | claim | decision")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class ErrorResponse(BaseModel):
|
|
26
|
+
error: str
|
|
27
|
+
detail: str
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""Observation request/response schemas."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import uuid
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
|
|
8
|
+
from pydantic import BaseModel, Field
|
|
9
|
+
|
|
10
|
+
from rootmemory.models.enums import ValidityStatus
|
|
11
|
+
from rootmemory.schemas.common import ORMModel
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ObservationCreate(BaseModel):
|
|
15
|
+
source_type: str = Field(min_length=1, max_length=100)
|
|
16
|
+
content: str = Field(min_length=1)
|
|
17
|
+
source_uri: str | None = None
|
|
18
|
+
source_actor: str | None = None
|
|
19
|
+
created_by_agent_id: uuid.UUID | None = None
|
|
20
|
+
reliability_score: float = Field(default=1.0, ge=0.0, le=1.0)
|
|
21
|
+
source_family_id: str | None = Field(
|
|
22
|
+
default=None,
|
|
23
|
+
description=(
|
|
24
|
+
"Observations copied from a common origin should share this id; they then "
|
|
25
|
+
"count as one independent source rather than several."
|
|
26
|
+
),
|
|
27
|
+
)
|
|
28
|
+
metadata: dict = Field(default_factory=dict)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class ObservationRead(ORMModel):
|
|
32
|
+
id: uuid.UUID
|
|
33
|
+
source_type: str
|
|
34
|
+
source_uri: str | None
|
|
35
|
+
source_actor: str | None
|
|
36
|
+
content: str
|
|
37
|
+
content_hash: str
|
|
38
|
+
timestamp: datetime
|
|
39
|
+
created_by_agent_id: uuid.UUID | None
|
|
40
|
+
validity_status: ValidityStatus
|
|
41
|
+
validity_reason: str | None
|
|
42
|
+
reliability_score: float
|
|
43
|
+
source_family_id: str | None
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class InvalidationRequest(BaseModel):
|
|
47
|
+
reason: str = Field(default="", max_length=2000)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class InvalidationResponse(BaseModel):
|
|
51
|
+
repair_report_id: uuid.UUID
|
|
52
|
+
trigger_node_id: uuid.UUID
|
|
53
|
+
trigger_reason: str
|
|
54
|
+
affected_beliefs: list[uuid.UUID]
|
|
55
|
+
affected_claims: list[uuid.UUID]
|
|
56
|
+
affected_decisions: list[uuid.UUID]
|
|
57
|
+
details: dict
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class RepairReportRead(ORMModel):
|
|
61
|
+
id: uuid.UUID
|
|
62
|
+
trigger_node_id: uuid.UUID
|
|
63
|
+
trigger_node_type: str
|
|
64
|
+
trigger_reason: str
|
|
65
|
+
affected_beliefs: list[str]
|
|
66
|
+
affected_claims: list[str]
|
|
67
|
+
affected_decisions: list[str]
|
|
68
|
+
details: dict
|
|
69
|
+
created_at: datetime
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""Promotion request/response schemas."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import uuid
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
from typing import Literal
|
|
8
|
+
|
|
9
|
+
from pydantic import BaseModel, Field
|
|
10
|
+
|
|
11
|
+
from rootmemory.schemas.common import ORMModel
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class PromotionEvaluationResponse(BaseModel):
|
|
15
|
+
"""The gate's verdict.
|
|
16
|
+
|
|
17
|
+
``agreeing_agents`` and ``independent_support_count`` are both present and
|
|
18
|
+
are never conflated - that difference is the point of the system
|
|
19
|
+
(spec section 64).
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
claim_id: uuid.UUID
|
|
23
|
+
claim_key: str
|
|
24
|
+
supporting_belief_count: int
|
|
25
|
+
agreeing_agents: int
|
|
26
|
+
independent_support_count: int
|
|
27
|
+
contradiction_count: int
|
|
28
|
+
independent_contradiction_count: int
|
|
29
|
+
aggregate_confidence: float
|
|
30
|
+
naive_average_confidence: float
|
|
31
|
+
decision: Literal["promote", "reject"]
|
|
32
|
+
reasons: list[str]
|
|
33
|
+
explanation: str
|
|
34
|
+
policy_version: str
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class PromotionAuditRead(ORMModel):
|
|
38
|
+
id: uuid.UUID
|
|
39
|
+
claim_id: uuid.UUID
|
|
40
|
+
evaluated_at: datetime
|
|
41
|
+
supporting_belief_count: int
|
|
42
|
+
agreeing_agent_count: int
|
|
43
|
+
independent_support_count: int
|
|
44
|
+
contradiction_count: int
|
|
45
|
+
independent_contradiction_count: int
|
|
46
|
+
aggregate_confidence: float
|
|
47
|
+
result: str
|
|
48
|
+
reasons: list[str]
|
|
49
|
+
policy_version: str
|
|
50
|
+
policy_snapshot: dict = Field(default_factory=dict)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""Provenance and decision schemas."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import uuid
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
|
|
8
|
+
from pydantic import BaseModel, Field
|
|
9
|
+
|
|
10
|
+
from rootmemory.models.enums import DecisionStatus, NodeType
|
|
11
|
+
from rootmemory.schemas.common import ORMModel
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class EvidenceRootRead(BaseModel):
|
|
15
|
+
id: uuid.UUID
|
|
16
|
+
source_type: str
|
|
17
|
+
source_uri: str | None
|
|
18
|
+
validity_status: str
|
|
19
|
+
reliability_score: float
|
|
20
|
+
source_family_id: str | None
|
|
21
|
+
independence_key: str
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ProvenanceResponse(BaseModel):
|
|
25
|
+
node_id: uuid.UUID
|
|
26
|
+
node_type: NodeType
|
|
27
|
+
evidence_roots: list[uuid.UUID]
|
|
28
|
+
excluded_roots: dict[str, str] = Field(
|
|
29
|
+
default_factory=dict,
|
|
30
|
+
description="Ancestor observations that were reached but are not valid evidence.",
|
|
31
|
+
)
|
|
32
|
+
independent_source_count: int
|
|
33
|
+
root_details: list[EvidenceRootRead] = Field(default_factory=list)
|
|
34
|
+
paths: list[list[str]] = Field(default_factory=list)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class DecisionCreate(BaseModel):
|
|
38
|
+
agent_id: uuid.UUID
|
|
39
|
+
decision_type: str = Field(min_length=1, max_length=100)
|
|
40
|
+
content: str = Field(min_length=1)
|
|
41
|
+
depends_on_claim_ids: list[uuid.UUID] = Field(default_factory=list)
|
|
42
|
+
metadata: dict = Field(default_factory=dict)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class DecisionRead(ORMModel):
|
|
46
|
+
id: uuid.UUID
|
|
47
|
+
agent_id: uuid.UUID
|
|
48
|
+
decision_type: str
|
|
49
|
+
content: str
|
|
50
|
+
status: DecisionStatus
|
|
51
|
+
created_at: datetime
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""Service layer: all epistemic rules live here, none of them in the API layer."""
|
|
2
|
+
|
|
3
|
+
from rootmemory.services.belief_service import BeliefService, ParentSpec
|
|
4
|
+
from rootmemory.services.contradiction_service import ClaimContext, ContradictionService
|
|
5
|
+
from rootmemory.services.decision_service import DecisionService
|
|
6
|
+
from rootmemory.services.independence_service import IndependenceReport, IndependenceService
|
|
7
|
+
from rootmemory.services.invalidation_service import InvalidationService, RepairSummary
|
|
8
|
+
from rootmemory.services.promotion_service import (
|
|
9
|
+
PromotionEvaluation,
|
|
10
|
+
PromotionPolicy,
|
|
11
|
+
PromotionService,
|
|
12
|
+
)
|
|
13
|
+
from rootmemory.services.provenance_service import (
|
|
14
|
+
CascadeResult,
|
|
15
|
+
EvidenceRoots,
|
|
16
|
+
ProvenanceService,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
"BeliefService",
|
|
21
|
+
"CascadeResult",
|
|
22
|
+
"ClaimContext",
|
|
23
|
+
"ContradictionService",
|
|
24
|
+
"DecisionService",
|
|
25
|
+
"EvidenceRoots",
|
|
26
|
+
"IndependenceReport",
|
|
27
|
+
"IndependenceService",
|
|
28
|
+
"InvalidationService",
|
|
29
|
+
"ParentSpec",
|
|
30
|
+
"PromotionEvaluation",
|
|
31
|
+
"PromotionPolicy",
|
|
32
|
+
"PromotionService",
|
|
33
|
+
"ProvenanceService",
|
|
34
|
+
"RepairSummary",
|
|
35
|
+
]
|