continuum-toolkit 1.0.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.
- cli/__init__.py +12 -0
- cli/main.py +415 -0
- confidence/__init__.py +13 -0
- confidence/calculator.py +308 -0
- confidence/models.py +40 -0
- context/__init__.py +17 -0
- context/models.py +108 -0
- context/pruner.py +228 -0
- context/selector.py +193 -0
- continuum_toolkit-1.0.0.dist-info/METADATA +511 -0
- continuum_toolkit-1.0.0.dist-info/RECORD +72 -0
- continuum_toolkit-1.0.0.dist-info/WHEEL +5 -0
- continuum_toolkit-1.0.0.dist-info/entry_points.txt +2 -0
- continuum_toolkit-1.0.0.dist-info/licenses/LICENSE +21 -0
- continuum_toolkit-1.0.0.dist-info/top_level.txt +13 -0
- contradictions/__init__.py +19 -0
- contradictions/detector.py +442 -0
- contradictions/models.py +75 -0
- core/__init__.py +97 -0
- core/enums.py +130 -0
- core/evidence.py +117 -0
- core/interfaces.py +209 -0
- core/schema.py +391 -0
- core/serializer.py +84 -0
- core/state_models.py +530 -0
- daemon/__init__.py +12 -0
- daemon/service.py +170 -0
- extractors/__init__.py +51 -0
- extractors/base.py +117 -0
- extractors/config/parsers.py +288 -0
- extractors/config/secret_sanitizer.py +91 -0
- extractors/config_extractor.py +172 -0
- extractors/conversation/analyzers.py +193 -0
- extractors/conversation/models.py +148 -0
- extractors/conversation_extractor.py +166 -0
- extractors/git_extractor.py +305 -0
- extractors/parsers/base.py +91 -0
- extractors/parsers/comment_parser.py +51 -0
- extractors/parsers/js_ts_parser.py +171 -0
- extractors/parsers/python_parser.py +180 -0
- extractors/verification/runners.py +278 -0
- extractors/verification_extractor.py +263 -0
- extractors/workspace_extractor.py +221 -0
- graph/__init__.py +24 -0
- graph/diff.py +109 -0
- graph/manager.py +473 -0
- graph/models.py +62 -0
- graph/propagator.py +194 -0
- graph/query.py +86 -0
- graph/snapshot.py +85 -0
- handoff/__init__.py +28 -0
- handoff/adapters/__init__.py +45 -0
- handoff/adapters/base.py +90 -0
- handoff/adapters/claude_adapter.py +176 -0
- handoff/adapters/codex_gpt_adapter.py +151 -0
- handoff/adapters/gemini_adapter.py +151 -0
- handoff/adapters/local_model_adapter.py +130 -0
- handoff/models.py +88 -0
- handoff/packager.py +288 -0
- pipeline/__init__.py +9 -0
- pipeline/orchestrator.py +260 -0
- resolution/__init__.py +15 -0
- resolution/resolver.py +311 -0
- storage/__init__.py +18 -0
- storage/hooks.py +125 -0
- storage/manager.py +127 -0
- storage/models.py +39 -0
- storage/recovery.py +98 -0
- watcher/__init__.py +17 -0
- watcher/detector.py +136 -0
- watcher/models.py +62 -0
- watcher/updater.py +163 -0
core/schema.py
ADDED
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Project Continuum - Schema Generator and Validator
|
|
3
|
+
==================================================
|
|
4
|
+
Provides JSON Schema definitions, versioning metadata, and validation
|
|
5
|
+
utilities for CanonicalProjectState and all its subcomponents.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
from typing import Any, Dict, List, Tuple
|
|
10
|
+
from core.enums import Status, EvidenceType, EvidenceLevel, NodeType, RelationType, TargetModel
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
CANONICAL_STATE_SCHEMA_VERSION = "1.0.0"
|
|
14
|
+
|
|
15
|
+
def get_canonical_project_state_schema() -> Dict[str, Any]:
|
|
16
|
+
"""
|
|
17
|
+
Returns the full JSON Schema (Draft 2020-12 / Draft-07 compatible)
|
|
18
|
+
for Project Continuum's Canonical Project State.
|
|
19
|
+
"""
|
|
20
|
+
return {
|
|
21
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
22
|
+
"$id": f"https://project-continuum.dev/schemas/v{CANONICAL_STATE_SCHEMA_VERSION}/canonical-project-state.json",
|
|
23
|
+
"title": "CanonicalProjectState",
|
|
24
|
+
"description": "Root container for Project Continuum's verified state, segregating physical ground truth, conversational claims, and agent execution intent.",
|
|
25
|
+
"type": "object",
|
|
26
|
+
"required": [
|
|
27
|
+
"schema_version",
|
|
28
|
+
"project_id",
|
|
29
|
+
"created_at",
|
|
30
|
+
"updated_at",
|
|
31
|
+
"project_state",
|
|
32
|
+
"conversational_state",
|
|
33
|
+
"agent_execution_state",
|
|
34
|
+
"evidence_pool",
|
|
35
|
+
"graph_nodes",
|
|
36
|
+
"graph_edges",
|
|
37
|
+
"contradictions"
|
|
38
|
+
],
|
|
39
|
+
"properties": {
|
|
40
|
+
"schema_version": {
|
|
41
|
+
"type": "string",
|
|
42
|
+
"pattern": r"^\d+\.\d+\.\d+$",
|
|
43
|
+
"default": CANONICAL_STATE_SCHEMA_VERSION
|
|
44
|
+
},
|
|
45
|
+
"project_id": {"type": "string"},
|
|
46
|
+
"created_at": {"type": "string", "format": "date-time"},
|
|
47
|
+
"updated_at": {"type": "string", "format": "date-time"},
|
|
48
|
+
|
|
49
|
+
# 1. Project State
|
|
50
|
+
"project_state": {
|
|
51
|
+
"type": "object",
|
|
52
|
+
"required": ["root_path", "detected_languages", "files", "manifests", "symbols", "git_state", "test_results", "build_status", "todo_markers", "evidence_ids", "last_scanned_at"],
|
|
53
|
+
"properties": {
|
|
54
|
+
"root_path": {"type": "string"},
|
|
55
|
+
"detected_languages": {"type": "array", "items": {"type": "string"}},
|
|
56
|
+
"files": {"type": "array", "items": {"type": "string"}},
|
|
57
|
+
"manifests": {
|
|
58
|
+
"type": "array",
|
|
59
|
+
"items": {
|
|
60
|
+
"type": "object",
|
|
61
|
+
"required": ["manifest_type", "file_path", "dependencies", "dev_dependencies", "scripts"],
|
|
62
|
+
"properties": {
|
|
63
|
+
"manifest_type": {"type": "string"},
|
|
64
|
+
"file_path": {"type": "string"},
|
|
65
|
+
"project_name": {"type": ["string", "null"]},
|
|
66
|
+
"version": {"type": ["string", "null"]},
|
|
67
|
+
"dependencies": {"type": "object"},
|
|
68
|
+
"dev_dependencies": {"type": "object"},
|
|
69
|
+
"scripts": {"type": "object"},
|
|
70
|
+
"evidence_id": {"type": ["string", "null"]}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
"symbols": {
|
|
75
|
+
"type": "array",
|
|
76
|
+
"items": {
|
|
77
|
+
"type": "object",
|
|
78
|
+
"required": ["name", "kind", "file_path", "line_start", "line_end", "exported"],
|
|
79
|
+
"properties": {
|
|
80
|
+
"name": {"type": "string"},
|
|
81
|
+
"kind": {"type": "string"},
|
|
82
|
+
"file_path": {"type": "string"},
|
|
83
|
+
"line_start": {"type": "integer", "minimum": 1},
|
|
84
|
+
"line_end": {"type": "integer", "minimum": 1},
|
|
85
|
+
"exported": {"type": "boolean"},
|
|
86
|
+
"docstring": {"type": ["string", "null"]},
|
|
87
|
+
"parameters": {"type": "array", "items": {"type": "string"}},
|
|
88
|
+
"return_type": {"type": ["string", "null"]},
|
|
89
|
+
"evidence_id": {"type": ["string", "null"]}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
"git_state": {
|
|
94
|
+
"type": "object",
|
|
95
|
+
"required": ["is_repo", "is_dirty", "staged_files", "unstaged_files", "untracked_files", "recent_commits", "evidence_ids"],
|
|
96
|
+
"properties": {
|
|
97
|
+
"is_repo": {"type": "boolean"},
|
|
98
|
+
"branch": {"type": ["string", "null"]},
|
|
99
|
+
"head_commit": {"type": ["string", "null"]},
|
|
100
|
+
"is_dirty": {"type": "boolean"},
|
|
101
|
+
"staged_files": {"type": "array", "items": {"type": "string"}},
|
|
102
|
+
"unstaged_files": {"type": "array", "items": {"type": "string"}},
|
|
103
|
+
"untracked_files": {"type": "array", "items": {"type": "string"}},
|
|
104
|
+
"recent_commits": {"type": "array", "items": {"type": "object"}},
|
|
105
|
+
"evidence_ids": {"type": "array", "items": {"type": "string"}}
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
"test_results": {
|
|
109
|
+
"type": "array",
|
|
110
|
+
"items": {
|
|
111
|
+
"type": "object",
|
|
112
|
+
"required": ["test_id", "name", "suite", "status", "exit_code", "duration_ms"],
|
|
113
|
+
"properties": {
|
|
114
|
+
"test_id": {"type": "string"},
|
|
115
|
+
"name": {"type": "string"},
|
|
116
|
+
"suite": {"type": "string"},
|
|
117
|
+
"status": {"type": "string", "enum": [s.value for s in Status]},
|
|
118
|
+
"exit_code": {"type": "integer"},
|
|
119
|
+
"duration_ms": {"type": "number"},
|
|
120
|
+
"output_snippet": {"type": "string"},
|
|
121
|
+
"error_message": {"type": ["string", "null"]},
|
|
122
|
+
"evidence_id": {"type": ["string", "null"]}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
"build_status": {"type": "string", "enum": [s.value for s in Status]},
|
|
127
|
+
"todo_markers": {"type": "array", "items": {"type": "object"}},
|
|
128
|
+
"evidence_ids": {"type": "array", "items": {"type": "string"}},
|
|
129
|
+
"last_scanned_at": {"type": "string"}
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
|
|
133
|
+
# 2. Conversational State
|
|
134
|
+
"conversational_state": {
|
|
135
|
+
"type": "object",
|
|
136
|
+
"required": ["session_id", "user_requirements", "architectural_decisions", "agent_claims", "assumptions", "unresolved_questions", "transcript_provenance"],
|
|
137
|
+
"properties": {
|
|
138
|
+
"session_id": {"type": "string"},
|
|
139
|
+
"user_requirements": {
|
|
140
|
+
"type": "array",
|
|
141
|
+
"items": {
|
|
142
|
+
"type": "object",
|
|
143
|
+
"required": ["id", "title", "description", "status"],
|
|
144
|
+
"properties": {
|
|
145
|
+
"id": {"type": "string"},
|
|
146
|
+
"title": {"type": "string"},
|
|
147
|
+
"description": {"type": "string"},
|
|
148
|
+
"source_turn": {"type": ["integer", "null"]},
|
|
149
|
+
"status": {"type": "string", "enum": [s.value for s in Status]},
|
|
150
|
+
"evidence_id": {"type": ["string", "null"]}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
"architectural_decisions": {
|
|
155
|
+
"type": "array",
|
|
156
|
+
"items": {
|
|
157
|
+
"type": "object",
|
|
158
|
+
"required": ["id", "title", "rationale", "constraints", "recorded_at"],
|
|
159
|
+
"properties": {
|
|
160
|
+
"id": {"type": "string"},
|
|
161
|
+
"title": {"type": "string"},
|
|
162
|
+
"rationale": {"type": "string"},
|
|
163
|
+
"constraints": {"type": "array", "items": {"type": "string"}},
|
|
164
|
+
"recorded_at": {"type": "string"},
|
|
165
|
+
"evidence_id": {"type": ["string", "null"]}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
"agent_claims": {
|
|
170
|
+
"type": "array",
|
|
171
|
+
"items": {
|
|
172
|
+
"type": "object",
|
|
173
|
+
"required": ["id", "claim_text", "claimed_status", "source_agent"],
|
|
174
|
+
"properties": {
|
|
175
|
+
"id": {"type": "string"},
|
|
176
|
+
"claim_text": {"type": "string"},
|
|
177
|
+
"target_component": {"type": ["string", "null"]},
|
|
178
|
+
"claimed_status": {"type": "string", "enum": [s.value for s in Status]},
|
|
179
|
+
"source_agent": {"type": "string"},
|
|
180
|
+
"turn_id": {"type": ["integer", "null"]},
|
|
181
|
+
"confidence_claimed": {"type": ["number", "null"]},
|
|
182
|
+
"evidence_id": {"type": ["string", "null"]}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
},
|
|
186
|
+
"assumptions": {"type": "array", "items": {"type": "string"}},
|
|
187
|
+
"unresolved_questions": {
|
|
188
|
+
"type": "array",
|
|
189
|
+
"items": {
|
|
190
|
+
"type": "object",
|
|
191
|
+
"required": ["id", "question", "context", "blocking", "asked_at"],
|
|
192
|
+
"properties": {
|
|
193
|
+
"id": {"type": "string"},
|
|
194
|
+
"question": {"type": "string"},
|
|
195
|
+
"context": {"type": "string"},
|
|
196
|
+
"blocking": {"type": "boolean"},
|
|
197
|
+
"asked_at": {"type": "string"},
|
|
198
|
+
"evidence_id": {"type": ["string", "null"]}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
},
|
|
202
|
+
"transcript_provenance": {"type": "array", "items": {"type": "string"}}
|
|
203
|
+
}
|
|
204
|
+
},
|
|
205
|
+
|
|
206
|
+
# 3. Agent Execution State
|
|
207
|
+
"agent_execution_state": {
|
|
208
|
+
"type": "object",
|
|
209
|
+
"required": ["agent_id", "model_name", "active_tasks", "modified_files_in_flight", "last_error_encountered", "next_action", "execution_context_metadata", "updated_at"],
|
|
210
|
+
"properties": {
|
|
211
|
+
"agent_id": {"type": "string"},
|
|
212
|
+
"model_name": {"type": "string"},
|
|
213
|
+
"active_tasks": {
|
|
214
|
+
"type": "array",
|
|
215
|
+
"items": {
|
|
216
|
+
"type": "object",
|
|
217
|
+
"required": ["id", "title", "status", "target_files", "target_symbols", "notes"],
|
|
218
|
+
"properties": {
|
|
219
|
+
"id": {"type": "string"},
|
|
220
|
+
"title": {"type": "string"},
|
|
221
|
+
"status": {"type": "string", "enum": [s.value for s in Status]},
|
|
222
|
+
"target_files": {"type": "array", "items": {"type": "string"}},
|
|
223
|
+
"target_symbols": {"type": "array", "items": {"type": "string"}},
|
|
224
|
+
"notes": {"type": "string"}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
},
|
|
228
|
+
"modified_files_in_flight": {"type": "array", "items": {"type": "string"}},
|
|
229
|
+
"last_error_encountered": {"type": ["string", "null"]},
|
|
230
|
+
"next_action": {
|
|
231
|
+
"type": ["object", "null"],
|
|
232
|
+
"properties": {
|
|
233
|
+
"action_type": {"type": "string"},
|
|
234
|
+
"target_uri": {"type": "string"},
|
|
235
|
+
"description": {"type": "string"},
|
|
236
|
+
"prerequisites": {"type": "array", "items": {"type": "string"}}
|
|
237
|
+
}
|
|
238
|
+
},
|
|
239
|
+
"execution_context_metadata": {"type": "object"},
|
|
240
|
+
"updated_at": {"type": "string"}
|
|
241
|
+
}
|
|
242
|
+
},
|
|
243
|
+
|
|
244
|
+
# Global Evidence Pool
|
|
245
|
+
"evidence_pool": {
|
|
246
|
+
"type": "object",
|
|
247
|
+
"additionalProperties": {
|
|
248
|
+
"type": "object",
|
|
249
|
+
"required": ["id", "type", "level", "summary", "raw_payload", "checksum", "created_at"],
|
|
250
|
+
"properties": {
|
|
251
|
+
"id": {"type": "string"},
|
|
252
|
+
"type": {"type": "string", "enum": [e.value for e in EvidenceType]},
|
|
253
|
+
"level": {"type": "integer", "enum": [int(l.value) for l in EvidenceLevel]},
|
|
254
|
+
"summary": {"type": "string"},
|
|
255
|
+
"raw_payload": {"type": "object"},
|
|
256
|
+
"provenance": {
|
|
257
|
+
"type": ["object", "null"],
|
|
258
|
+
"properties": {
|
|
259
|
+
"extractor_name": {"type": "string"},
|
|
260
|
+
"source_uri": {"type": "string"},
|
|
261
|
+
"locator": {"type": "string"},
|
|
262
|
+
"collected_at": {"type": "string"},
|
|
263
|
+
"environment_info": {"type": "object"}
|
|
264
|
+
}
|
|
265
|
+
},
|
|
266
|
+
"checksum": {"type": "string"},
|
|
267
|
+
"created_at": {"type": "string"},
|
|
268
|
+
"metadata": {"type": "object"}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
},
|
|
272
|
+
|
|
273
|
+
# Graph Nodes
|
|
274
|
+
"graph_nodes": {
|
|
275
|
+
"type": "object",
|
|
276
|
+
"additionalProperties": {
|
|
277
|
+
"type": "object",
|
|
278
|
+
"required": ["id", "name", "node_type", "status", "confidence_score", "evidence_ids"],
|
|
279
|
+
"properties": {
|
|
280
|
+
"id": {"type": "string"},
|
|
281
|
+
"name": {"type": "string"},
|
|
282
|
+
"node_type": {"type": "string", "enum": [n.value for n in NodeType]},
|
|
283
|
+
"status": {"type": "string", "enum": [s.value for s in Status]},
|
|
284
|
+
"confidence_score": {"type": "number", "minimum": 0.0, "maximum": 100.0},
|
|
285
|
+
"evidence_ids": {"type": "array", "items": {"type": "string"}},
|
|
286
|
+
"metadata": {"type": "object"}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
},
|
|
290
|
+
|
|
291
|
+
# Graph Edges
|
|
292
|
+
"graph_edges": {
|
|
293
|
+
"type": "array",
|
|
294
|
+
"items": {
|
|
295
|
+
"type": "object",
|
|
296
|
+
"required": ["source_id", "target_id", "relation"],
|
|
297
|
+
"properties": {
|
|
298
|
+
"source_id": {"type": "string"},
|
|
299
|
+
"target_id": {"type": "string"},
|
|
300
|
+
"relation": {"type": "string", "enum": [r.value for r in RelationType]}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
},
|
|
304
|
+
|
|
305
|
+
# Contradictions
|
|
306
|
+
"contradictions": {
|
|
307
|
+
"type": "array",
|
|
308
|
+
"items": {
|
|
309
|
+
"type": "object",
|
|
310
|
+
"required": ["id", "severity", "claim_text", "explanation", "detected_at", "resolved"],
|
|
311
|
+
"properties": {
|
|
312
|
+
"id": {"type": "string"},
|
|
313
|
+
"severity": {"type": "string", "enum": ["HIGH", "MEDIUM", "LOW"]},
|
|
314
|
+
"claim_id": {"type": ["string", "null"]},
|
|
315
|
+
"claim_text": {"type": "string"},
|
|
316
|
+
"physical_evidence_id": {"type": ["string", "null"]},
|
|
317
|
+
"explanation": {"type": "string"},
|
|
318
|
+
"detected_at": {"type": "string"},
|
|
319
|
+
"resolved": {"type": "boolean"}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
def validate_canonical_state_dict(data: Dict[str, Any]) -> Tuple[bool, List[str]]:
|
|
328
|
+
"""
|
|
329
|
+
Lightweight validator that verifies dictionary structure against Canonical schema rules.
|
|
330
|
+
Returns (is_valid, list_of_errors).
|
|
331
|
+
"""
|
|
332
|
+
errors: List[str] = []
|
|
333
|
+
|
|
334
|
+
# Required top level keys
|
|
335
|
+
required_top = [
|
|
336
|
+
"schema_version", "project_id", "created_at", "updated_at",
|
|
337
|
+
"project_state", "conversational_state", "agent_execution_state",
|
|
338
|
+
"evidence_pool", "graph_nodes", "graph_edges", "contradictions"
|
|
339
|
+
]
|
|
340
|
+
for key in required_top:
|
|
341
|
+
if key not in data:
|
|
342
|
+
errors.append(f"Missing required top-level field: '{key}'")
|
|
343
|
+
|
|
344
|
+
if errors:
|
|
345
|
+
return False, errors
|
|
346
|
+
|
|
347
|
+
# Check state separation
|
|
348
|
+
if not isinstance(data.get("project_state"), dict):
|
|
349
|
+
errors.append("'project_state' must be an object")
|
|
350
|
+
if not isinstance(data.get("conversational_state"), dict):
|
|
351
|
+
errors.append("'conversational_state' must be an object")
|
|
352
|
+
if not isinstance(data.get("agent_execution_state"), dict):
|
|
353
|
+
errors.append("'agent_execution_state' must be an object")
|
|
354
|
+
|
|
355
|
+
# Validate statuses inside test_results
|
|
356
|
+
for test in data.get("project_state", {}).get("test_results", []):
|
|
357
|
+
st = test.get("status")
|
|
358
|
+
if st not in [s.value for s in Status]:
|
|
359
|
+
errors.append(f"Invalid TestResult status '{st}'")
|
|
360
|
+
|
|
361
|
+
# Validate evidence pool
|
|
362
|
+
for ev_id, ev in data.get("evidence_pool", {}).items():
|
|
363
|
+
if not isinstance(ev, dict):
|
|
364
|
+
errors.append(f"Evidence '{ev_id}' must be an object")
|
|
365
|
+
continue
|
|
366
|
+
ev_type = ev.get("type")
|
|
367
|
+
if ev_type not in [e.value for e in EvidenceType]:
|
|
368
|
+
errors.append(f"Invalid EvidenceType '{ev_type}' in evidence '{ev_id}'")
|
|
369
|
+
ev_lvl = ev.get("level")
|
|
370
|
+
if ev_lvl not in [int(l.value) for l in EvidenceLevel]:
|
|
371
|
+
errors.append(f"Invalid EvidenceLevel '{ev_lvl}' in evidence '{ev_id}'")
|
|
372
|
+
|
|
373
|
+
# Validate Graph Nodes
|
|
374
|
+
for n_id, node in data.get("graph_nodes", {}).items():
|
|
375
|
+
if not isinstance(node, dict):
|
|
376
|
+
errors.append(f"GraphNode '{n_id}' must be an object")
|
|
377
|
+
continue
|
|
378
|
+
nt = node.get("node_type")
|
|
379
|
+
if nt not in [n.value for n in NodeType]:
|
|
380
|
+
errors.append(f"Invalid NodeType '{nt}' in node '{n_id}'")
|
|
381
|
+
conf = node.get("confidence_score", 0.0)
|
|
382
|
+
if not (0.0 <= conf <= 100.0):
|
|
383
|
+
errors.append(f"Confidence score {conf} out of range [0, 100] in node '{n_id}'")
|
|
384
|
+
|
|
385
|
+
# Validate Graph Edges
|
|
386
|
+
for edge in data.get("graph_edges", []):
|
|
387
|
+
rel = edge.get("relation")
|
|
388
|
+
if rel not in [r.value for r in RelationType]:
|
|
389
|
+
errors.append(f"Invalid RelationType '{rel}' in edge")
|
|
390
|
+
|
|
391
|
+
return (len(errors) == 0, errors)
|
core/serializer.py
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Project Continuum - Canonical State Serializer & Deserializer
|
|
3
|
+
============================================================
|
|
4
|
+
Handles deterministic JSON round-trip serialization, file persistence,
|
|
5
|
+
integrity checks, and schema validation.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Any, Dict, Optional, Union
|
|
11
|
+
|
|
12
|
+
from core.state_models import CanonicalProjectState
|
|
13
|
+
from core.schema import validate_canonical_state_dict
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class StateSerializationError(Exception):
|
|
17
|
+
"""Raised when serialization or deserialization fails."""
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class StateValidationError(Exception):
|
|
22
|
+
"""Raised when a state object violates schema contracts."""
|
|
23
|
+
pass
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class CanonicalStateSerializer:
|
|
27
|
+
"""
|
|
28
|
+
Serializer/Deserializer for Project Continuum Canonical Project State.
|
|
29
|
+
Ensures deterministic ordering, schema validation, and strict state isolation.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
@staticmethod
|
|
33
|
+
def to_dict(state: CanonicalProjectState) -> Dict[str, Any]:
|
|
34
|
+
"""Converts CanonicalProjectState to a validated python dictionary."""
|
|
35
|
+
d = state.to_dict()
|
|
36
|
+
is_valid, errors = validate_canonical_state_dict(d)
|
|
37
|
+
if not is_valid:
|
|
38
|
+
raise StateValidationError(f"State failed schema validation: {', '.join(errors)}")
|
|
39
|
+
return d
|
|
40
|
+
|
|
41
|
+
@staticmethod
|
|
42
|
+
def to_json(state: CanonicalProjectState, indent: int = 2) -> str:
|
|
43
|
+
"""Serializes CanonicalProjectState to a formatted JSON string with deterministic sorting."""
|
|
44
|
+
state_dict = CanonicalStateSerializer.to_dict(state)
|
|
45
|
+
return json.dumps(state_dict, indent=indent, sort_keys=True, ensure_ascii=False)
|
|
46
|
+
|
|
47
|
+
@staticmethod
|
|
48
|
+
def from_dict(data: Dict[str, Any], validate: bool = True) -> CanonicalProjectState:
|
|
49
|
+
"""Constructs and validates a CanonicalProjectState instance from a dict."""
|
|
50
|
+
if validate:
|
|
51
|
+
is_valid, errors = validate_canonical_state_dict(data)
|
|
52
|
+
if not is_valid:
|
|
53
|
+
raise StateValidationError(f"State dict failed schema validation: {', '.join(errors)}")
|
|
54
|
+
return CanonicalProjectState.from_dict(data)
|
|
55
|
+
|
|
56
|
+
@staticmethod
|
|
57
|
+
def from_json(json_str: str, validate: bool = True) -> CanonicalProjectState:
|
|
58
|
+
"""Parses a JSON string into CanonicalProjectState."""
|
|
59
|
+
try:
|
|
60
|
+
data = json.loads(json_str)
|
|
61
|
+
except json.JSONDecodeError as e:
|
|
62
|
+
raise StateSerializationError(f"Malformed JSON: {e}") from e
|
|
63
|
+
return CanonicalStateSerializer.from_dict(data, validate=validate)
|
|
64
|
+
|
|
65
|
+
@staticmethod
|
|
66
|
+
def save_to_file(state: CanonicalProjectState, file_path: Union[str, Path], indent: int = 2) -> None:
|
|
67
|
+
"""Writes canonical state JSON atomically to disk."""
|
|
68
|
+
target_path = Path(file_path)
|
|
69
|
+
target_path.parent.mkdir(parents=True, exist_ok=True)
|
|
70
|
+
json_content = CanonicalStateSerializer.to_json(state, indent=indent)
|
|
71
|
+
|
|
72
|
+
# Write to temp file then rename for atomic write
|
|
73
|
+
temp_file = target_path.with_suffix(f"{target_path.suffix}.tmp")
|
|
74
|
+
temp_file.write_text(json_content, encoding="utf-8")
|
|
75
|
+
temp_file.replace(target_path)
|
|
76
|
+
|
|
77
|
+
@staticmethod
|
|
78
|
+
def load_from_file(file_path: Union[str, Path], validate: bool = True) -> CanonicalProjectState:
|
|
79
|
+
"""Reads canonical state from a JSON file."""
|
|
80
|
+
target_path = Path(file_path)
|
|
81
|
+
if not target_path.exists():
|
|
82
|
+
raise FileNotFoundError(f"State file not found: {target_path}")
|
|
83
|
+
content = target_path.read_text(encoding="utf-8")
|
|
84
|
+
return CanonicalStateSerializer.from_json(content, validate=validate)
|