ct-toolkit 0.3.4__tar.gz → 0.3.6__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.3.4 → ct_toolkit-0.3.6}/PKG-INFO +26 -6
- {ct_toolkit-0.3.4 → ct_toolkit-0.3.6}/README.md +23 -5
- {ct_toolkit-0.3.4 → ct_toolkit-0.3.6}/ct_toolkit/__init__.py +3 -0
- ct_toolkit-0.3.6/ct_toolkit/__main__.py +9 -0
- ct_toolkit-0.3.6/ct_toolkit/cli.py +169 -0
- ct_toolkit-0.3.4/ct_toolkit/middleware/deepagents.py → ct_toolkit-0.3.6/ct_toolkit/core/compression_guard.py +23 -60
- {ct_toolkit-0.3.4 → ct_toolkit-0.3.6}/ct_toolkit/core/wrapper.py +24 -0
- {ct_toolkit-0.3.4 → ct_toolkit-0.3.6}/ct_toolkit/middleware/crewai.py +4 -0
- ct_toolkit-0.3.6/ct_toolkit/middleware/deepagents.py +67 -0
- {ct_toolkit-0.3.4 → ct_toolkit-0.3.6}/ct_toolkit/middleware/langchain.py +5 -0
- {ct_toolkit-0.3.4 → ct_toolkit-0.3.6}/ct_toolkit.egg-info/PKG-INFO +26 -6
- {ct_toolkit-0.3.4 → ct_toolkit-0.3.6}/ct_toolkit.egg-info/SOURCES.txt +6 -0
- ct_toolkit-0.3.6/ct_toolkit.egg-info/entry_points.txt +2 -0
- {ct_toolkit-0.3.4 → ct_toolkit-0.3.6}/ct_toolkit.egg-info/requires.txt +2 -0
- {ct_toolkit-0.3.4 → ct_toolkit-0.3.6}/pyproject.toml +6 -1
- ct_toolkit-0.3.6/tests/test_cli.py +44 -0
- ct_toolkit-0.3.6/tests/test_cli_extended.py +53 -0
- {ct_toolkit-0.3.4 → ct_toolkit-0.3.6}/LICENSE +0 -0
- {ct_toolkit-0.3.4 → ct_toolkit-0.3.6}/ct_toolkit/core/__init__.py +0 -0
- {ct_toolkit-0.3.4 → ct_toolkit-0.3.6}/ct_toolkit/core/compatibility.py +0 -0
- {ct_toolkit-0.3.4 → ct_toolkit-0.3.6}/ct_toolkit/core/exceptions.py +0 -0
- {ct_toolkit-0.3.4 → ct_toolkit-0.3.6}/ct_toolkit/core/integrity.py +0 -0
- {ct_toolkit-0.3.4 → ct_toolkit-0.3.6}/ct_toolkit/core/kernel.py +0 -0
- {ct_toolkit-0.3.4 → ct_toolkit-0.3.6}/ct_toolkit/divergence/__init__.py +0 -0
- {ct_toolkit-0.3.4 → ct_toolkit-0.3.6}/ct_toolkit/divergence/analysis.py +0 -0
- {ct_toolkit-0.3.4 → ct_toolkit-0.3.6}/ct_toolkit/divergence/engine.py +0 -0
- {ct_toolkit-0.3.4 → ct_toolkit-0.3.6}/ct_toolkit/divergence/l2_judge.py +0 -0
- {ct_toolkit-0.3.4 → ct_toolkit-0.3.6}/ct_toolkit/divergence/l3_icm.py +0 -0
- {ct_toolkit-0.3.4 → ct_toolkit-0.3.6}/ct_toolkit/divergence/loss.py +0 -0
- {ct_toolkit-0.3.4 → ct_toolkit-0.3.6}/ct_toolkit/divergence/scheduler.py +0 -0
- {ct_toolkit-0.3.4 → ct_toolkit-0.3.6}/ct_toolkit/endorsement/__init__.py +0 -0
- {ct_toolkit-0.3.4 → ct_toolkit-0.3.6}/ct_toolkit/endorsement/probes/__init__.py +0 -0
- {ct_toolkit-0.3.4 → ct_toolkit-0.3.6}/ct_toolkit/endorsement/reflective.py +0 -0
- {ct_toolkit-0.3.4 → ct_toolkit-0.3.6}/ct_toolkit/identity/__init__.py +0 -0
- {ct_toolkit-0.3.4 → ct_toolkit-0.3.6}/ct_toolkit/identity/embedding.py +0 -0
- {ct_toolkit-0.3.4 → ct_toolkit-0.3.6}/ct_toolkit/identity/templates/__init__.py +0 -0
- {ct_toolkit-0.3.4 → ct_toolkit-0.3.6}/ct_toolkit/identity/templates/defense.yaml +0 -0
- {ct_toolkit-0.3.4 → ct_toolkit-0.3.6}/ct_toolkit/identity/templates/finance.yaml +0 -0
- {ct_toolkit-0.3.4 → ct_toolkit-0.3.6}/ct_toolkit/identity/templates/general.yaml +0 -0
- {ct_toolkit-0.3.4 → ct_toolkit-0.3.6}/ct_toolkit/identity/templates/medical.yaml +0 -0
- {ct_toolkit-0.3.4 → ct_toolkit-0.3.6}/ct_toolkit/kernels/__init__.py +0 -0
- {ct_toolkit-0.3.4 → ct_toolkit-0.3.6}/ct_toolkit/kernels/default.yaml +0 -0
- {ct_toolkit-0.3.4 → ct_toolkit-0.3.6}/ct_toolkit/kernels/defense.yaml +0 -0
- {ct_toolkit-0.3.4 → ct_toolkit-0.3.6}/ct_toolkit/middleware/__init__.py +0 -0
- {ct_toolkit-0.3.4 → ct_toolkit-0.3.6}/ct_toolkit/middleware/autogen.py +0 -0
- {ct_toolkit-0.3.4 → ct_toolkit-0.3.6}/ct_toolkit/provenance/__init__.py +0 -0
- {ct_toolkit-0.3.4 → ct_toolkit-0.3.6}/ct_toolkit/provenance/log.py +0 -0
- {ct_toolkit-0.3.4 → ct_toolkit-0.3.6}/ct_toolkit/provenance/vault/__init__.py +0 -0
- {ct_toolkit-0.3.4 → ct_toolkit-0.3.6}/ct_toolkit/utils/__init__.py +0 -0
- {ct_toolkit-0.3.4 → ct_toolkit-0.3.6}/ct_toolkit/utils/logger.py +0 -0
- {ct_toolkit-0.3.4 → ct_toolkit-0.3.6}/ct_toolkit.egg-info/dependency_links.txt +0 -0
- {ct_toolkit-0.3.4 → ct_toolkit-0.3.6}/ct_toolkit.egg-info/top_level.txt +0 -0
- {ct_toolkit-0.3.4 → ct_toolkit-0.3.6}/setup.cfg +0 -0
- {ct_toolkit-0.3.4 → ct_toolkit-0.3.6}/tests/test_analysis.py +0 -0
- {ct_toolkit-0.3.4 → ct_toolkit-0.3.6}/tests/test_deep_agents_integration.py +0 -0
- {ct_toolkit-0.3.4 → ct_toolkit-0.3.6}/tests/test_loss.py +0 -0
- {ct_toolkit-0.3.4 → ct_toolkit-0.3.6}/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.3.
|
|
3
|
+
Version: 0.3.6
|
|
4
4
|
Summary: Computational Theseus Toolkit — Identity Continuity Guardrails for Agentic Systems
|
|
5
5
|
Author: Hakan Damar
|
|
6
6
|
License: Apache-2.0
|
|
@@ -18,6 +18,8 @@ Requires-Dist: SQLAlchemy>=2.0.0
|
|
|
18
18
|
Requires-Dist: httpx>=0.27.0
|
|
19
19
|
Requires-Dist: rich>=13.0.0
|
|
20
20
|
Requires-Dist: jinja2>=3.1.0
|
|
21
|
+
Requires-Dist: pyasn1>=0.6.3
|
|
22
|
+
Requires-Dist: typer[all]>=0.12.0
|
|
21
23
|
Provides-Extra: ollama
|
|
22
24
|
Requires-Dist: ollama>=0.2.0; extra == "ollama"
|
|
23
25
|
Provides-Extra: langchain
|
|
@@ -63,7 +65,9 @@ For full API reference, architecture details, examples, and integration guides,
|
|
|
63
65
|
|
|
64
66
|
In complex agentic workflows, LLMs tend to "drift" from their original instructions. CT Toolkit provides the mathematical and cryptographic guardrails to ensure your agents remain aligned with their core constitution, even across deep hierarchies.
|
|
65
67
|
|
|
68
|
+
- **Passive Context Compression Detection**: Automatically detects silent provider-side history compression (e.g., OpenAI/Anthropic) via history shrinkage heuristics.
|
|
66
69
|
- **Constitutional Kernels**: Axiomatic identity anchors.
|
|
70
|
+
- **Standalone Auditor CLI**: Rapidly audit any LLM endpoint for identity drift without writing code.
|
|
67
71
|
- **Autonomous Self-Correction**: Active L2->L1 feedback loop that retries and corrects divergent responses before they reach the user.
|
|
68
72
|
- **Divergence Engine**: Multi-tiered drift analysis (L1/L2/L3).
|
|
69
73
|
- **Hierarchical Propagation**: Mother-to-child constraint inheritance.
|
|
@@ -78,10 +82,13 @@ pip install ct-toolkit
|
|
|
78
82
|
```
|
|
79
83
|
|
|
80
84
|
```python
|
|
81
|
-
from ct_toolkit import TheseusWrapper
|
|
85
|
+
from ct_toolkit import TheseusWrapper, WrapperConfig
|
|
86
|
+
|
|
87
|
+
# Protect against silent provider context compression
|
|
88
|
+
config = WrapperConfig(compression_passive_detection=True)
|
|
82
89
|
|
|
83
90
|
# One-line injection for any LLM provider
|
|
84
|
-
client = TheseusWrapper(provider="openai")
|
|
91
|
+
client = TheseusWrapper(provider="openai", config=config)
|
|
85
92
|
|
|
86
93
|
# Guardrails and drift analysis applied automatically
|
|
87
94
|
response = client.chat("What are your core security axioms?")
|
|
@@ -90,15 +97,28 @@ print(response.content)
|
|
|
90
97
|
print(f"Divergence Score: {response.divergence_score}")
|
|
91
98
|
```
|
|
92
99
|
|
|
100
|
+
### 🔍 Standalone Auditor (CLI)
|
|
101
|
+
|
|
102
|
+
Audit any LLM endpoint (OpenAI, Ollama, LM Studio) directly from your terminal:
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
# Audit a local Ollama model
|
|
106
|
+
ct-toolkit audit --url http://localhost:11434/v1 --kernel defense
|
|
107
|
+
|
|
108
|
+
# List available kernels and templates
|
|
109
|
+
ct-toolkit list-kernels
|
|
110
|
+
ct-toolkit list-templates
|
|
111
|
+
```
|
|
112
|
+
|
|
93
113
|
---
|
|
94
114
|
|
|
95
115
|
## 🚦 Project Health & Status
|
|
96
116
|
|
|
97
117
|
| Metric | Status |
|
|
98
118
|
| :--------------- | :------------------------------------------------------ |
|
|
99
|
-
| **Tests** | ✅
|
|
100
|
-
| **Last Phase** | ✅ Phase
|
|
101
|
-
| **Current Goal** | 🔶 Phase
|
|
119
|
+
| **Tests** | ✅ 269/272 passing (92% coverage) |
|
|
120
|
+
| **Last Phase** | ✅ Phase 6: Auditor Mode (Complete) |
|
|
121
|
+
| **Current Goal** | 🔶 Phase 7: Multi-Agent Synchronization (Integration) |
|
|
102
122
|
|
|
103
123
|
For a detailed breakdown of the 8-phase roadmap, see [**PROJECT_STATUS.md**](docs/PROJECT_STATUS.md).
|
|
104
124
|
|
|
@@ -24,7 +24,9 @@ For full API reference, architecture details, examples, and integration guides,
|
|
|
24
24
|
|
|
25
25
|
In complex agentic workflows, LLMs tend to "drift" from their original instructions. CT Toolkit provides the mathematical and cryptographic guardrails to ensure your agents remain aligned with their core constitution, even across deep hierarchies.
|
|
26
26
|
|
|
27
|
+
- **Passive Context Compression Detection**: Automatically detects silent provider-side history compression (e.g., OpenAI/Anthropic) via history shrinkage heuristics.
|
|
27
28
|
- **Constitutional Kernels**: Axiomatic identity anchors.
|
|
29
|
+
- **Standalone Auditor CLI**: Rapidly audit any LLM endpoint for identity drift without writing code.
|
|
28
30
|
- **Autonomous Self-Correction**: Active L2->L1 feedback loop that retries and corrects divergent responses before they reach the user.
|
|
29
31
|
- **Divergence Engine**: Multi-tiered drift analysis (L1/L2/L3).
|
|
30
32
|
- **Hierarchical Propagation**: Mother-to-child constraint inheritance.
|
|
@@ -39,10 +41,13 @@ pip install ct-toolkit
|
|
|
39
41
|
```
|
|
40
42
|
|
|
41
43
|
```python
|
|
42
|
-
from ct_toolkit import TheseusWrapper
|
|
44
|
+
from ct_toolkit import TheseusWrapper, WrapperConfig
|
|
45
|
+
|
|
46
|
+
# Protect against silent provider context compression
|
|
47
|
+
config = WrapperConfig(compression_passive_detection=True)
|
|
43
48
|
|
|
44
49
|
# One-line injection for any LLM provider
|
|
45
|
-
client = TheseusWrapper(provider="openai")
|
|
50
|
+
client = TheseusWrapper(provider="openai", config=config)
|
|
46
51
|
|
|
47
52
|
# Guardrails and drift analysis applied automatically
|
|
48
53
|
response = client.chat("What are your core security axioms?")
|
|
@@ -51,15 +56,28 @@ print(response.content)
|
|
|
51
56
|
print(f"Divergence Score: {response.divergence_score}")
|
|
52
57
|
```
|
|
53
58
|
|
|
59
|
+
### 🔍 Standalone Auditor (CLI)
|
|
60
|
+
|
|
61
|
+
Audit any LLM endpoint (OpenAI, Ollama, LM Studio) directly from your terminal:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
# Audit a local Ollama model
|
|
65
|
+
ct-toolkit audit --url http://localhost:11434/v1 --kernel defense
|
|
66
|
+
|
|
67
|
+
# List available kernels and templates
|
|
68
|
+
ct-toolkit list-kernels
|
|
69
|
+
ct-toolkit list-templates
|
|
70
|
+
```
|
|
71
|
+
|
|
54
72
|
---
|
|
55
73
|
|
|
56
74
|
## 🚦 Project Health & Status
|
|
57
75
|
|
|
58
76
|
| Metric | Status |
|
|
59
77
|
| :--------------- | :------------------------------------------------------ |
|
|
60
|
-
| **Tests** | ✅
|
|
61
|
-
| **Last Phase** | ✅ Phase
|
|
62
|
-
| **Current Goal** | 🔶 Phase
|
|
78
|
+
| **Tests** | ✅ 269/272 passing (92% coverage) |
|
|
79
|
+
| **Last Phase** | ✅ Phase 6: Auditor Mode (Complete) |
|
|
80
|
+
| **Current Goal** | 🔶 Phase 7: Multi-Agent Synchronization (Integration) |
|
|
63
81
|
|
|
64
82
|
For a detailed breakdown of the 8-phase roadmap, see [**PROJECT_STATUS.md**](docs/PROJECT_STATUS.md).
|
|
65
83
|
|
|
@@ -23,6 +23,8 @@ from ct_toolkit.core.exceptions import (
|
|
|
23
23
|
ChainIntegrityError,
|
|
24
24
|
)
|
|
25
25
|
|
|
26
|
+
__version__ = "0.3.6"
|
|
27
|
+
|
|
26
28
|
__all__ = [
|
|
27
29
|
"TheseusWrapper",
|
|
28
30
|
"WrapperConfig",
|
|
@@ -34,4 +36,5 @@ __all__ = [
|
|
|
34
36
|
"IncompatibleProfileError",
|
|
35
37
|
"CriticalDivergenceError",
|
|
36
38
|
"ChainIntegrityError",
|
|
39
|
+
"__version__",
|
|
37
40
|
]
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ct_toolkit.cli
|
|
3
|
+
--------------
|
|
4
|
+
Main entry point for the Theseus Guard / CT Toolkit CLI.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Optional
|
|
11
|
+
|
|
12
|
+
import typer
|
|
13
|
+
from rich.console import Console
|
|
14
|
+
from rich.panel import Panel
|
|
15
|
+
from rich.table import Table
|
|
16
|
+
|
|
17
|
+
from ct_toolkit import TheseusWrapper, WrapperConfig, __version__
|
|
18
|
+
from ct_toolkit.divergence.l3_icm import ICMRunner
|
|
19
|
+
from ct_toolkit.core.kernel import ConstitutionalKernel
|
|
20
|
+
|
|
21
|
+
app = typer.Typer(
|
|
22
|
+
help="Computational Theseus Toolkit — Identity Continuity Guardrails for Agentic Systems.",
|
|
23
|
+
no_args_is_help=True,
|
|
24
|
+
rich_markup_mode="rich",
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
console = Console()
|
|
28
|
+
|
|
29
|
+
BANNER = r"""
|
|
30
|
+
_______ _ _ ______ _____ ______ _ _ _____ _____ _ _ _____ _____
|
|
31
|
+
|__ __| | | | ____|/ ____| ____| | | |/ ____| / ____| | | | /\ | __ \| __ \
|
|
32
|
+
| | | |__| | |__ | (___ | |__ | | | | (___ | | __| | | | / \ | |__) | | | |
|
|
33
|
+
| | | __ | __| \___ \| __| | | | |\___ \ | | |_ | | | | / /\ \ | _ /| | | |
|
|
34
|
+
| | | | | | |____ ____) | |____| |__| |____) | | |__| | |__| |/ ____ \| | \ \| |__| |
|
|
35
|
+
|_| |_| |_|______|_____/|______|_____/|_____/ \_____|\____//_/ \_\_| \_\_____/
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
def show_banner():
|
|
39
|
+
console.print(f"[bold cyan]{BANNER}[/bold cyan]")
|
|
40
|
+
console.print(f"[bold white] Computatonal Theseus Toolkit (CT Toolkit) v{__version__}[/bold white]")
|
|
41
|
+
console.print(f"[dim] Identity Continuity Guardrails for Agentic Systems[/dim]\n")
|
|
42
|
+
|
|
43
|
+
def version_callback(value: bool):
|
|
44
|
+
if value:
|
|
45
|
+
console.print(f"CT Toolkit v{__version__}")
|
|
46
|
+
raise typer.Exit()
|
|
47
|
+
|
|
48
|
+
@app.callback()
|
|
49
|
+
def main(
|
|
50
|
+
version: Optional[bool] = typer.Option(
|
|
51
|
+
None, "--version", "-v", help="Show version and exit.", callback=version_callback
|
|
52
|
+
),
|
|
53
|
+
):
|
|
54
|
+
"""Theseus Guard CLI — Preserve Agent Identity Continuity."""
|
|
55
|
+
if not version:
|
|
56
|
+
show_banner()
|
|
57
|
+
|
|
58
|
+
@app.command()
|
|
59
|
+
def audit(
|
|
60
|
+
url: str = typer.Option(..., "--url", help="Target LLM API base URL."),
|
|
61
|
+
api_key: str = typer.Option("no-key", "--api-key", help="API Key for the provider."),
|
|
62
|
+
provider: str = typer.Option("openai", "--provider", help="LLM provider (openai, anthropic, ollama)."),
|
|
63
|
+
kernel: str = typer.Option("general", "--kernel", help="Name of the Constitutional Kernel to use."),
|
|
64
|
+
template: str = typer.Option("general", "--template", help="Name of the Identity Template to use."),
|
|
65
|
+
model: Optional[str] = typer.Option(None, "--model", help="Specific model ID to test."),
|
|
66
|
+
max_probes: Optional[int] = typer.Option(None, "--max-probes", help="Max number of probes to run."),
|
|
67
|
+
):
|
|
68
|
+
"""Run an Independent Identity Audit (L3 ICM) against an LLM endpoint."""
|
|
69
|
+
with console.status("[bold green]Initializing Auditor...[/bold green]"):
|
|
70
|
+
try:
|
|
71
|
+
# Load kernel
|
|
72
|
+
config = WrapperConfig(
|
|
73
|
+
kernel_name=kernel,
|
|
74
|
+
template=template,
|
|
75
|
+
project_root=Path.cwd()
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
# Using TheseusWrapper to manage kernel loading
|
|
79
|
+
wrapper = TheseusWrapper(provider=provider, config=config)
|
|
80
|
+
|
|
81
|
+
import openai
|
|
82
|
+
client = openai.OpenAI(base_url=url, api_key=api_key)
|
|
83
|
+
|
|
84
|
+
runner = ICMRunner(
|
|
85
|
+
client=client,
|
|
86
|
+
provider=provider,
|
|
87
|
+
kernel=wrapper.kernel,
|
|
88
|
+
template=template,
|
|
89
|
+
model=model,
|
|
90
|
+
max_probes=max_probes,
|
|
91
|
+
project_root=Path.cwd()
|
|
92
|
+
)
|
|
93
|
+
except Exception as e:
|
|
94
|
+
console.print(f"[bold red]Initialization failed:[/bold red] {e}")
|
|
95
|
+
raise typer.Exit(code=1)
|
|
96
|
+
|
|
97
|
+
console.print(f"🚀 Starting audit against [bold cyan]{url}[/bold cyan] using kernel [bold yellow]{kernel}[/bold yellow]...")
|
|
98
|
+
|
|
99
|
+
with console.status("[bold blue]Running probes...[/bold blue]"):
|
|
100
|
+
try:
|
|
101
|
+
report = runner.run()
|
|
102
|
+
if report.total_probes == 0:
|
|
103
|
+
console.print(f"[bold red]Error:[/bold red] No probes were loaded from {runner.PROBES_DIR}. Check your configuration and probe files.")
|
|
104
|
+
raise typer.Exit(code=1)
|
|
105
|
+
except Exception as e:
|
|
106
|
+
console.print(f"[bold red]Audit failed during execution:[/bold red] {e}")
|
|
107
|
+
raise typer.Exit(code=1)
|
|
108
|
+
|
|
109
|
+
# Results Table
|
|
110
|
+
table = Table(title=f"ICM Audit Report: {kernel}", show_header=True, header_style="bold magenta")
|
|
111
|
+
table.add_column("Probe ID", style="dim", width=20)
|
|
112
|
+
table.add_column("Category")
|
|
113
|
+
table.add_column("Severity")
|
|
114
|
+
table.add_column("Result", justify="center")
|
|
115
|
+
|
|
116
|
+
for res in report.results:
|
|
117
|
+
status = "[green]PASS[/green]" if res.passed else "[red]FAIL[/red]"
|
|
118
|
+
table.add_row(res.probe_id, res.category, res.severity, status)
|
|
119
|
+
|
|
120
|
+
console.print(table)
|
|
121
|
+
|
|
122
|
+
# Summary Panel
|
|
123
|
+
color = "green" if report.is_healthy else "red"
|
|
124
|
+
if report.risk_level == "MEDIUM":
|
|
125
|
+
color = "yellow"
|
|
126
|
+
|
|
127
|
+
summary_text = (
|
|
128
|
+
f"Health Score : [bold]{report.health_score:.1%}[/bold]\n"
|
|
129
|
+
f"Risk Level : [bold {color}]{report.risk_level}[/bold {color}]\n"
|
|
130
|
+
f"Passed/Total : {report.passed}/{report.total_probes}"
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
console.print(Panel(summary_text, title="Summary", border_style=color, expand=False))
|
|
134
|
+
|
|
135
|
+
if not report.is_healthy:
|
|
136
|
+
raise typer.Exit(code=1)
|
|
137
|
+
|
|
138
|
+
@app.command()
|
|
139
|
+
def list_kernels():
|
|
140
|
+
"""List available Constitutional Kernels."""
|
|
141
|
+
kernels_dir = Path(__file__).parent / "kernels"
|
|
142
|
+
files = list(kernels_dir.glob("*.yaml"))
|
|
143
|
+
|
|
144
|
+
table = Table(title="Available Kernels", show_header=True, header_style="bold blue")
|
|
145
|
+
table.add_column("Name", style="cyan")
|
|
146
|
+
table.add_column("Path", style="dim")
|
|
147
|
+
|
|
148
|
+
for f in files:
|
|
149
|
+
table.add_row(f.stem, str(f))
|
|
150
|
+
|
|
151
|
+
console.print(table)
|
|
152
|
+
|
|
153
|
+
@app.command()
|
|
154
|
+
def list_templates():
|
|
155
|
+
"""List available Identity Templates."""
|
|
156
|
+
templates_dir = Path(__file__).parent / "identity" / "templates"
|
|
157
|
+
files = list(templates_dir.glob("*.yaml"))
|
|
158
|
+
|
|
159
|
+
table = Table(title="Available Templates", show_header=True, header_style="bold green")
|
|
160
|
+
table.add_column("Name", style="cyan")
|
|
161
|
+
table.add_column("Path", style="dim")
|
|
162
|
+
|
|
163
|
+
for f in files:
|
|
164
|
+
table.add_row(f.stem, str(f))
|
|
165
|
+
|
|
166
|
+
console.print(table)
|
|
167
|
+
|
|
168
|
+
if __name__ == "__main__":
|
|
169
|
+
app()
|
|
@@ -1,20 +1,18 @@
|
|
|
1
1
|
"""
|
|
2
|
-
ct_toolkit.
|
|
2
|
+
ct_toolkit.core.compression_guard
|
|
3
3
|
---------------------------------
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
identity-continuity guardrails, specifically ensuring that when a
|
|
8
|
-
Deep Agent spawns sub-agents, they inherit the mother agent's
|
|
9
|
-
Constitutional Kernel.
|
|
4
|
+
Provider-agnostic compression drift detector.
|
|
5
|
+
Works with OpenAI compaction, Anthropic compaction, LangChain memory,
|
|
6
|
+
or any framework that silently shortens context.
|
|
10
7
|
"""
|
|
11
8
|
from __future__ import annotations
|
|
12
9
|
|
|
13
|
-
from typing import Any, Dict, List,
|
|
14
|
-
from ct_toolkit.core.wrapper import TheseusWrapper, WrapperConfig
|
|
15
|
-
from ct_toolkit.middleware.langchain import TheseusChatModel
|
|
10
|
+
from typing import Any, Dict, List, TYPE_CHECKING
|
|
16
11
|
from ct_toolkit.utils.logger import get_logger
|
|
17
12
|
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from ct_toolkit.core.wrapper import TheseusWrapper
|
|
15
|
+
|
|
18
16
|
logger = get_logger(__name__)
|
|
19
17
|
|
|
20
18
|
class ContextCompressionGuard:
|
|
@@ -27,7 +25,7 @@ class ContextCompressionGuard:
|
|
|
27
25
|
|
|
28
26
|
def analyze_summary_drift(
|
|
29
27
|
self,
|
|
30
|
-
original_messages: List[Dict[str, str]],
|
|
28
|
+
original_messages: List[Dict[str, str]] | str,
|
|
31
29
|
summary_text: str
|
|
32
30
|
) -> Dict[str, Any]:
|
|
33
31
|
"""
|
|
@@ -37,9 +35,12 @@ class ContextCompressionGuard:
|
|
|
37
35
|
Returns:
|
|
38
36
|
Dict containing similarity score and drift status.
|
|
39
37
|
"""
|
|
40
|
-
# 1. Flatten original messages into a single identity string
|
|
41
|
-
|
|
42
|
-
|
|
38
|
+
# 1. Flatten original messages into a single identity string if it's a list
|
|
39
|
+
if isinstance(original_messages, list):
|
|
40
|
+
original_text = " ".join([m.get("content", "") for m in original_messages])
|
|
41
|
+
else:
|
|
42
|
+
original_text = original_messages
|
|
43
|
+
|
|
43
44
|
# 2. Compute embeddings via identity layer
|
|
44
45
|
original_emb = self.wrapper._identity_layer._compute_vector(original_text)
|
|
45
46
|
summary_emb = self.wrapper._identity_layer._compute_vector(summary_text)
|
|
@@ -49,7 +50,6 @@ class ContextCompressionGuard:
|
|
|
49
50
|
similarity = IdentityEmbeddingLayer.calculate_similarity(original_emb, summary_emb)
|
|
50
51
|
|
|
51
52
|
# 4. Identity Safety Floor (Internal protection against manipulated thresholds)
|
|
52
|
-
# Even if the user sets threshold to 0.4, 0.6 is still risky for LLM identity.
|
|
53
53
|
drift_detected = similarity < self.threshold
|
|
54
54
|
is_critical = similarity < 0.70 # A hard floor for identity integrity
|
|
55
55
|
|
|
@@ -88,48 +88,11 @@ class ContextCompressionGuard:
|
|
|
88
88
|
|
|
89
89
|
return result
|
|
90
90
|
|
|
91
|
-
def
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
)
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
inject Theseus guardrails, enable hierarchical kernel propagation,
|
|
100
|
-
and monitor context compression drift.
|
|
101
|
-
"""
|
|
102
|
-
_wrapper = wrapper or TheseusWrapper(config=wrapper_config)
|
|
103
|
-
guard = ContextCompressionGuard(_wrapper, threshold=compression_threshold)
|
|
104
|
-
|
|
105
|
-
def wrapped_create_deep_agent(*args: Any, **kwargs: Any) -> Any:
|
|
106
|
-
# 1. Inject TheseusChatModel
|
|
107
|
-
model = kwargs.get("model")
|
|
108
|
-
if isinstance(model, str) or model is None:
|
|
109
|
-
kwargs["model"] = TheseusChatModel(
|
|
110
|
-
wrapper=_wrapper,
|
|
111
|
-
model=model if isinstance(model, str) else "gpt-4o-mini"
|
|
112
|
-
)
|
|
113
|
-
|
|
114
|
-
# 2. Inject ContextCompressionGuard into the middleware stack if possible
|
|
115
|
-
# Since deepagents middleware is internal, we use the helper to signal it
|
|
116
|
-
# In a real integration, we'd wrap the SummarizationMiddleware.
|
|
117
|
-
|
|
118
|
-
return create_deep_agent_fn(*args, **kwargs)
|
|
119
|
-
|
|
120
|
-
return wrapped_create_deep_agent
|
|
121
|
-
|
|
122
|
-
class DeepAgentTheseusHelper:
|
|
123
|
-
"""
|
|
124
|
-
Helper for programmatic integration with Deep Agents.
|
|
125
|
-
"""
|
|
126
|
-
@staticmethod
|
|
127
|
-
def prepare_config(wrapper: TheseusWrapper) -> Dict[str, Any]:
|
|
128
|
-
"""Returns configuration for Deep Agents that includes identity constraints."""
|
|
129
|
-
return {
|
|
130
|
-
"callbacks": [], # We could add TheseusLangChainCallback here
|
|
131
|
-
"metadata": {
|
|
132
|
-
"ct_kernel": wrapper.kernel.name,
|
|
133
|
-
"ct_identity_protection": True
|
|
134
|
-
}
|
|
135
|
-
}
|
|
91
|
+
def on_passive_detection(self, original: List[Dict[str, str]], compressed: List[Dict[str, str]]) -> Dict[str, Any]:
|
|
92
|
+
"""
|
|
93
|
+
Triggered when TheseusWrapper detects a significant history shrinkage.
|
|
94
|
+
"""
|
|
95
|
+
logger.info("CT Toolkit | Passive compression detection triggered.")
|
|
96
|
+
original_text = " ".join(m.get("content", "") for m in original)
|
|
97
|
+
compressed_text = " ".join(m.get("content", "") for m in compressed)
|
|
98
|
+
return self.analyze_summary_drift(original_text, compressed_text)
|
|
@@ -34,6 +34,7 @@ from ct_toolkit.core.compatibility import CompatibilityLayer, CompatibilityResul
|
|
|
34
34
|
from ct_toolkit.core.exceptions import CTToolkitError, MissingClientError
|
|
35
35
|
from ct_toolkit.core.integrity import IntegrityMonitor
|
|
36
36
|
from ct_toolkit.divergence.scheduler import RiskProfile
|
|
37
|
+
from ct_toolkit.core.compression_guard import ContextCompressionGuard
|
|
37
38
|
from ct_toolkit.utils.logger import get_logger
|
|
38
39
|
|
|
39
40
|
logger = get_logger(__name__)
|
|
@@ -71,6 +72,10 @@ class WrapperConfig:
|
|
|
71
72
|
|
|
72
73
|
strict_embedding: bool = False # Raise error if embedding API fails
|
|
73
74
|
|
|
75
|
+
# -- Context Compression Guard --
|
|
76
|
+
compression_threshold: float = 0.85
|
|
77
|
+
compression_passive_detection: bool = True
|
|
78
|
+
|
|
74
79
|
|
|
75
80
|
@dataclass
|
|
76
81
|
class CTResponse:
|
|
@@ -165,6 +170,13 @@ class TheseusWrapper:
|
|
|
165
170
|
self._divergence_engine = self._init_divergence_engine()
|
|
166
171
|
self._last_model: str = "unknown"
|
|
167
172
|
|
|
173
|
+
# ── Context Compression Guard ──
|
|
174
|
+
self._compression_guard = ContextCompressionGuard(
|
|
175
|
+
self,
|
|
176
|
+
threshold=self._config.compression_threshold
|
|
177
|
+
)
|
|
178
|
+
self._shadow_history: Optional[List[Dict[str, str]]] = None
|
|
179
|
+
|
|
168
180
|
# ── Integrity Monitoring ──
|
|
169
181
|
self._integrity_monitor = IntegrityMonitor()
|
|
170
182
|
self._register_monitored_files()
|
|
@@ -490,6 +502,18 @@ class TheseusWrapper:
|
|
|
490
502
|
**kwargs: Any,
|
|
491
503
|
) -> Any:
|
|
492
504
|
"""Uses any_llm for unified provider calling."""
|
|
505
|
+
# --- Passive Context Compression Detection ---
|
|
506
|
+
if self._config.compression_passive_detection and self._shadow_history:
|
|
507
|
+
# Simple heuristic: message count dropped significantly
|
|
508
|
+
# We use 0.7 as a threshold (30% drop)
|
|
509
|
+
if len(messages) < len(self._shadow_history) * 0.7:
|
|
510
|
+
self._compression_guard.on_passive_detection(
|
|
511
|
+
original=self._shadow_history,
|
|
512
|
+
compressed=messages
|
|
513
|
+
)
|
|
514
|
+
|
|
515
|
+
# Update shadow history for next call
|
|
516
|
+
self._shadow_history = list(messages)
|
|
493
517
|
# Convert messages to any_llm format if necessary
|
|
494
518
|
# any_llm.completion handles different formats internally
|
|
495
519
|
|
|
@@ -92,6 +92,8 @@ class TheseusCrewMiddleware:
|
|
|
92
92
|
parent_kernel=manager_wrapper.kernel,
|
|
93
93
|
vault_path=manager_wrapper._config.vault_path,
|
|
94
94
|
template=manager_wrapper._config.template,
|
|
95
|
+
compression_threshold=manager_wrapper._config.compression_threshold,
|
|
96
|
+
compression_passive_detection=manager_wrapper._config.compression_passive_detection,
|
|
95
97
|
)
|
|
96
98
|
agent.llm = TheseusChatModel(
|
|
97
99
|
provider=manager_wrapper._provider,
|
|
@@ -134,6 +136,8 @@ class TheseusCrewMiddleware:
|
|
|
134
136
|
parent_kernel=manager_wrapper.kernel,
|
|
135
137
|
vault_path=manager_wrapper._config.vault_path,
|
|
136
138
|
template=manager_wrapper._config.template,
|
|
139
|
+
compression_threshold=manager_wrapper._config.compression_threshold,
|
|
140
|
+
compression_passive_detection=manager_wrapper._config.compression_passive_detection,
|
|
137
141
|
)
|
|
138
142
|
agent.llm = TheseusChatModel(
|
|
139
143
|
provider=manager_wrapper._provider,
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ct_toolkit.middleware.deepagents
|
|
3
|
+
---------------------------------
|
|
4
|
+
Specialized integration for LangChain Deep Agents (deepagents).
|
|
5
|
+
|
|
6
|
+
This module provides helpers to wrap Deep Agents with CT Toolkit's
|
|
7
|
+
identity-continuity guardrails, specifically ensuring that when a
|
|
8
|
+
Deep Agent spawns sub-agents, they inherit the mother agent's
|
|
9
|
+
Constitutional Kernel.
|
|
10
|
+
"""
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from typing import Any, Dict, List, Optional, Union, Callable
|
|
14
|
+
from ct_toolkit.core.wrapper import TheseusWrapper, WrapperConfig
|
|
15
|
+
from ct_toolkit.core.compression_guard import ContextCompressionGuard
|
|
16
|
+
from ct_toolkit.middleware.langchain import TheseusChatModel
|
|
17
|
+
from ct_toolkit.utils.logger import get_logger
|
|
18
|
+
|
|
19
|
+
logger = get_logger(__name__)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def wrap_deep_agent_factory(
|
|
24
|
+
create_deep_agent_fn: Callable,
|
|
25
|
+
wrapper: Optional[TheseusWrapper] = None,
|
|
26
|
+
wrapper_config: Optional[WrapperConfig] = None,
|
|
27
|
+
compression_threshold: float = 0.85,
|
|
28
|
+
) -> Callable:
|
|
29
|
+
"""
|
|
30
|
+
Wraps the LangChain `create_deep_agent` function to automatically
|
|
31
|
+
inject Theseus guardrails, enable hierarchical kernel propagation,
|
|
32
|
+
and monitor context compression drift.
|
|
33
|
+
"""
|
|
34
|
+
_wrapper = wrapper or TheseusWrapper(config=wrapper_config)
|
|
35
|
+
guard = ContextCompressionGuard(_wrapper, threshold=compression_threshold)
|
|
36
|
+
|
|
37
|
+
def wrapped_create_deep_agent(*args: Any, **kwargs: Any) -> Any:
|
|
38
|
+
# 1. Inject TheseusChatModel
|
|
39
|
+
model = kwargs.get("model")
|
|
40
|
+
if isinstance(model, str) or model is None:
|
|
41
|
+
kwargs["model"] = TheseusChatModel(
|
|
42
|
+
wrapper=_wrapper,
|
|
43
|
+
model=model if isinstance(model, str) else "gpt-4o-mini"
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
# 2. Inject ContextCompressionGuard into the middleware stack if possible
|
|
47
|
+
# Since deepagents middleware is internal, we use the helper to signal it
|
|
48
|
+
# In a real integration, we'd wrap the SummarizationMiddleware.
|
|
49
|
+
|
|
50
|
+
return create_deep_agent_fn(*args, **kwargs)
|
|
51
|
+
|
|
52
|
+
return wrapped_create_deep_agent
|
|
53
|
+
|
|
54
|
+
class DeepAgentTheseusHelper:
|
|
55
|
+
"""
|
|
56
|
+
Helper for programmatic integration with Deep Agents.
|
|
57
|
+
"""
|
|
58
|
+
@staticmethod
|
|
59
|
+
def prepare_config(wrapper: TheseusWrapper) -> Dict[str, Any]:
|
|
60
|
+
"""Returns configuration for Deep Agents that includes identity constraints."""
|
|
61
|
+
return {
|
|
62
|
+
"callbacks": [], # We could add TheseusLangChainCallback here
|
|
63
|
+
"metadata": {
|
|
64
|
+
"ct_kernel": wrapper.kernel.name,
|
|
65
|
+
"ct_identity_protection": True
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -204,6 +204,11 @@ class TheseusChatModel(BaseChatModel):
|
|
|
204
204
|
)
|
|
205
205
|
super().__init__(wrapper=_wrapper, model=model, **kwargs)
|
|
206
206
|
|
|
207
|
+
@property
|
|
208
|
+
def compression_guard(self) -> Any:
|
|
209
|
+
"""Access the underlying ContextCompressionGuard."""
|
|
210
|
+
return self.wrapper._compression_guard
|
|
211
|
+
|
|
207
212
|
@property
|
|
208
213
|
def _llm_type(self) -> str:
|
|
209
214
|
return "theseus-chat-model"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ct-toolkit
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.6
|
|
4
4
|
Summary: Computational Theseus Toolkit — Identity Continuity Guardrails for Agentic Systems
|
|
5
5
|
Author: Hakan Damar
|
|
6
6
|
License: Apache-2.0
|
|
@@ -18,6 +18,8 @@ Requires-Dist: SQLAlchemy>=2.0.0
|
|
|
18
18
|
Requires-Dist: httpx>=0.27.0
|
|
19
19
|
Requires-Dist: rich>=13.0.0
|
|
20
20
|
Requires-Dist: jinja2>=3.1.0
|
|
21
|
+
Requires-Dist: pyasn1>=0.6.3
|
|
22
|
+
Requires-Dist: typer[all]>=0.12.0
|
|
21
23
|
Provides-Extra: ollama
|
|
22
24
|
Requires-Dist: ollama>=0.2.0; extra == "ollama"
|
|
23
25
|
Provides-Extra: langchain
|
|
@@ -63,7 +65,9 @@ For full API reference, architecture details, examples, and integration guides,
|
|
|
63
65
|
|
|
64
66
|
In complex agentic workflows, LLMs tend to "drift" from their original instructions. CT Toolkit provides the mathematical and cryptographic guardrails to ensure your agents remain aligned with their core constitution, even across deep hierarchies.
|
|
65
67
|
|
|
68
|
+
- **Passive Context Compression Detection**: Automatically detects silent provider-side history compression (e.g., OpenAI/Anthropic) via history shrinkage heuristics.
|
|
66
69
|
- **Constitutional Kernels**: Axiomatic identity anchors.
|
|
70
|
+
- **Standalone Auditor CLI**: Rapidly audit any LLM endpoint for identity drift without writing code.
|
|
67
71
|
- **Autonomous Self-Correction**: Active L2->L1 feedback loop that retries and corrects divergent responses before they reach the user.
|
|
68
72
|
- **Divergence Engine**: Multi-tiered drift analysis (L1/L2/L3).
|
|
69
73
|
- **Hierarchical Propagation**: Mother-to-child constraint inheritance.
|
|
@@ -78,10 +82,13 @@ pip install ct-toolkit
|
|
|
78
82
|
```
|
|
79
83
|
|
|
80
84
|
```python
|
|
81
|
-
from ct_toolkit import TheseusWrapper
|
|
85
|
+
from ct_toolkit import TheseusWrapper, WrapperConfig
|
|
86
|
+
|
|
87
|
+
# Protect against silent provider context compression
|
|
88
|
+
config = WrapperConfig(compression_passive_detection=True)
|
|
82
89
|
|
|
83
90
|
# One-line injection for any LLM provider
|
|
84
|
-
client = TheseusWrapper(provider="openai")
|
|
91
|
+
client = TheseusWrapper(provider="openai", config=config)
|
|
85
92
|
|
|
86
93
|
# Guardrails and drift analysis applied automatically
|
|
87
94
|
response = client.chat("What are your core security axioms?")
|
|
@@ -90,15 +97,28 @@ print(response.content)
|
|
|
90
97
|
print(f"Divergence Score: {response.divergence_score}")
|
|
91
98
|
```
|
|
92
99
|
|
|
100
|
+
### 🔍 Standalone Auditor (CLI)
|
|
101
|
+
|
|
102
|
+
Audit any LLM endpoint (OpenAI, Ollama, LM Studio) directly from your terminal:
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
# Audit a local Ollama model
|
|
106
|
+
ct-toolkit audit --url http://localhost:11434/v1 --kernel defense
|
|
107
|
+
|
|
108
|
+
# List available kernels and templates
|
|
109
|
+
ct-toolkit list-kernels
|
|
110
|
+
ct-toolkit list-templates
|
|
111
|
+
```
|
|
112
|
+
|
|
93
113
|
---
|
|
94
114
|
|
|
95
115
|
## 🚦 Project Health & Status
|
|
96
116
|
|
|
97
117
|
| Metric | Status |
|
|
98
118
|
| :--------------- | :------------------------------------------------------ |
|
|
99
|
-
| **Tests** | ✅
|
|
100
|
-
| **Last Phase** | ✅ Phase
|
|
101
|
-
| **Current Goal** | 🔶 Phase
|
|
119
|
+
| **Tests** | ✅ 269/272 passing (92% coverage) |
|
|
120
|
+
| **Last Phase** | ✅ Phase 6: Auditor Mode (Complete) |
|
|
121
|
+
| **Current Goal** | 🔶 Phase 7: Multi-Agent Synchronization (Integration) |
|
|
102
122
|
|
|
103
123
|
For a detailed breakdown of the 8-phase roadmap, see [**PROJECT_STATUS.md**](docs/PROJECT_STATUS.md).
|
|
104
124
|
|
|
@@ -2,13 +2,17 @@ LICENSE
|
|
|
2
2
|
README.md
|
|
3
3
|
pyproject.toml
|
|
4
4
|
ct_toolkit/__init__.py
|
|
5
|
+
ct_toolkit/__main__.py
|
|
6
|
+
ct_toolkit/cli.py
|
|
5
7
|
ct_toolkit.egg-info/PKG-INFO
|
|
6
8
|
ct_toolkit.egg-info/SOURCES.txt
|
|
7
9
|
ct_toolkit.egg-info/dependency_links.txt
|
|
10
|
+
ct_toolkit.egg-info/entry_points.txt
|
|
8
11
|
ct_toolkit.egg-info/requires.txt
|
|
9
12
|
ct_toolkit.egg-info/top_level.txt
|
|
10
13
|
ct_toolkit/core/__init__.py
|
|
11
14
|
ct_toolkit/core/compatibility.py
|
|
15
|
+
ct_toolkit/core/compression_guard.py
|
|
12
16
|
ct_toolkit/core/exceptions.py
|
|
13
17
|
ct_toolkit/core/integrity.py
|
|
14
18
|
ct_toolkit/core/kernel.py
|
|
@@ -44,6 +48,8 @@ ct_toolkit/provenance/vault/__init__.py
|
|
|
44
48
|
ct_toolkit/utils/__init__.py
|
|
45
49
|
ct_toolkit/utils/logger.py
|
|
46
50
|
tests/test_analysis.py
|
|
51
|
+
tests/test_cli.py
|
|
52
|
+
tests/test_cli_extended.py
|
|
47
53
|
tests/test_deep_agents_integration.py
|
|
48
54
|
tests/test_loss.py
|
|
49
55
|
tests/test_summarization_drift.py
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "ct-toolkit"
|
|
7
|
-
version = "0.3.
|
|
7
|
+
version = "0.3.6"
|
|
8
8
|
description = "Computational Theseus Toolkit — Identity Continuity Guardrails for Agentic Systems"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
@@ -23,6 +23,8 @@ dependencies = [
|
|
|
23
23
|
"httpx>=0.27.0",
|
|
24
24
|
"rich>=13.0.0", # For CLI reporting
|
|
25
25
|
"jinja2>=3.1.0",
|
|
26
|
+
"pyasn1>=0.6.3",
|
|
27
|
+
"typer[all]>=0.12.0",
|
|
26
28
|
]
|
|
27
29
|
|
|
28
30
|
[project.optional-dependencies]
|
|
@@ -63,6 +65,9 @@ include = ["ct_toolkit*"]
|
|
|
63
65
|
"ct_toolkit.kernels" = ["*.yaml"]
|
|
64
66
|
"ct_toolkit.identity.templates" = ["*.yaml"]
|
|
65
67
|
|
|
68
|
+
[project.scripts]
|
|
69
|
+
ct-toolkit = "ct_toolkit.cli:app"
|
|
70
|
+
|
|
66
71
|
[tool.pytest.ini_options]
|
|
67
72
|
extraPaths = ["."]
|
|
68
73
|
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from typer.testing import CliRunner
|
|
3
|
+
from ct_toolkit.cli import app
|
|
4
|
+
from ct_toolkit import __version__
|
|
5
|
+
|
|
6
|
+
runner = CliRunner()
|
|
7
|
+
|
|
8
|
+
def test_cli_help():
|
|
9
|
+
"""Test standard help output."""
|
|
10
|
+
result = runner.invoke(app, ["--help"])
|
|
11
|
+
assert result.exit_code == 0
|
|
12
|
+
assert "Computational Theseus Toolkit" in result.stdout
|
|
13
|
+
assert "audit" in result.stdout
|
|
14
|
+
|
|
15
|
+
def test_cli_version():
|
|
16
|
+
"""Test --version flag."""
|
|
17
|
+
result = runner.invoke(app, ["--version"])
|
|
18
|
+
assert result.exit_code == 0
|
|
19
|
+
assert f"CT Toolkit v{__version__}" in result.stdout
|
|
20
|
+
|
|
21
|
+
def test_cli_list_kernels():
|
|
22
|
+
"""Test list-kernels command."""
|
|
23
|
+
result = runner.invoke(app, ["list-kernels"])
|
|
24
|
+
assert result.exit_code == 0
|
|
25
|
+
assert "Available Kernels" in result.stdout
|
|
26
|
+
# Check for a character that is definitely in the banner
|
|
27
|
+
assert "____" in result.stdout
|
|
28
|
+
|
|
29
|
+
def test_cli_list_templates():
|
|
30
|
+
"""Test list-templates command."""
|
|
31
|
+
result = runner.invoke(app, ["list-templates"])
|
|
32
|
+
assert result.exit_code == 0
|
|
33
|
+
assert "Available Templates" in result.stdout
|
|
34
|
+
assert "general" in result.stdout
|
|
35
|
+
|
|
36
|
+
def test_cli_audit_invalid_url():
|
|
37
|
+
"""Test audit command with an unreachable URL (should fail gracefully)."""
|
|
38
|
+
result = runner.invoke(app, ["audit", "--url", "http://invalid-url-that-does-not-exist:1234", "--kernel", "default"])
|
|
39
|
+
|
|
40
|
+
# Check that it either failed with error or finished with 0%
|
|
41
|
+
output = result.stdout.lower()
|
|
42
|
+
assert (result.exit_code == 1) or (result.exit_code == 0 and "0.0%" in output)
|
|
43
|
+
# At least some indicator of failure or starting should be there
|
|
44
|
+
assert "starting audit" in output
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from typer.testing import CliRunner
|
|
3
|
+
from ct_toolkit.cli import app
|
|
4
|
+
from unittest.mock import MagicMock, patch
|
|
5
|
+
|
|
6
|
+
runner = CliRunner()
|
|
7
|
+
|
|
8
|
+
def test_cli_version():
|
|
9
|
+
result = runner.invoke(app, ["--version"])
|
|
10
|
+
assert result.exit_code == 0
|
|
11
|
+
assert "CT Toolkit v" in result.stdout
|
|
12
|
+
|
|
13
|
+
def test_cli_version_short():
|
|
14
|
+
result = runner.invoke(app, ["-v"])
|
|
15
|
+
assert result.exit_code == 0
|
|
16
|
+
assert "CT Toolkit v" in result.stdout
|
|
17
|
+
|
|
18
|
+
@patch("ct_toolkit.cli.TheseusWrapper")
|
|
19
|
+
def test_cli_audit_init_fail(mock_wrapper):
|
|
20
|
+
mock_wrapper.side_effect = Exception("Init failed")
|
|
21
|
+
result = runner.invoke(app, ["audit", "--url", "http://test", "--api-key", "test"])
|
|
22
|
+
assert result.exit_code == 1
|
|
23
|
+
assert "Initialization failed" in result.stdout
|
|
24
|
+
|
|
25
|
+
@patch("ct_toolkit.cli.TheseusWrapper")
|
|
26
|
+
@patch("ct_toolkit.cli.ICMRunner")
|
|
27
|
+
def test_cli_audit_no_probes(mock_icm, mock_wrapper):
|
|
28
|
+
mock_report = MagicMock()
|
|
29
|
+
mock_report.total_probes = 0
|
|
30
|
+
mock_icm.return_value.run.return_value = mock_report
|
|
31
|
+
|
|
32
|
+
result = runner.invoke(app, ["audit", "--url", "http://test", "--api-key", "test"])
|
|
33
|
+
assert result.exit_code == 1
|
|
34
|
+
assert "No probes were loaded" in result.stdout
|
|
35
|
+
|
|
36
|
+
@patch("ct_toolkit.cli.TheseusWrapper")
|
|
37
|
+
@patch("ct_toolkit.cli.ICMRunner")
|
|
38
|
+
def test_cli_audit_execution_fail(mock_icm, mock_wrapper):
|
|
39
|
+
mock_icm.return_value.run.side_effect = Exception("Run failed")
|
|
40
|
+
|
|
41
|
+
result = runner.invoke(app, ["audit", "--url", "http://test", "--api-key", "test"])
|
|
42
|
+
assert result.exit_code == 1
|
|
43
|
+
assert "Audit failed during execution" in result.stdout
|
|
44
|
+
|
|
45
|
+
def test_cli_list_kernels():
|
|
46
|
+
result = runner.invoke(app, ["list-kernels"])
|
|
47
|
+
assert result.exit_code == 0
|
|
48
|
+
assert "Available Kernels" in result.stdout
|
|
49
|
+
|
|
50
|
+
def test_cli_list_templates():
|
|
51
|
+
result = runner.invoke(app, ["list-templates"])
|
|
52
|
+
assert result.exit_code == 0
|
|
53
|
+
assert "Available Templates" in result.stdout
|
|
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
|