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.
- {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/PKG-INFO +1 -1
- {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/ct_toolkit/core/kernel.py +4 -1
- {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/ct_toolkit/core/wrapper.py +15 -0
- {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/ct_toolkit/endorsement/reflective.py +1 -0
- {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/ct_toolkit/provenance/log.py +100 -16
- {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/ct_toolkit.egg-info/PKG-INFO +1 -1
- {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/pyproject.toml +1 -1
- {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/LICENSE +0 -0
- {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/README.md +0 -0
- {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/ct_toolkit/__init__.py +0 -0
- {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/ct_toolkit/core/__init__.py +0 -0
- {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/ct_toolkit/core/compatibility.py +0 -0
- {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/ct_toolkit/core/exceptions.py +0 -0
- {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/ct_toolkit/core/integrity.py +0 -0
- {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/ct_toolkit/divergence/__init__.py +0 -0
- {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/ct_toolkit/divergence/analysis.py +0 -0
- {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/ct_toolkit/divergence/engine.py +0 -0
- {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/ct_toolkit/divergence/l2_judge.py +0 -0
- {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/ct_toolkit/divergence/l3_icm.py +0 -0
- {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/ct_toolkit/divergence/loss.py +0 -0
- {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/ct_toolkit/divergence/scheduler.py +0 -0
- {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/ct_toolkit/endorsement/__init__.py +0 -0
- {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/ct_toolkit/endorsement/probes/__init__.py +0 -0
- {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/ct_toolkit/identity/__init__.py +0 -0
- {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/ct_toolkit/identity/embedding.py +0 -0
- {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/ct_toolkit/identity/templates/__init__.py +0 -0
- {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/ct_toolkit/identity/templates/defense.yaml +0 -0
- {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/ct_toolkit/identity/templates/finance.yaml +0 -0
- {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/ct_toolkit/identity/templates/general.yaml +0 -0
- {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/ct_toolkit/identity/templates/medical.yaml +0 -0
- {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/ct_toolkit/kernels/__init__.py +0 -0
- {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/ct_toolkit/kernels/default.yaml +0 -0
- {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/ct_toolkit/kernels/defense.yaml +0 -0
- {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/ct_toolkit/middleware/__init__.py +0 -0
- {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/ct_toolkit/middleware/autogen.py +0 -0
- {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/ct_toolkit/middleware/crewai.py +0 -0
- {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/ct_toolkit/middleware/deepagents.py +0 -0
- {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/ct_toolkit/middleware/langchain.py +0 -0
- {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/ct_toolkit/provenance/__init__.py +0 -0
- {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/ct_toolkit/provenance/vault/__init__.py +0 -0
- {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/ct_toolkit/utils/__init__.py +0 -0
- {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/ct_toolkit/utils/logger.py +0 -0
- {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/ct_toolkit.egg-info/SOURCES.txt +0 -0
- {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/ct_toolkit.egg-info/dependency_links.txt +0 -0
- {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/ct_toolkit.egg-info/requires.txt +0 -0
- {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/ct_toolkit.egg-info/top_level.txt +0 -0
- {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/setup.cfg +0 -0
- {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/tests/test_analysis.py +0 -0
- {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/tests/test_deep_agents_integration.py +0 -0
- {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/tests/test_loss.py +0 -0
- {ct_toolkit-0.2.9 → ct_toolkit-0.3.1}/tests/test_summarization_drift.py +0 -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
|
|
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
|
|
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
|
|
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 ────────────────────────────────────────────────────────────
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "ct-toolkit"
|
|
7
|
-
version = "0.
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|