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.
Files changed (75) hide show
  1. rootmemory/__init__.py +84 -0
  2. rootmemory/api/__init__.py +25 -0
  3. rootmemory/api/agents.py +38 -0
  4. rootmemory/api/beliefs.py +66 -0
  5. rootmemory/api/claims.py +87 -0
  6. rootmemory/api/deps.py +21 -0
  7. rootmemory/api/errors.py +36 -0
  8. rootmemory/api/graph.py +32 -0
  9. rootmemory/api/invalidation.py +47 -0
  10. rootmemory/api/observations.py +46 -0
  11. rootmemory/api/promotion.py +37 -0
  12. rootmemory/api/provenance.py +88 -0
  13. rootmemory/api/security.py +53 -0
  14. rootmemory/cli.py +96 -0
  15. rootmemory/client.py +219 -0
  16. rootmemory/config.py +64 -0
  17. rootmemory/db/__init__.py +0 -0
  18. rootmemory/db/base.py +42 -0
  19. rootmemory/db/migrations/env.py +59 -0
  20. rootmemory/db/migrations/script.py.mako +26 -0
  21. rootmemory/db/migrations/versions/d336d5cf7fb9_initial_schema.py +260 -0
  22. rootmemory/db/session.py +138 -0
  23. rootmemory/errors.py +27 -0
  24. rootmemory/integrations/__init__.py +16 -0
  25. rootmemory/integrations/async_client.py +268 -0
  26. rootmemory/integrations/langchain_tools.py +237 -0
  27. rootmemory/integrations/langgraph_memory.py +179 -0
  28. rootmemory/integrations/langgraph_store.py +402 -0
  29. rootmemory/logging_config.py +30 -0
  30. rootmemory/main.py +133 -0
  31. rootmemory/models/__init__.py +22 -0
  32. rootmemory/models/agent.py +30 -0
  33. rootmemory/models/audit.py +51 -0
  34. rootmemory/models/belief.py +39 -0
  35. rootmemory/models/claim.py +52 -0
  36. rootmemory/models/decision.py +38 -0
  37. rootmemory/models/edge.py +43 -0
  38. rootmemory/models/enums.py +104 -0
  39. rootmemory/models/node_ref.py +42 -0
  40. rootmemory/models/observation.py +51 -0
  41. rootmemory/py.typed +0 -0
  42. rootmemory/repositories/__init__.py +19 -0
  43. rootmemory/repositories/agent_repo.py +44 -0
  44. rootmemory/repositories/audit_repo.py +43 -0
  45. rootmemory/repositories/belief_repo.py +63 -0
  46. rootmemory/repositories/claim_repo.py +102 -0
  47. rootmemory/repositories/decision_repo.py +76 -0
  48. rootmemory/repositories/edge_repo.py +77 -0
  49. rootmemory/repositories/observation_repo.py +76 -0
  50. rootmemory/schemas/__init__.py +52 -0
  51. rootmemory/schemas/agent.py +26 -0
  52. rootmemory/schemas/belief.py +49 -0
  53. rootmemory/schemas/claim.py +54 -0
  54. rootmemory/schemas/common.py +27 -0
  55. rootmemory/schemas/observation.py +69 -0
  56. rootmemory/schemas/promotion.py +50 -0
  57. rootmemory/schemas/provenance.py +51 -0
  58. rootmemory/services/__init__.py +35 -0
  59. rootmemory/services/belief_service.py +161 -0
  60. rootmemory/services/contradiction_service.py +148 -0
  61. rootmemory/services/decision_service.py +65 -0
  62. rootmemory/services/graph_service.py +316 -0
  63. rootmemory/services/independence_service.py +159 -0
  64. rootmemory/services/invalidation_service.py +164 -0
  65. rootmemory/services/normalization_service.py +234 -0
  66. rootmemory/services/promotion_service.py +318 -0
  67. rootmemory/services/provenance_service.py +282 -0
  68. rootmemory/services/scoring_service.py +53 -0
  69. rootmemory/static/index.html +866 -0
  70. rootmemory-0.1.0.dist-info/METADATA +266 -0
  71. rootmemory-0.1.0.dist-info/RECORD +75 -0
  72. rootmemory-0.1.0.dist-info/WHEEL +5 -0
  73. rootmemory-0.1.0.dist-info/entry_points.txt +2 -0
  74. rootmemory-0.1.0.dist-info/licenses/LICENSE +21 -0
  75. 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
+ ]