ct-toolkit 0.2.6__tar.gz → 0.2.8__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.6 → ct_toolkit-0.2.8}/PKG-INFO +1 -1
- {ct_toolkit-0.2.6 → ct_toolkit-0.2.8}/ct_toolkit/core/exceptions.py +15 -0
- ct_toolkit-0.2.8/ct_toolkit/core/integrity.py +56 -0
- {ct_toolkit-0.2.6 → ct_toolkit-0.2.8}/ct_toolkit/core/wrapper.py +96 -5
- {ct_toolkit-0.2.6 → ct_toolkit-0.2.8}/ct_toolkit.egg-info/PKG-INFO +1 -1
- {ct_toolkit-0.2.6 → ct_toolkit-0.2.8}/ct_toolkit.egg-info/SOURCES.txt +1 -0
- {ct_toolkit-0.2.6 → ct_toolkit-0.2.8}/pyproject.toml +7 -1
- {ct_toolkit-0.2.6 → ct_toolkit-0.2.8}/LICENSE +0 -0
- {ct_toolkit-0.2.6 → ct_toolkit-0.2.8}/README.md +0 -0
- {ct_toolkit-0.2.6 → ct_toolkit-0.2.8}/ct_toolkit/__init__.py +0 -0
- {ct_toolkit-0.2.6 → ct_toolkit-0.2.8}/ct_toolkit/core/__init__.py +0 -0
- {ct_toolkit-0.2.6 → ct_toolkit-0.2.8}/ct_toolkit/core/compatibility.py +0 -0
- {ct_toolkit-0.2.6 → ct_toolkit-0.2.8}/ct_toolkit/core/kernel.py +0 -0
- {ct_toolkit-0.2.6 → ct_toolkit-0.2.8}/ct_toolkit/divergence/__init__.py +0 -0
- {ct_toolkit-0.2.6 → ct_toolkit-0.2.8}/ct_toolkit/divergence/analysis.py +0 -0
- {ct_toolkit-0.2.6 → ct_toolkit-0.2.8}/ct_toolkit/divergence/engine.py +0 -0
- {ct_toolkit-0.2.6 → ct_toolkit-0.2.8}/ct_toolkit/divergence/l2_judge.py +0 -0
- {ct_toolkit-0.2.6 → ct_toolkit-0.2.8}/ct_toolkit/divergence/l3_icm.py +0 -0
- {ct_toolkit-0.2.6 → ct_toolkit-0.2.8}/ct_toolkit/divergence/loss.py +0 -0
- {ct_toolkit-0.2.6 → ct_toolkit-0.2.8}/ct_toolkit/divergence/scheduler.py +0 -0
- {ct_toolkit-0.2.6 → ct_toolkit-0.2.8}/ct_toolkit/endorsement/__init__.py +0 -0
- {ct_toolkit-0.2.6 → ct_toolkit-0.2.8}/ct_toolkit/endorsement/probes/__init__.py +0 -0
- {ct_toolkit-0.2.6 → ct_toolkit-0.2.8}/ct_toolkit/endorsement/reflective.py +0 -0
- {ct_toolkit-0.2.6 → ct_toolkit-0.2.8}/ct_toolkit/identity/__init__.py +0 -0
- {ct_toolkit-0.2.6 → ct_toolkit-0.2.8}/ct_toolkit/identity/embedding.py +0 -0
- {ct_toolkit-0.2.6 → ct_toolkit-0.2.8}/ct_toolkit/identity/templates/__init__.py +0 -0
- {ct_toolkit-0.2.6 → ct_toolkit-0.2.8}/ct_toolkit/identity/templates/defense.yaml +0 -0
- {ct_toolkit-0.2.6 → ct_toolkit-0.2.8}/ct_toolkit/identity/templates/finance.yaml +0 -0
- {ct_toolkit-0.2.6 → ct_toolkit-0.2.8}/ct_toolkit/identity/templates/general.yaml +0 -0
- {ct_toolkit-0.2.6 → ct_toolkit-0.2.8}/ct_toolkit/identity/templates/medical.yaml +0 -0
- {ct_toolkit-0.2.6 → ct_toolkit-0.2.8}/ct_toolkit/kernels/__init__.py +0 -0
- {ct_toolkit-0.2.6 → ct_toolkit-0.2.8}/ct_toolkit/kernels/default.yaml +0 -0
- {ct_toolkit-0.2.6 → ct_toolkit-0.2.8}/ct_toolkit/kernels/defense.yaml +0 -0
- {ct_toolkit-0.2.6 → ct_toolkit-0.2.8}/ct_toolkit/middleware/__init__.py +0 -0
- {ct_toolkit-0.2.6 → ct_toolkit-0.2.8}/ct_toolkit/middleware/autogen.py +0 -0
- {ct_toolkit-0.2.6 → ct_toolkit-0.2.8}/ct_toolkit/middleware/crewai.py +0 -0
- {ct_toolkit-0.2.6 → ct_toolkit-0.2.8}/ct_toolkit/middleware/deepagents.py +0 -0
- {ct_toolkit-0.2.6 → ct_toolkit-0.2.8}/ct_toolkit/middleware/langchain.py +0 -0
- {ct_toolkit-0.2.6 → ct_toolkit-0.2.8}/ct_toolkit/provenance/__init__.py +0 -0
- {ct_toolkit-0.2.6 → ct_toolkit-0.2.8}/ct_toolkit/provenance/log.py +0 -0
- {ct_toolkit-0.2.6 → ct_toolkit-0.2.8}/ct_toolkit/provenance/vault/__init__.py +0 -0
- {ct_toolkit-0.2.6 → ct_toolkit-0.2.8}/ct_toolkit/utils/__init__.py +0 -0
- {ct_toolkit-0.2.6 → ct_toolkit-0.2.8}/ct_toolkit/utils/logger.py +0 -0
- {ct_toolkit-0.2.6 → ct_toolkit-0.2.8}/ct_toolkit.egg-info/dependency_links.txt +0 -0
- {ct_toolkit-0.2.6 → ct_toolkit-0.2.8}/ct_toolkit.egg-info/requires.txt +0 -0
- {ct_toolkit-0.2.6 → ct_toolkit-0.2.8}/ct_toolkit.egg-info/top_level.txt +0 -0
- {ct_toolkit-0.2.6 → ct_toolkit-0.2.8}/setup.cfg +0 -0
- {ct_toolkit-0.2.6 → ct_toolkit-0.2.8}/tests/test_analysis.py +0 -0
- {ct_toolkit-0.2.6 → ct_toolkit-0.2.8}/tests/test_deep_agents_integration.py +0 -0
- {ct_toolkit-0.2.6 → ct_toolkit-0.2.8}/tests/test_loss.py +0 -0
- {ct_toolkit-0.2.6 → ct_toolkit-0.2.8}/tests/test_summarization_drift.py +0 -0
|
@@ -103,3 +103,18 @@ class ChainIntegrityError(ProvenanceError):
|
|
|
103
103
|
|
|
104
104
|
class VaultError(ProvenanceError):
|
|
105
105
|
"""Vault connection or authorization error."""
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
# ── File Integrity Errors ────────────────────────────────────────────────────
|
|
109
|
+
|
|
110
|
+
class ConfigurationTamperingError(CTToolkitError):
|
|
111
|
+
"""
|
|
112
|
+
Raised when a configuration file has been modified after initialization.
|
|
113
|
+
Indicates a potential security breach or unintended change.
|
|
114
|
+
"""
|
|
115
|
+
def __init__(self, file_path: str):
|
|
116
|
+
self.file_path = file_path
|
|
117
|
+
super().__init__(
|
|
118
|
+
f"SECURITY ALERT: Tampering detected in configuration file: {file_path}. "
|
|
119
|
+
"Execution halted to prevent security bypass."
|
|
120
|
+
)
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ct_toolkit.core.integrity
|
|
3
|
+
-------------------------
|
|
4
|
+
Cryptographic integrity monitoring for configuration files.
|
|
5
|
+
"""
|
|
6
|
+
import hashlib
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from ct_toolkit.utils.logger import get_logger
|
|
9
|
+
from ct_toolkit.core.exceptions import ConfigurationTamperingError
|
|
10
|
+
|
|
11
|
+
logger = get_logger(__name__)
|
|
12
|
+
|
|
13
|
+
class IntegrityMonitor:
|
|
14
|
+
"""
|
|
15
|
+
Monitors the cryptographic integrity of CT configuration files.
|
|
16
|
+
Prevents runtime tampering by agents or malicious actors.
|
|
17
|
+
"""
|
|
18
|
+
def __init__(self):
|
|
19
|
+
self._file_hashes: dict[str, str] = {}
|
|
20
|
+
|
|
21
|
+
def _calculate_hash(self, file_path: Path) -> str:
|
|
22
|
+
"""Calculates the SHA-256 hash of a given file."""
|
|
23
|
+
hasher = hashlib.sha256()
|
|
24
|
+
try:
|
|
25
|
+
with open(file_path, 'rb') as f:
|
|
26
|
+
buf = f.read()
|
|
27
|
+
hasher.update(buf)
|
|
28
|
+
return hasher.hexdigest()
|
|
29
|
+
except FileNotFoundError:
|
|
30
|
+
logger.error(f"Integrity check failed: File not found at {file_path}")
|
|
31
|
+
raise
|
|
32
|
+
|
|
33
|
+
def register_file(self, file_path: Path) -> None:
|
|
34
|
+
"""Registers a file and stores its initial hash."""
|
|
35
|
+
if not file_path.is_file():
|
|
36
|
+
logger.warning(f"Integrity check: Cannot register non-file path: {file_path}")
|
|
37
|
+
return
|
|
38
|
+
|
|
39
|
+
file_hash = self._calculate_hash(file_path)
|
|
40
|
+
self._file_hashes[str(file_path)] = file_hash
|
|
41
|
+
logger.debug(f"Registered integrity hash for {file_path.name}")
|
|
42
|
+
|
|
43
|
+
def verify_integrity(self) -> None:
|
|
44
|
+
"""
|
|
45
|
+
Verifies that none of the registered files have been modified.
|
|
46
|
+
Raises ConfigurationTamperingError if a mismatch is found.
|
|
47
|
+
"""
|
|
48
|
+
if not self._file_hashes:
|
|
49
|
+
return # Nothing to verify
|
|
50
|
+
|
|
51
|
+
for path_str, original_hash in self._file_hashes.items():
|
|
52
|
+
current_hash = self._calculate_hash(Path(path_str))
|
|
53
|
+
if current_hash != original_hash:
|
|
54
|
+
raise ConfigurationTamperingError(path_str)
|
|
55
|
+
logger.debug("Integrity verification passed for all registered files.")
|
|
56
|
+
|
|
@@ -21,15 +21,18 @@ from __future__ import annotations
|
|
|
21
21
|
|
|
22
22
|
import time
|
|
23
23
|
import hashlib
|
|
24
|
+
import os
|
|
24
25
|
from dataclasses import dataclass, field
|
|
25
26
|
from pathlib import Path
|
|
26
27
|
from typing import Any, Iterator
|
|
27
28
|
import any_llm
|
|
28
29
|
from any_llm import AnyLLM
|
|
30
|
+
import glob
|
|
29
31
|
|
|
30
32
|
from ct_toolkit.core.kernel import ConstitutionalKernel
|
|
31
33
|
from ct_toolkit.core.compatibility import CompatibilityLayer, CompatibilityResult
|
|
32
34
|
from ct_toolkit.core.exceptions import CTToolkitError, MissingClientError
|
|
35
|
+
from ct_toolkit.core.integrity import IntegrityMonitor
|
|
33
36
|
from ct_toolkit.divergence.scheduler import RiskProfile
|
|
34
37
|
from ct_toolkit.utils.logger import get_logger
|
|
35
38
|
|
|
@@ -39,6 +42,7 @@ logger = get_logger(__name__)
|
|
|
39
42
|
@dataclass
|
|
40
43
|
class WrapperConfig:
|
|
41
44
|
"""TheseusWrapper configuration."""
|
|
45
|
+
project_root: str | Path | None = None
|
|
42
46
|
kernel_path: str | Path | None = None
|
|
43
47
|
template: str = "general"
|
|
44
48
|
kernel_name: str = "default"
|
|
@@ -108,6 +112,7 @@ class TheseusWrapper:
|
|
|
108
112
|
kernel_path: str | Path | None = None,
|
|
109
113
|
template: str = "general",
|
|
110
114
|
kernel_name: str = "default",
|
|
115
|
+
project_root: str | Path | None = None,
|
|
111
116
|
) -> None:
|
|
112
117
|
"""
|
|
113
118
|
Initializes TheseusWrapper.
|
|
@@ -118,11 +123,14 @@ class TheseusWrapper:
|
|
|
118
123
|
config: Optional WrapperConfig instance.
|
|
119
124
|
provider: Provider name (e.g. "openai", "anthropic", "ollama", "google", "cohere").
|
|
120
125
|
If provided, 'any-llm' will be used to initialize the client internally.
|
|
126
|
+
project_root: Path to the user's project root directory. Used to find a custom 'config/' folder.
|
|
127
|
+
Defaults to the current working directory.
|
|
121
128
|
"""
|
|
122
129
|
self._config = config or WrapperConfig(
|
|
123
130
|
kernel_path=kernel_path,
|
|
124
131
|
template=template,
|
|
125
132
|
kernel_name=kernel_name,
|
|
133
|
+
project_root=project_root,
|
|
126
134
|
)
|
|
127
135
|
|
|
128
136
|
if client is None and provider is None:
|
|
@@ -131,6 +139,11 @@ class TheseusWrapper:
|
|
|
131
139
|
|
|
132
140
|
self._client = client
|
|
133
141
|
self._provider = provider or self._detect_provider(client)
|
|
142
|
+
self._project_root = (
|
|
143
|
+
Path(self._config.project_root)
|
|
144
|
+
if self._config.project_root
|
|
145
|
+
else Path(os.getcwd())
|
|
146
|
+
)
|
|
134
147
|
self._kernel = self._load_kernel()
|
|
135
148
|
|
|
136
149
|
# If a parent kernel exists, merge it into our own kernel as axiomatic constraints
|
|
@@ -146,8 +159,57 @@ class TheseusWrapper:
|
|
|
146
159
|
self._divergence_engine = self._init_divergence_engine()
|
|
147
160
|
self._last_model: str = "unknown"
|
|
148
161
|
|
|
162
|
+
# ── Integrity Monitoring ──
|
|
163
|
+
self._integrity_monitor = IntegrityMonitor()
|
|
164
|
+
self._register_monitored_files()
|
|
165
|
+
|
|
149
166
|
self._log_init()
|
|
150
167
|
|
|
168
|
+
def _register_monitored_files(self):
|
|
169
|
+
"""
|
|
170
|
+
Finds and registers all critical configuration files for integrity monitoring,
|
|
171
|
+
including both built-in package files and user-provided custom configs.
|
|
172
|
+
"""
|
|
173
|
+
logger.debug("Registering configuration files for integrity monitoring.")
|
|
174
|
+
|
|
175
|
+
# 1. Scan built-in package files
|
|
176
|
+
try:
|
|
177
|
+
from importlib.resources import files
|
|
178
|
+
package_root = files("ct_toolkit")
|
|
179
|
+
|
|
180
|
+
internal_patterns = [
|
|
181
|
+
"kernels/**/*.yaml",
|
|
182
|
+
"identity/templates/**/*.yaml",
|
|
183
|
+
"endorsement/probes/**/*.json",
|
|
184
|
+
]
|
|
185
|
+
|
|
186
|
+
for pattern in internal_patterns:
|
|
187
|
+
for file_path in package_root.rglob(pattern):
|
|
188
|
+
if file_path.is_file():
|
|
189
|
+
self._integrity_monitor.register_file(file_path)
|
|
190
|
+
|
|
191
|
+
except (ImportError, ModuleNotFoundError):
|
|
192
|
+
# Fallback for older Python or different environments
|
|
193
|
+
package_root = Path(__file__).parent.parent
|
|
194
|
+
# This is less ideal as it might find non-package files, but it's a
|
|
195
|
+
# reasonable fallback for development environments.
|
|
196
|
+
for pattern in internal_patterns:
|
|
197
|
+
for file_path in package_root.rglob(pattern):
|
|
198
|
+
if file_path.is_file():
|
|
199
|
+
self._integrity_monitor.register_file(file_path)
|
|
200
|
+
|
|
201
|
+
# 2. Scan user's custom config directory
|
|
202
|
+
if self._project_root:
|
|
203
|
+
user_config_dir = self._project_root / "config"
|
|
204
|
+
if user_config_dir.is_dir():
|
|
205
|
+
logger.debug(f"Scanning user config directory for integrity monitoring: {user_config_dir}")
|
|
206
|
+
user_patterns = ["*_kernel.yaml", "*_identity.yaml", "*_probes.json", "*.yaml", "*.json"]
|
|
207
|
+
for pattern in user_patterns:
|
|
208
|
+
for file_path in user_config_dir.rglob(pattern):
|
|
209
|
+
if file_path.is_file():
|
|
210
|
+
self._integrity_monitor.register_file(file_path)
|
|
211
|
+
|
|
212
|
+
|
|
151
213
|
# ── Factory / Init ─────────────────────────────────────────────────────────
|
|
152
214
|
|
|
153
215
|
def _detect_provider(self, client: Any) -> str:
|
|
@@ -180,30 +242,40 @@ class TheseusWrapper:
|
|
|
180
242
|
return "unknown"
|
|
181
243
|
|
|
182
244
|
def _load_kernel(self) -> ConstitutionalKernel:
|
|
245
|
+
# 1. Check for explicit path
|
|
183
246
|
if self._config.kernel_path:
|
|
247
|
+
logger.debug(f"Loading kernel from explicit path: {self._config.kernel_path}")
|
|
184
248
|
return ConstitutionalKernel.from_yaml(self._config.kernel_path)
|
|
185
|
-
|
|
186
|
-
import os
|
|
249
|
+
|
|
187
250
|
safe_name = os.path.basename(self._config.kernel_name)
|
|
188
251
|
|
|
189
|
-
#
|
|
252
|
+
# 2. Check for user-defined config directory
|
|
253
|
+
if self._project_root:
|
|
254
|
+
user_config_path = self._project_root / "config" / f"{safe_name}.yaml"
|
|
255
|
+
if user_config_path.exists():
|
|
256
|
+
logger.debug(f"Loading kernel from user config: {user_config_path}")
|
|
257
|
+
return ConstitutionalKernel.from_yaml(user_config_path)
|
|
258
|
+
|
|
259
|
+
# 3. Load built-in kernel using importlib.resources
|
|
190
260
|
try:
|
|
191
261
|
from importlib.resources import files
|
|
192
262
|
kernel_resource = files("ct_toolkit.kernels").joinpath(f"{safe_name}.yaml")
|
|
193
263
|
if kernel_resource.is_file():
|
|
264
|
+
logger.debug(f"Loading built-in kernel: {safe_name}.yaml")
|
|
194
265
|
return ConstitutionalKernel.from_yaml(kernel_resource)
|
|
195
266
|
except (ImportError, FileNotFoundError):
|
|
196
|
-
# Fallback
|
|
267
|
+
# Fallback for older python versions
|
|
197
268
|
kernel_path = (
|
|
198
269
|
Path(__file__).parent.parent
|
|
199
270
|
/ "kernels"
|
|
200
271
|
/ f"{safe_name}.yaml"
|
|
201
272
|
)
|
|
202
273
|
if kernel_path.exists():
|
|
274
|
+
logger.debug(f"Loading built-in kernel (fallback): {kernel_path}")
|
|
203
275
|
return ConstitutionalKernel.from_yaml(kernel_path)
|
|
204
276
|
|
|
205
277
|
logger.warning(
|
|
206
|
-
f"Kernel '{self._config.kernel_name}' not found
|
|
278
|
+
f"Kernel '{self._config.kernel_name}' not found in user config or built-ins. Using default."
|
|
207
279
|
)
|
|
208
280
|
return ConstitutionalKernel.default()
|
|
209
281
|
|
|
@@ -299,6 +371,9 @@ class TheseusWrapper:
|
|
|
299
371
|
system: Additional system prompt (appended to kernel)
|
|
300
372
|
history: Conversation history [{"role": ..., "content": ...}]
|
|
301
373
|
"""
|
|
374
|
+
# --- Pre-flight Checks ---
|
|
375
|
+
self._integrity_monitor.verify_integrity()
|
|
376
|
+
|
|
302
377
|
composed_system = self._compose_system_prompt(system)
|
|
303
378
|
messages = self._build_messages(message, history, composed_system)
|
|
304
379
|
|
|
@@ -416,6 +491,10 @@ class TheseusWrapper:
|
|
|
416
491
|
if hasattr(self._client, "api_key") and self._client.api_key:
|
|
417
492
|
any_llm_kwargs["api_key"] = self._client.api_key
|
|
418
493
|
|
|
494
|
+
# Transfer common client configurations if available
|
|
495
|
+
if hasattr(self._client, "timeout") and "timeout" not in any_llm_kwargs:
|
|
496
|
+
any_llm_kwargs["timeout"] = self._client.timeout
|
|
497
|
+
|
|
419
498
|
try:
|
|
420
499
|
return any_llm.completion(**any_llm_kwargs)
|
|
421
500
|
except Exception as e:
|
|
@@ -473,6 +552,16 @@ class TheseusWrapper:
|
|
|
473
552
|
return messages
|
|
474
553
|
|
|
475
554
|
def _extract_content(self, raw: Any) -> str:
|
|
555
|
+
# Handle dict responses (e.g. from some mockers or raw APIs)
|
|
556
|
+
if isinstance(raw, dict):
|
|
557
|
+
if "choices" in raw and isinstance(raw["choices"], list) and raw["choices"]:
|
|
558
|
+
# OpenAI dict format
|
|
559
|
+
return raw["choices"][0].get("message", {}).get("content", "") or ""
|
|
560
|
+
if "message" in raw:
|
|
561
|
+
# Ollama dict format
|
|
562
|
+
return raw["message"].get("content", "") or ""
|
|
563
|
+
return str(raw)
|
|
564
|
+
|
|
476
565
|
# OpenAI
|
|
477
566
|
if hasattr(raw, "choices"):
|
|
478
567
|
return raw.choices[0].message.content or ""
|
|
@@ -485,6 +574,8 @@ class TheseusWrapper:
|
|
|
485
574
|
return str(raw)
|
|
486
575
|
|
|
487
576
|
def _extract_model(self, raw: Any, fallback: str | None) -> str:
|
|
577
|
+
if isinstance(raw, dict) and "model" in raw:
|
|
578
|
+
return raw["model"]
|
|
488
579
|
if hasattr(raw, "model"):
|
|
489
580
|
return raw.model
|
|
490
581
|
return fallback or "unknown"
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "ct-toolkit"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.8"
|
|
8
8
|
description = "Computational Theseus Toolkit — Identity Continuity Guardrails for Agentic Systems"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
@@ -71,3 +71,9 @@ dev = [
|
|
|
71
71
|
"pytest-cov>=7.0.0",
|
|
72
72
|
"twine>=6.2.0",
|
|
73
73
|
]
|
|
74
|
+
|
|
75
|
+
[tool.semantic_release]
|
|
76
|
+
version_toml = ["pyproject.toml:project.version"]
|
|
77
|
+
branch = "main"
|
|
78
|
+
changelog_file = "CHANGELOG.md"
|
|
79
|
+
build_command = "pip install build && python -m build"
|
|
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
|