ct-toolkit 0.2.9__tar.gz → 0.3.1__tar.gz

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 (51) hide show
  1. {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/PKG-INFO +1 -1
  2. {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/ct_toolkit/core/kernel.py +4 -1
  3. {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/ct_toolkit/core/wrapper.py +15 -0
  4. {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/ct_toolkit/endorsement/reflective.py +1 -0
  5. {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/ct_toolkit/provenance/log.py +100 -16
  6. {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/ct_toolkit.egg-info/PKG-INFO +1 -1
  7. {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/pyproject.toml +1 -1
  8. {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/LICENSE +0 -0
  9. {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/README.md +0 -0
  10. {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/ct_toolkit/__init__.py +0 -0
  11. {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/ct_toolkit/core/__init__.py +0 -0
  12. {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/ct_toolkit/core/compatibility.py +0 -0
  13. {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/ct_toolkit/core/exceptions.py +0 -0
  14. {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/ct_toolkit/core/integrity.py +0 -0
  15. {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/ct_toolkit/divergence/__init__.py +0 -0
  16. {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/ct_toolkit/divergence/analysis.py +0 -0
  17. {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/ct_toolkit/divergence/engine.py +0 -0
  18. {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/ct_toolkit/divergence/l2_judge.py +0 -0
  19. {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/ct_toolkit/divergence/l3_icm.py +0 -0
  20. {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/ct_toolkit/divergence/loss.py +0 -0
  21. {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/ct_toolkit/divergence/scheduler.py +0 -0
  22. {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/ct_toolkit/endorsement/__init__.py +0 -0
  23. {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/ct_toolkit/endorsement/probes/__init__.py +0 -0
  24. {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/ct_toolkit/identity/__init__.py +0 -0
  25. {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/ct_toolkit/identity/embedding.py +0 -0
  26. {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/ct_toolkit/identity/templates/__init__.py +0 -0
  27. {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/ct_toolkit/identity/templates/defense.yaml +0 -0
  28. {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/ct_toolkit/identity/templates/finance.yaml +0 -0
  29. {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/ct_toolkit/identity/templates/general.yaml +0 -0
  30. {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/ct_toolkit/identity/templates/medical.yaml +0 -0
  31. {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/ct_toolkit/kernels/__init__.py +0 -0
  32. {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/ct_toolkit/kernels/default.yaml +0 -0
  33. {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/ct_toolkit/kernels/defense.yaml +0 -0
  34. {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/ct_toolkit/middleware/__init__.py +0 -0
  35. {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/ct_toolkit/middleware/autogen.py +0 -0
  36. {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/ct_toolkit/middleware/crewai.py +0 -0
  37. {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/ct_toolkit/middleware/deepagents.py +0 -0
  38. {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/ct_toolkit/middleware/langchain.py +0 -0
  39. {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/ct_toolkit/provenance/__init__.py +0 -0
  40. {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/ct_toolkit/provenance/vault/__init__.py +0 -0
  41. {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/ct_toolkit/utils/__init__.py +0 -0
  42. {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/ct_toolkit/utils/logger.py +0 -0
  43. {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/ct_toolkit.egg-info/SOURCES.txt +0 -0
  44. {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/ct_toolkit.egg-info/dependency_links.txt +0 -0
  45. {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/ct_toolkit.egg-info/requires.txt +0 -0
  46. {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/ct_toolkit.egg-info/top_level.txt +0 -0
  47. {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/setup.cfg +0 -0
  48. {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/tests/test_analysis.py +0 -0
  49. {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/tests/test_deep_agents_integration.py +0 -0
  50. {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/tests/test_loss.py +0 -0
  51. {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/tests/test_summarization_drift.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ct-toolkit
3
- Version: 0.2.9
3
+ Version: 0.3.1
4
4
  Summary: Computational Theseus Toolkit — Identity Continuity Guardrails for Agentic Systems
5
5
  Author: Hakan Damar
6
6
  License: Apache-2.0
@@ -73,12 +73,15 @@ class ConstitutionalKernel:
73
73
  else:
74
74
  with open(path, "r", encoding="utf-8") as f:
75
75
  data = yaml.safe_load(f)
76
+
77
+ if not isinstance(data, dict):
78
+ data = {}
76
79
 
77
80
  anchors = [AxiomaticAnchor(**a) for a in data.get("axiomatic_anchors", [])]
78
81
  commitments = [PlasticCommitment(**c) for c in data.get("plastic_commitments", [])]
79
82
 
80
83
  profile = KernelProfile(
81
- name=data["name"],
84
+ name=data.get("name", "custom_kernel"),
82
85
  version=data.get("version", "1.0.0"),
83
86
  description=data.get("description", ""),
84
87
  axiomatic_anchors=anchors,
@@ -635,6 +635,21 @@ class TheseusWrapper:
635
635
  commitment_new_value=commitment_new_value,
636
636
  )
637
637
 
638
+ def export_provenance_log(self) -> list[dict[str, Any]]:
639
+ """
640
+ Exports the entire verified provenance log.
641
+
642
+ Verifies the integrity of the log before returning the data.
643
+ This is intended for external auditing and analysis.
644
+
645
+ Returns:
646
+ A list of all log entries as dictionaries.
647
+
648
+ Raises:
649
+ ChainIntegrityError: If the log's hash chain is broken.
650
+ """
651
+ return self._provenance_log.export_log()
652
+
638
653
  @property
639
654
  def kernel(self) -> ConstitutionalKernel:
640
655
  return self._kernel
@@ -210,6 +210,7 @@ class ReflectiveEndorsement:
210
210
  self._log = provenance_log
211
211
  self._approval_channel = approval_channel or cli_approval_channel
212
212
  self._pending_records: list[EndorsementRecord] = []
213
+ self._pre_update_snapshot: dict[str, Any] | None = None
213
214
 
214
215
  # ── Main Flow ───────────────────────────────────────────────────────────────
215
216
 
@@ -43,6 +43,7 @@ class ProvenanceEntry:
43
43
  divergence_score: float | None
44
44
  metadata: dict[str, Any]
45
45
  prev_entry_hash: str # Content hash of the previous entry
46
+ status: str = field(default="active") # "active" or "rolled_back"
46
47
  hmac_signature: str = field(default="", repr=False)
47
48
 
48
49
  def content_hash(self) -> str:
@@ -56,6 +57,7 @@ class ProvenanceEntry:
56
57
  "divergence_score": self.divergence_score,
57
58
  "metadata": self.metadata,
58
59
  "prev_entry_hash": self.prev_entry_hash,
60
+ "status": self.status,
59
61
  },
60
62
  sort_keys=True,
61
63
  )
@@ -111,6 +113,7 @@ class ProvenanceLog:
111
113
  divergence_score=divergence_score,
112
114
  metadata=metadata or {},
113
115
  prev_entry_hash=prev_hash,
116
+ status="active",
114
117
  )
115
118
 
116
119
  content_hash = entry.content_hash()
@@ -124,13 +127,14 @@ class ProvenanceLog:
124
127
 
125
128
  def verify_chain(self) -> bool:
126
129
  """
127
- Verifies the entire chain from start to finish.
130
+ Verifies the active chain from start to finish.
128
131
  - Is each entry's HMAC valid?
129
132
  - Does each entry correctly reference the previous one?
133
+ - Ignores 'rolled_back' entries.
130
134
 
131
135
  Returns: True (valid) | raises ChainIntegrityError
132
136
  """
133
- entries = self._load_all_entries()
137
+ entries = self._load_all_entries(include_rolled_back=False)
134
138
  if not entries:
135
139
  return True
136
140
 
@@ -148,12 +152,12 @@ class ProvenanceLog:
148
152
 
149
153
  expected_prev = entry.content_hash()
150
154
 
151
- logger.info(f"Chain verified: {len(entries)} entries valid.")
155
+ logger.info(f"Chain verified: {len(entries)} active entries valid.")
152
156
  return True
153
157
 
154
- def get_entries(self, limit: int = 100) -> list[ProvenanceEntry]:
158
+ def get_entries(self, limit: int = 100, include_rolled_back: bool = False) -> list[ProvenanceEntry]:
155
159
  """Returns the last N entries."""
156
- return self._load_all_entries(limit=limit)
160
+ return self._load_all_entries(limit=limit, include_rolled_back=include_rolled_back)
157
161
 
158
162
  def get_entry(self, entry_id: str) -> ProvenanceEntry | None:
159
163
  cursor = self._conn.execute(
@@ -166,15 +170,15 @@ class ProvenanceLog:
166
170
  self, template: str, kernel_name: str, model: str
167
171
  ) -> int:
168
172
  """
169
- Calculates the number of interactions matching the current context.
173
+ Calculates the number of active interactions matching the current context.
170
174
  This provides the experience metric for Stability-Plasticity Scheduling.
171
175
  """
172
- # We parse the JSON metadata column. SQLite JSON1 extension is standard.
173
176
  query = """
174
177
  SELECT COUNT(*) FROM provenance
175
178
  WHERE json_extract(metadata, '$.template') = ?
176
179
  AND json_extract(metadata, '$.kernel') = ?
177
180
  AND json_extract(metadata, '$.model') = ?
181
+ AND status = 'active'
178
182
  """
179
183
  try:
180
184
  row = self._conn.execute(query, (template, kernel_name, model)).fetchone()
@@ -184,21 +188,81 @@ class ProvenanceLog:
184
188
  f"Could not count interactions (JSON query failed): {e}. "
185
189
  "Returning 0 to avoid inflating ElasticityScheduler thresholds."
186
190
  )
187
- # Return 0 (conservative) — do NOT return total count without filters
188
- # as it would incorrectly inflate the interaction_count for the scheduler.
189
191
  return 0
190
192
 
193
+ def export_log(self, include_rolled_back: bool = False) -> list[dict[str, Any]]:
194
+ """
195
+ Exports the verified log as a list of dictionaries.
196
+ By default, only active entries are exported.
197
+
198
+ Args:
199
+ include_rolled_back: If True, include all entries.
200
+
201
+ Returns:
202
+ A list of all log entries, where each entry is a dictionary.
203
+
204
+ Raises:
205
+ ChainIntegrityError: If the active chain is invalid.
206
+ """
207
+ self.verify_chain() # Ensure integrity before export
208
+ all_entries = self._load_all_entries(include_rolled_back=include_rolled_back)
209
+ return [asdict(entry) for entry in all_entries]
210
+
211
+ def get_read_only_connection(self) -> sqlite3.Connection:
212
+ """
213
+ Returns a read-only connection to the database.
214
+ """
215
+ try:
216
+ db_uri = self._vault_path.as_uri() + "?mode=ro"
217
+ return sqlite3.connect(db_uri, uri=True, check_same_thread=False)
218
+ except sqlite3.OperationalError as e:
219
+ raise VaultError(
220
+ "Could not open read-only connection. "
221
+ "Ensure the database file exists and has correct permissions."
222
+ ) from e
223
+
224
+ def rollback(self, agent_id: str, entry_id: str) -> None:
225
+ """
226
+ Rolls back the log to the specified entry ID for a given agent.
227
+ All subsequent entries for that agent will be marked as 'rolled_back'.
228
+ """
229
+ with self._lock:
230
+ entry = self.get_entry(entry_id)
231
+ if not entry:
232
+ raise ValueError(f"Entry with ID '{entry_id}' not found.")
233
+
234
+ # This is a simplification. In a real multi-agent system,
235
+ # we would need a more robust way to identify agent-specific entries.
236
+ # Here, we assume an 'agent_id' is stored in the metadata.
237
+ if entry.metadata.get("agent_id") != agent_id:
238
+ raise ValueError("Entry does not belong to the specified agent.")
239
+
240
+ try:
241
+ self._conn.execute(
242
+ """
243
+ UPDATE provenance
244
+ SET status = 'rolled_back'
245
+ WHERE timestamp > ? AND json_extract(metadata, '$.agent_id') = ?
246
+ """,
247
+ (entry.timestamp, agent_id),
248
+ )
249
+ self._conn.commit()
250
+ logger.info(f"Provenance log rolled back for agent '{agent_id}' to entry '{entry_id}'")
251
+ except sqlite3.Error as e:
252
+ raise VaultError(f"SQLite rollback error: {e}") from e
253
+
191
254
  # ── Database ──────────────────────────────────────────────────────────────
192
255
 
193
256
  def _init_db(self) -> sqlite3.Connection:
194
257
  import os
195
258
  self._vault_path.parent.mkdir(parents=True, exist_ok=True)
196
- # Create file if not exist to set permissions before sqlite connection
197
259
  if not self._vault_path.exists():
198
260
  self._vault_path.touch()
199
261
  self._vault_path.chmod(0o600)
200
262
 
201
263
  conn = sqlite3.connect(str(self._vault_path), check_same_thread=False)
264
+
265
+ # Create table if it doesn't exist
202
266
  conn.execute("""
203
267
  CREATE TABLE IF NOT EXISTS provenance (
204
268
  id TEXT PRIMARY KEY,
@@ -208,9 +272,18 @@ class ProvenanceLog:
208
272
  divergence_score REAL,
209
273
  metadata TEXT NOT NULL,
210
274
  prev_entry_hash TEXT NOT NULL,
211
- hmac_signature TEXT NOT NULL
275
+ hmac_signature TEXT NOT NULL,
276
+ status TEXT NOT NULL DEFAULT 'active'
212
277
  )
213
278
  """)
279
+
280
+ # Add status column for migration from older versions
281
+ try:
282
+ conn.execute("SELECT status FROM provenance LIMIT 1").fetchall()
283
+ except sqlite3.OperationalError:
284
+ conn.execute("ALTER TABLE provenance ADD COLUMN status TEXT DEFAULT 'active'")
285
+ logger.info("Migrated database: Added 'status' column to provenance table.")
286
+
214
287
  conn.commit()
215
288
  return conn
216
289
 
@@ -220,8 +293,8 @@ class ProvenanceLog:
220
293
  """
221
294
  INSERT INTO provenance
222
295
  (id, timestamp, request_hash, response_hash,
223
- divergence_score, metadata, prev_entry_hash, hmac_signature)
224
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)
296
+ divergence_score, metadata, prev_entry_hash, hmac_signature, status)
297
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
225
298
  """,
226
299
  (
227
300
  entry.id,
@@ -232,22 +305,32 @@ class ProvenanceLog:
232
305
  json.dumps(entry.metadata),
233
306
  entry.prev_entry_hash,
234
307
  entry.hmac_signature,
308
+ entry.status,
235
309
  ),
236
310
  )
237
311
  self._conn.commit()
238
312
  except sqlite3.Error as e:
239
313
  raise VaultError(f"SQLite write error: {e}") from e
240
314
 
241
- def _load_all_entries(self, limit: int | None = None) -> list[ProvenanceEntry]:
242
- query = "SELECT * FROM provenance ORDER BY timestamp ASC"
315
+ def _load_all_entries(self, limit: int | None = None, include_rolled_back: bool = False) -> list[ProvenanceEntry]:
316
+ query = "SELECT * FROM provenance"
317
+ conditions = []
318
+ if not include_rolled_back:
319
+ conditions.append("status = 'active'")
320
+
321
+ if conditions:
322
+ query += " WHERE " + " AND ".join(conditions)
323
+
324
+ query += " ORDER BY timestamp ASC"
243
325
  if limit:
244
326
  query += f" LIMIT {limit}"
327
+
245
328
  rows = self._conn.execute(query).fetchall()
246
329
  return [self._row_to_entry(r) for r in rows]
247
330
 
248
331
  def _get_last_entry_hash(self) -> str:
249
332
  row = self._conn.execute(
250
- "SELECT * FROM provenance ORDER BY timestamp DESC LIMIT 1"
333
+ "SELECT * FROM provenance WHERE status = 'active' ORDER BY timestamp DESC LIMIT 1"
251
334
  ).fetchone()
252
335
  if not row:
253
336
  return self.GENESIS_HASH
@@ -264,6 +347,7 @@ class ProvenanceLog:
264
347
  metadata=json.loads(row[5]),
265
348
  prev_entry_hash=row[6],
266
349
  hmac_signature=row[7],
350
+ status=row[8],
267
351
  )
268
352
 
269
353
  # ── Cryptography ────────────────────────────────────────────────────────────
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ct-toolkit
3
- Version: 0.2.9
3
+ Version: 0.3.1
4
4
  Summary: Computational Theseus Toolkit — Identity Continuity Guardrails for Agentic Systems
5
5
  Author: Hakan Damar
6
6
  License: Apache-2.0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "ct-toolkit"
7
- version = "0.2.9"
7
+ version = "0.3.1"
8
8
  description = "Computational Theseus Toolkit — Identity Continuity Guardrails for Agentic Systems"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
File without changes
File without changes
File without changes