coreason-manifest 0.2.0__py3-none-any.whl → 0.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- coreason_manifest/__init__.py +19 -1
- coreason_manifest/engine.py +143 -38
- coreason_manifest/errors.py +28 -3
- coreason_manifest/integrity.py +12 -7
- coreason_manifest/loader.py +55 -22
- coreason_manifest/main.py +10 -9
- coreason_manifest/models.py +73 -9
- coreason_manifest/policy.py +15 -9
- coreason_manifest/schemas/agent.schema.json +6 -6
- coreason_manifest/server.py +123 -0
- coreason_manifest/validator.py +17 -6
- {coreason_manifest-0.2.0.dist-info → coreason_manifest-0.4.0.dist-info}/METADATA +53 -27
- coreason_manifest-0.4.0.dist-info/RECORD +21 -0
- {coreason_manifest-0.2.0.dist-info → coreason_manifest-0.4.0.dist-info}/WHEEL +1 -1
- coreason_manifest-0.2.0.dist-info/RECORD +0 -20
- {coreason_manifest-0.2.0.dist-info → coreason_manifest-0.4.0.dist-info}/licenses/LICENSE +0 -0
- {coreason_manifest-0.2.0.dist-info → coreason_manifest-0.4.0.dist-info}/licenses/NOTICE +0 -0
coreason_manifest/__init__.py
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# Prosperity-3.0
|
|
2
|
-
|
|
2
|
+
"""Coreason Manifest Package.
|
|
3
|
+
|
|
4
|
+
This package provides the core functionality for the Coreason Manifest system,
|
|
5
|
+
including loading, validation, policy enforcement, and integrity checking of
|
|
6
|
+
agent definitions.
|
|
7
|
+
|
|
8
|
+
The `coreason-manifest` package serves as the definitive source of truth for
|
|
9
|
+
Asset definitions in the CoReason-AI ecosystem.
|
|
10
|
+
|
|
11
|
+
Usage:
|
|
12
|
+
from coreason_manifest import ManifestEngine, ManifestConfig
|
|
13
|
+
|
|
14
|
+
config = ManifestConfig(policy_path="./policies/gx_compliant.rego")
|
|
15
|
+
engine = ManifestEngine(config)
|
|
16
|
+
agent_def = engine.load_and_validate("agent.yaml", "./src")
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from .engine import ManifestConfig, ManifestEngine, ManifestEngineAsync
|
|
3
20
|
from .errors import (
|
|
4
21
|
IntegrityCompromisedError,
|
|
5
22
|
ManifestError,
|
|
@@ -30,6 +47,7 @@ __all__ = [
|
|
|
30
47
|
"IntegrityCompromisedError",
|
|
31
48
|
"ManifestConfig",
|
|
32
49
|
"ManifestEngine",
|
|
50
|
+
"ManifestEngineAsync",
|
|
33
51
|
"ManifestError",
|
|
34
52
|
"ManifestLoader",
|
|
35
53
|
"ManifestSyntaxError",
|
coreason_manifest/engine.py
CHANGED
|
@@ -1,24 +1,38 @@
|
|
|
1
1
|
# Prosperity-3.0
|
|
2
|
+
"""Engine for the Coreason Manifest system.
|
|
3
|
+
|
|
4
|
+
This module provides the main entry point for verifying and loading Agent Manifests.
|
|
5
|
+
It coordinates schema validation, policy enforcement, and integrity checking.
|
|
6
|
+
"""
|
|
7
|
+
|
|
2
8
|
from __future__ import annotations
|
|
3
9
|
|
|
4
10
|
import time
|
|
5
11
|
from dataclasses import dataclass, field
|
|
6
12
|
from pathlib import Path
|
|
7
|
-
from typing import List, Optional, Union
|
|
13
|
+
from typing import Any, List, Optional, Union, cast
|
|
14
|
+
|
|
15
|
+
import anyio
|
|
16
|
+
import anyio.to_thread
|
|
8
17
|
|
|
9
18
|
from coreason_manifest.integrity import IntegrityChecker
|
|
10
19
|
from coreason_manifest.loader import ManifestLoader
|
|
11
20
|
from coreason_manifest.models import AgentDefinition
|
|
12
21
|
from coreason_manifest.policy import PolicyEnforcer
|
|
13
|
-
|
|
14
|
-
# Import logger from utils to ensure configuration is applied
|
|
15
22
|
from coreason_manifest.utils.logger import logger
|
|
16
23
|
from coreason_manifest.validator import SchemaValidator
|
|
17
24
|
|
|
18
25
|
|
|
19
26
|
@dataclass
|
|
20
27
|
class ManifestConfig:
|
|
21
|
-
"""Configuration for the ManifestEngine.
|
|
28
|
+
"""Configuration for the ManifestEngine.
|
|
29
|
+
|
|
30
|
+
Attributes:
|
|
31
|
+
policy_path: Path to the Rego policy file.
|
|
32
|
+
opa_path: Path to the OPA executable. Defaults to "opa".
|
|
33
|
+
tbom_path: Optional path to the Trusted Bill of Materials.
|
|
34
|
+
extra_data_paths: Additional data paths to load into OPA.
|
|
35
|
+
"""
|
|
22
36
|
|
|
23
37
|
policy_path: Union[str, Path]
|
|
24
38
|
opa_path: str = "opa"
|
|
@@ -26,14 +40,19 @@ class ManifestConfig:
|
|
|
26
40
|
extra_data_paths: List[Union[str, Path]] = field(default_factory=list)
|
|
27
41
|
|
|
28
42
|
|
|
29
|
-
class
|
|
30
|
-
"""
|
|
31
|
-
|
|
43
|
+
class ManifestEngineAsync:
|
|
44
|
+
"""The async core for verifying and loading Agent Manifests.
|
|
45
|
+
|
|
46
|
+
This class coordinates the validation process, including:
|
|
47
|
+
1. Loading raw YAML.
|
|
48
|
+
2. Validating against JSON Schema.
|
|
49
|
+
3. Converting to AgentDefinition Pydantic model (Normalization).
|
|
50
|
+
4. Enforcing Policy (Rego).
|
|
51
|
+
5. Verifying Integrity (Hash check).
|
|
32
52
|
"""
|
|
33
53
|
|
|
34
54
|
def __init__(self, config: ManifestConfig) -> None:
|
|
35
|
-
"""
|
|
36
|
-
Initialize the ManifestEngine.
|
|
55
|
+
"""Initialize the ManifestEngineAsync.
|
|
37
56
|
|
|
38
57
|
Args:
|
|
39
58
|
config: Configuration including policy path and OPA path.
|
|
@@ -52,56 +71,57 @@ class ManifestEngine:
|
|
|
52
71
|
data_paths=data_paths,
|
|
53
72
|
)
|
|
54
73
|
|
|
55
|
-
def
|
|
56
|
-
"""
|
|
57
|
-
|
|
74
|
+
async def __aenter__(self) -> ManifestEngineAsync:
|
|
75
|
+
"""Async context manager entry."""
|
|
76
|
+
return self
|
|
77
|
+
|
|
78
|
+
async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
|
|
79
|
+
"""Async context manager exit."""
|
|
80
|
+
# Clean up resources if necessary.
|
|
81
|
+
pass
|
|
82
|
+
|
|
83
|
+
async def validate_manifest_dict(self, raw_data: dict[str, Any]) -> AgentDefinition:
|
|
84
|
+
"""Validates an Agent Manifest dictionary in memory.
|
|
85
|
+
|
|
86
|
+
Performs:
|
|
87
|
+
1. Normalization (stripping version prefixes)
|
|
88
|
+
2. Schema Validation
|
|
89
|
+
3. Model Conversion
|
|
90
|
+
4. Policy Enforcement
|
|
58
91
|
|
|
59
|
-
|
|
60
|
-
1. Load raw YAML.
|
|
61
|
-
2. Validate against JSON Schema.
|
|
62
|
-
3. Convert to AgentDefinition Pydantic model (Normalization).
|
|
63
|
-
4. Enforce Policy (Rego).
|
|
64
|
-
5. Verify Integrity (Hash check).
|
|
92
|
+
Does NOT perform Integrity Check (hashing).
|
|
65
93
|
|
|
66
94
|
Args:
|
|
67
|
-
|
|
68
|
-
source_dir: Path to the source code directory.
|
|
95
|
+
raw_data: The raw dictionary of the manifest.
|
|
69
96
|
|
|
70
97
|
Returns:
|
|
71
|
-
AgentDefinition: The fully validated
|
|
98
|
+
AgentDefinition: The fully validated agent definition.
|
|
72
99
|
|
|
73
100
|
Raises:
|
|
74
101
|
ManifestSyntaxError: If structure or schema is invalid.
|
|
75
102
|
PolicyViolationError: If business rules are violated.
|
|
76
|
-
IntegrityCompromisedError: If source code hash does not match.
|
|
77
|
-
FileNotFoundError: If files are missing.
|
|
78
103
|
"""
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
logger.info(f"Validating Agent Manifest: {manifest_path}")
|
|
83
|
-
|
|
84
|
-
# 1. Load Raw YAML
|
|
85
|
-
raw_data = ManifestLoader.load_raw_from_file(manifest_path)
|
|
104
|
+
# 1. Normalization (ensure version string is clean before schema/model validation)
|
|
105
|
+
# We access the static method on ManifestLoader.
|
|
106
|
+
ManifestLoader._normalize_data(raw_data)
|
|
86
107
|
|
|
87
108
|
# 2. Schema Validation
|
|
88
109
|
logger.debug("Running Schema Validation...")
|
|
89
110
|
self.schema_validator.validate(raw_data)
|
|
90
111
|
|
|
91
|
-
# 3. Model Conversion (Normalization)
|
|
112
|
+
# 3. Model Conversion (Normalization) (CPU bound)
|
|
92
113
|
logger.debug("Converting to AgentDefinition...")
|
|
93
|
-
agent_def = ManifestLoader.load_from_dict
|
|
114
|
+
agent_def = await anyio.to_thread.run_sync(ManifestLoader.load_from_dict, raw_data)
|
|
94
115
|
logger.info(f"Validating Agent {agent_def.metadata.id} v{agent_def.metadata.version}")
|
|
95
116
|
|
|
96
|
-
# 4. Policy Enforcement
|
|
117
|
+
# 4. Policy Enforcement (Subprocess / Blocking)
|
|
97
118
|
logger.debug("Enforcing Policies...")
|
|
98
119
|
# We assume policy is checked against the Normalized data (model dumped back to dict)
|
|
99
|
-
# or raw data? Standard practice: Check against normalized data to prevent bypasses.
|
|
100
|
-
# dump mode='json' converts UUIDs/Dates to strings which is what OPA expects usually.
|
|
101
120
|
normalized_data = agent_def.model_dump(mode="json")
|
|
102
121
|
start_time = time.perf_counter()
|
|
103
122
|
try:
|
|
104
|
-
|
|
123
|
+
# PolicyEnforcer.evaluate is synchronous and runs subprocess.run, so we wrap it.
|
|
124
|
+
await anyio.to_thread.run_sync(self.policy_enforcer.evaluate, normalized_data)
|
|
105
125
|
duration_ms = (time.perf_counter() - start_time) * 1000
|
|
106
126
|
logger.info(f"Policy Check: Pass - {duration_ms:.2f}ms")
|
|
107
127
|
except Exception:
|
|
@@ -109,9 +129,94 @@ class ManifestEngine:
|
|
|
109
129
|
logger.info(f"Policy Check: Fail - {duration_ms:.2f}ms")
|
|
110
130
|
raise
|
|
111
131
|
|
|
112
|
-
|
|
132
|
+
return cast(AgentDefinition, agent_def)
|
|
133
|
+
|
|
134
|
+
async def load_and_validate(self, manifest_path: Union[str, Path], source_dir: Union[str, Path]) -> AgentDefinition:
|
|
135
|
+
"""Loads, validates, and verifies an Agent Manifest asynchronously.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
manifest_path: Path to the agent.yaml file.
|
|
139
|
+
source_dir: Path to the source code directory.
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
AgentDefinition: The fully validated and verified agent definition.
|
|
143
|
+
|
|
144
|
+
Raises:
|
|
145
|
+
ManifestSyntaxError: If structure or schema is invalid.
|
|
146
|
+
PolicyViolationError: If business rules are violated.
|
|
147
|
+
IntegrityCompromisedError: If source code hash does not match.
|
|
148
|
+
FileNotFoundError: If files are missing.
|
|
149
|
+
"""
|
|
150
|
+
manifest_path = Path(manifest_path)
|
|
151
|
+
source_dir = Path(source_dir)
|
|
152
|
+
|
|
153
|
+
logger.info(f"Validating Agent Manifest: {manifest_path}")
|
|
154
|
+
|
|
155
|
+
# 1. Load Raw YAML (I/O)
|
|
156
|
+
raw_data = await ManifestLoader.load_raw_from_file_async(manifest_path)
|
|
157
|
+
|
|
158
|
+
# 2. Validate Manifest Dict (Schema, Model, Policy)
|
|
159
|
+
agent_def = await self.validate_manifest_dict(raw_data)
|
|
160
|
+
|
|
161
|
+
# 5. Integrity Check (Heavy I/O and CPU)
|
|
113
162
|
logger.debug("Verifying Integrity...")
|
|
114
|
-
IntegrityChecker.verify
|
|
163
|
+
# IntegrityChecker.verify is synchronous and does heavy IO, so we wrap it.
|
|
164
|
+
await anyio.to_thread.run_sync(IntegrityChecker.verify, agent_def, source_dir, manifest_path)
|
|
115
165
|
|
|
116
166
|
logger.info("Agent validation successful.")
|
|
117
167
|
return agent_def
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
class ManifestEngine:
|
|
171
|
+
"""The Sync Facade for ManifestEngineAsync.
|
|
172
|
+
|
|
173
|
+
Allows synchronous usage of the async core via anyio.run.
|
|
174
|
+
"""
|
|
175
|
+
|
|
176
|
+
def __init__(self, config: ManifestConfig) -> None:
|
|
177
|
+
"""Initialize the ManifestEngine facade.
|
|
178
|
+
|
|
179
|
+
Args:
|
|
180
|
+
config: Configuration including policy path and OPA path.
|
|
181
|
+
"""
|
|
182
|
+
self._async = ManifestEngineAsync(config)
|
|
183
|
+
|
|
184
|
+
def __getattr__(self, name: str) -> Any:
|
|
185
|
+
"""Delegate attribute access to the async engine instance.
|
|
186
|
+
|
|
187
|
+
This ensures backward compatibility for accessing attributes like
|
|
188
|
+
'config', 'schema_validator', and 'policy_enforcer'.
|
|
189
|
+
"""
|
|
190
|
+
return getattr(self._async, name)
|
|
191
|
+
|
|
192
|
+
def __enter__(self) -> ManifestEngine:
|
|
193
|
+
"""Context manager entry."""
|
|
194
|
+
anyio.run(self._async.__aenter__)
|
|
195
|
+
return self
|
|
196
|
+
|
|
197
|
+
def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
|
|
198
|
+
"""Context manager exit."""
|
|
199
|
+
anyio.run(self._async.__aexit__, exc_type, exc_val, exc_tb)
|
|
200
|
+
|
|
201
|
+
def load_and_validate(self, manifest_path: Union[str, Path], source_dir: Union[str, Path]) -> AgentDefinition:
|
|
202
|
+
"""Loads, validates, and verifies an Agent Manifest synchronously.
|
|
203
|
+
|
|
204
|
+
Args:
|
|
205
|
+
manifest_path: Path to the agent.yaml file.
|
|
206
|
+
source_dir: Path to the source code directory.
|
|
207
|
+
|
|
208
|
+
Returns:
|
|
209
|
+
AgentDefinition: The fully validated and verified agent definition.
|
|
210
|
+
"""
|
|
211
|
+
return cast(AgentDefinition, anyio.run(self._async.load_and_validate, manifest_path, source_dir))
|
|
212
|
+
|
|
213
|
+
def validate_manifest_dict(self, raw_data: dict[str, Any]) -> AgentDefinition:
|
|
214
|
+
"""Validates an Agent Manifest dictionary synchronously.
|
|
215
|
+
|
|
216
|
+
Args:
|
|
217
|
+
raw_data: The raw dictionary of the manifest.
|
|
218
|
+
|
|
219
|
+
Returns:
|
|
220
|
+
AgentDefinition: The fully validated agent definition.
|
|
221
|
+
"""
|
|
222
|
+
return cast(AgentDefinition, anyio.run(self._async.validate_manifest_dict, raw_data))
|
coreason_manifest/errors.py
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
# Prosperity-3.0
|
|
2
|
+
"""Exceptions for the Coreason Manifest system.
|
|
3
|
+
|
|
4
|
+
This module defines the hierarchy of exceptions raised by the package.
|
|
5
|
+
"""
|
|
6
|
+
|
|
2
7
|
from __future__ import annotations
|
|
3
8
|
|
|
4
9
|
|
|
@@ -9,20 +14,40 @@ class ManifestError(Exception):
|
|
|
9
14
|
|
|
10
15
|
|
|
11
16
|
class ManifestSyntaxError(ManifestError):
|
|
12
|
-
"""Raised when the manifest YAML is invalid or missing required fields.
|
|
17
|
+
"""Raised when the manifest YAML is invalid or missing required fields.
|
|
18
|
+
|
|
19
|
+
This includes YAML parsing errors and JSON Schema validation failures.
|
|
20
|
+
"""
|
|
13
21
|
|
|
14
22
|
pass
|
|
15
23
|
|
|
16
24
|
|
|
17
25
|
class PolicyViolationError(ManifestError):
|
|
18
|
-
"""Raised when the agent violates a compliance policy.
|
|
26
|
+
"""Raised when the agent violates a compliance policy.
|
|
27
|
+
|
|
28
|
+
This error indicates that the manifest is structurally valid but fails
|
|
29
|
+
business rules or compliance checks (e.g., banned libraries).
|
|
30
|
+
|
|
31
|
+
Attributes:
|
|
32
|
+
violations: A list of specific policy violation messages.
|
|
33
|
+
"""
|
|
19
34
|
|
|
20
35
|
def __init__(self, message: str, violations: list[str] | None = None) -> None:
|
|
36
|
+
"""Initialize PolicyViolationError.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
message: The error message.
|
|
40
|
+
violations: Optional list of detailed violation strings.
|
|
41
|
+
"""
|
|
21
42
|
super().__init__(message)
|
|
22
43
|
self.violations = violations or []
|
|
23
44
|
|
|
24
45
|
|
|
25
46
|
class IntegrityCompromisedError(ManifestError):
|
|
26
|
-
"""Raised when the source code hash does not match the manifest.
|
|
47
|
+
"""Raised when the source code hash does not match the manifest.
|
|
48
|
+
|
|
49
|
+
This indicates that the source code may have been tampered with or changed
|
|
50
|
+
without updating the manifest's integrity hash.
|
|
51
|
+
"""
|
|
27
52
|
|
|
28
53
|
pass
|
coreason_manifest/integrity.py
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
# Prosperity-3.0
|
|
2
|
+
"""Integrity checking functionality.
|
|
3
|
+
|
|
4
|
+
This module provides the `IntegrityChecker` class, which is responsible for
|
|
5
|
+
calculating deterministic hashes of source code directories and verifying
|
|
6
|
+
them against the expected hash in the agent manifest.
|
|
7
|
+
"""
|
|
8
|
+
|
|
2
9
|
from __future__ import annotations
|
|
3
10
|
|
|
4
11
|
import hashlib
|
|
@@ -11,8 +18,7 @@ from coreason_manifest.models import AgentDefinition
|
|
|
11
18
|
|
|
12
19
|
|
|
13
20
|
class IntegrityChecker:
|
|
14
|
-
"""
|
|
15
|
-
Component D: IntegrityChecker (The Notary).
|
|
21
|
+
"""Component D: IntegrityChecker (The Notary).
|
|
16
22
|
|
|
17
23
|
Responsibility:
|
|
18
24
|
- Calculate the SHA256 hash of the source code directory.
|
|
@@ -23,8 +29,7 @@ class IntegrityChecker:
|
|
|
23
29
|
|
|
24
30
|
@staticmethod
|
|
25
31
|
def calculate_hash(source_dir: Union[Path, str], exclude_files: Optional[Set[Union[Path, str]]] = None) -> str:
|
|
26
|
-
"""
|
|
27
|
-
Calculates a deterministic SHA256 hash of the source code directory.
|
|
32
|
+
"""Calculates a deterministic SHA256 hash of the source code directory.
|
|
28
33
|
|
|
29
34
|
It walks the directory using os.walk to efficiently prune ignored directories.
|
|
30
35
|
Sorts files by relative path, hashes each file, and then hashes the sequence.
|
|
@@ -34,7 +39,8 @@ class IntegrityChecker:
|
|
|
34
39
|
|
|
35
40
|
Args:
|
|
36
41
|
source_dir: The directory containing source code.
|
|
37
|
-
exclude_files: Optional set of file paths (absolute or relative to CWD)
|
|
42
|
+
exclude_files: Optional set of file paths (absolute or relative to CWD)
|
|
43
|
+
to exclude from hashing.
|
|
38
44
|
|
|
39
45
|
Returns:
|
|
40
46
|
The hex digest of the SHA256 hash.
|
|
@@ -113,8 +119,7 @@ class IntegrityChecker:
|
|
|
113
119
|
source_dir: Union[Path, str],
|
|
114
120
|
manifest_path: Optional[Union[Path, str]] = None,
|
|
115
121
|
) -> None:
|
|
116
|
-
"""
|
|
117
|
-
Verifies the integrity of the source code against the manifest.
|
|
122
|
+
"""Verifies the integrity of the source code against the manifest.
|
|
118
123
|
|
|
119
124
|
Args:
|
|
120
125
|
agent_def: The AgentDefinition containing the expected hash.
|
coreason_manifest/loader.py
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
# Prosperity-3.0
|
|
2
|
+
"""Manifest Loader.
|
|
3
|
+
|
|
4
|
+
This module is responsible for loading the agent manifest from YAML files or
|
|
5
|
+
dictionaries, normalizing the data, and converting it into Pydantic models.
|
|
6
|
+
"""
|
|
7
|
+
|
|
2
8
|
from __future__ import annotations
|
|
3
9
|
|
|
4
10
|
from pathlib import Path
|
|
5
11
|
from typing import Any, Union
|
|
6
12
|
|
|
13
|
+
import aiofiles
|
|
7
14
|
import yaml
|
|
8
15
|
from pydantic import ValidationError
|
|
9
16
|
|
|
@@ -12,8 +19,7 @@ from coreason_manifest.models import AgentDefinition
|
|
|
12
19
|
|
|
13
20
|
|
|
14
21
|
class ManifestLoader:
|
|
15
|
-
"""
|
|
16
|
-
Component A: ManifestLoader (The Parser).
|
|
22
|
+
"""Component A: ManifestLoader (The Parser).
|
|
17
23
|
|
|
18
24
|
Responsibility:
|
|
19
25
|
- Load YAML safely.
|
|
@@ -23,8 +29,7 @@ class ManifestLoader:
|
|
|
23
29
|
|
|
24
30
|
@staticmethod
|
|
25
31
|
def load_raw_from_file(path: Union[str, Path]) -> dict[str, Any]:
|
|
26
|
-
"""
|
|
27
|
-
Loads the raw dict from a YAML file.
|
|
32
|
+
"""Loads the raw dict from a YAML file.
|
|
28
33
|
|
|
29
34
|
Args:
|
|
30
35
|
path: The path to the agent.yaml file.
|
|
@@ -48,15 +53,42 @@ class ManifestLoader:
|
|
|
48
53
|
if not isinstance(data, dict):
|
|
49
54
|
raise ManifestSyntaxError(f"Invalid YAML content in {path}: must be a dictionary.")
|
|
50
55
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
56
|
+
ManifestLoader._normalize_data(data)
|
|
57
|
+
|
|
58
|
+
return data
|
|
59
|
+
|
|
60
|
+
except yaml.YAMLError as e:
|
|
61
|
+
raise ManifestSyntaxError(f"Failed to parse YAML file {path}: {str(e)}") from e
|
|
62
|
+
except OSError as e:
|
|
63
|
+
if isinstance(e, FileNotFoundError):
|
|
64
|
+
raise
|
|
65
|
+
raise ManifestSyntaxError(f"Error reading file {path}: {str(e)}") from e
|
|
66
|
+
|
|
67
|
+
@staticmethod
|
|
68
|
+
async def load_raw_from_file_async(path: Union[str, Path]) -> dict[str, Any]:
|
|
69
|
+
"""Loads the raw dict from a YAML file asynchronously.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
path: The path to the agent.yaml file.
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
dict: The raw dictionary content.
|
|
76
|
+
|
|
77
|
+
Raises:
|
|
78
|
+
ManifestSyntaxError: If YAML is invalid.
|
|
79
|
+
FileNotFoundError: If the file does not exist.
|
|
80
|
+
"""
|
|
81
|
+
try:
|
|
82
|
+
path_obj = Path(path)
|
|
83
|
+
if not path_obj.exists():
|
|
84
|
+
raise FileNotFoundError(f"Manifest file not found: {path}")
|
|
85
|
+
|
|
86
|
+
async with aiofiles.open(path_obj, "r", encoding="utf-8") as f:
|
|
87
|
+
content = await f.read()
|
|
88
|
+
data = yaml.safe_load(content)
|
|
89
|
+
|
|
90
|
+
if not isinstance(data, dict):
|
|
91
|
+
raise ManifestSyntaxError(f"Invalid YAML content in {path}: must be a dictionary.")
|
|
60
92
|
|
|
61
93
|
ManifestLoader._normalize_data(data)
|
|
62
94
|
|
|
@@ -71,8 +103,7 @@ class ManifestLoader:
|
|
|
71
103
|
|
|
72
104
|
@staticmethod
|
|
73
105
|
def load_from_file(path: Union[str, Path]) -> AgentDefinition:
|
|
74
|
-
"""
|
|
75
|
-
Loads the agent manifest from a YAML file.
|
|
106
|
+
"""Loads the agent manifest from a YAML file.
|
|
76
107
|
|
|
77
108
|
Args:
|
|
78
109
|
path: The path to the agent.yaml file.
|
|
@@ -89,8 +120,7 @@ class ManifestLoader:
|
|
|
89
120
|
|
|
90
121
|
@staticmethod
|
|
91
122
|
def load_from_dict(data: dict[str, Any]) -> AgentDefinition:
|
|
92
|
-
"""
|
|
93
|
-
Converts a dictionary into an AgentDefinition model.
|
|
123
|
+
"""Converts a dictionary into an AgentDefinition model.
|
|
94
124
|
|
|
95
125
|
Args:
|
|
96
126
|
data: The raw dictionary.
|
|
@@ -115,11 +145,14 @@ class ManifestLoader:
|
|
|
115
145
|
|
|
116
146
|
@staticmethod
|
|
117
147
|
def _normalize_data(data: dict[str, Any]) -> None:
|
|
118
|
-
"""
|
|
119
|
-
|
|
120
|
-
Specifically strips 'v' or 'V' from version strings.
|
|
148
|
+
"""Normalizes the data dictionary in place.
|
|
149
|
+
|
|
150
|
+
Specifically strips 'v' or 'V' from version strings recursively until clean.
|
|
121
151
|
"""
|
|
122
152
|
if "metadata" in data and isinstance(data["metadata"], dict):
|
|
123
153
|
version = data["metadata"].get("version")
|
|
124
|
-
if isinstance(version, str)
|
|
125
|
-
|
|
154
|
+
if isinstance(version, str):
|
|
155
|
+
# Recursively strip leading 'v' or 'V'
|
|
156
|
+
while version and version[0] in ("v", "V"):
|
|
157
|
+
version = version[1:]
|
|
158
|
+
data["metadata"]["version"] = version
|
coreason_manifest/main.py
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
# For details, see the LICENSE file.
|
|
7
|
-
# Commercial use beyond a 30-day trial requires a separate license.
|
|
8
|
-
#
|
|
9
|
-
# Source Code: https://github.com/CoReason-AI/coreason_manifest
|
|
1
|
+
# Prosperity-3.0
|
|
2
|
+
"""Main module for coreason_manifest.
|
|
3
|
+
|
|
4
|
+
This module is primarily used for testing and demonstration purposes.
|
|
5
|
+
"""
|
|
10
6
|
|
|
11
7
|
from coreason_manifest.utils.logger import logger
|
|
12
8
|
|
|
13
9
|
|
|
14
10
|
def hello_world() -> str:
|
|
11
|
+
"""Returns a hello world string.
|
|
12
|
+
|
|
13
|
+
Returns:
|
|
14
|
+
"Hello World!" string.
|
|
15
|
+
"""
|
|
15
16
|
logger.info("Hello World!")
|
|
16
17
|
return "Hello World!"
|
coreason_manifest/models.py
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
# Prosperity-3.0
|
|
2
|
+
"""Pydantic models for the Coreason Manifest system.
|
|
3
|
+
|
|
4
|
+
These models define the structure and validation rules for the Agent Manifest
|
|
5
|
+
(OAS). They represent the source of truth for Agent definitions.
|
|
6
|
+
"""
|
|
7
|
+
|
|
2
8
|
from __future__ import annotations
|
|
3
9
|
|
|
4
10
|
from datetime import datetime
|
|
@@ -27,7 +33,14 @@ SEMVER_REGEX = (
|
|
|
27
33
|
|
|
28
34
|
|
|
29
35
|
def normalize_version(v: str) -> str:
|
|
30
|
-
"""Normalize version string by recursively stripping 'v' or 'V' prefix.
|
|
36
|
+
"""Normalize version string by recursively stripping 'v' or 'V' prefix.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
v: The version string to normalize.
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
The normalized version string without 'v' prefix.
|
|
43
|
+
"""
|
|
31
44
|
while v.lower().startswith("v"):
|
|
32
45
|
v = v[1:]
|
|
33
46
|
return v
|
|
@@ -56,7 +69,15 @@ StrictUri = Annotated[
|
|
|
56
69
|
|
|
57
70
|
|
|
58
71
|
class AgentMetadata(BaseModel):
|
|
59
|
-
"""Metadata for the Agent.
|
|
72
|
+
"""Metadata for the Agent.
|
|
73
|
+
|
|
74
|
+
Attributes:
|
|
75
|
+
id: Unique Identifier for the Agent (UUID).
|
|
76
|
+
version: Semantic Version of the Agent.
|
|
77
|
+
name: Name of the Agent.
|
|
78
|
+
author: Author of the Agent.
|
|
79
|
+
created_at: Creation timestamp (ISO 8601).
|
|
80
|
+
"""
|
|
60
81
|
|
|
61
82
|
model_config = ConfigDict(extra="forbid", frozen=True)
|
|
62
83
|
|
|
@@ -68,7 +89,12 @@ class AgentMetadata(BaseModel):
|
|
|
68
89
|
|
|
69
90
|
|
|
70
91
|
class AgentInterface(BaseModel):
|
|
71
|
-
"""Interface definition for the Agent.
|
|
92
|
+
"""Interface definition for the Agent.
|
|
93
|
+
|
|
94
|
+
Attributes:
|
|
95
|
+
inputs: Typed arguments the agent accepts (JSON Schema).
|
|
96
|
+
outputs: Typed structure of the result.
|
|
97
|
+
"""
|
|
72
98
|
|
|
73
99
|
model_config = ConfigDict(extra="forbid", frozen=True)
|
|
74
100
|
|
|
@@ -77,7 +103,12 @@ class AgentInterface(BaseModel):
|
|
|
77
103
|
|
|
78
104
|
|
|
79
105
|
class Step(BaseModel):
|
|
80
|
-
"""A single step in the execution graph.
|
|
106
|
+
"""A single step in the execution graph.
|
|
107
|
+
|
|
108
|
+
Attributes:
|
|
109
|
+
id: Unique identifier for the step.
|
|
110
|
+
description: Description of the step.
|
|
111
|
+
"""
|
|
81
112
|
|
|
82
113
|
model_config = ConfigDict(extra="forbid", frozen=True)
|
|
83
114
|
|
|
@@ -86,7 +117,12 @@ class Step(BaseModel):
|
|
|
86
117
|
|
|
87
118
|
|
|
88
119
|
class ModelConfig(BaseModel):
|
|
89
|
-
"""LLM Configuration parameters.
|
|
120
|
+
"""LLM Configuration parameters.
|
|
121
|
+
|
|
122
|
+
Attributes:
|
|
123
|
+
model: The LLM model identifier.
|
|
124
|
+
temperature: Temperature for generation.
|
|
125
|
+
"""
|
|
90
126
|
|
|
91
127
|
model_config = ConfigDict(extra="forbid", frozen=True)
|
|
92
128
|
|
|
@@ -95,7 +131,12 @@ class ModelConfig(BaseModel):
|
|
|
95
131
|
|
|
96
132
|
|
|
97
133
|
class AgentTopology(BaseModel):
|
|
98
|
-
"""Topology of the Agent execution.
|
|
134
|
+
"""Topology of the Agent execution.
|
|
135
|
+
|
|
136
|
+
Attributes:
|
|
137
|
+
steps: A directed acyclic graph (DAG) of execution steps.
|
|
138
|
+
llm_config: Specific LLM parameters.
|
|
139
|
+
"""
|
|
99
140
|
|
|
100
141
|
model_config = ConfigDict(extra="forbid", frozen=True)
|
|
101
142
|
|
|
@@ -105,7 +146,17 @@ class AgentTopology(BaseModel):
|
|
|
105
146
|
@field_validator("steps")
|
|
106
147
|
@classmethod
|
|
107
148
|
def validate_unique_step_ids(cls, v: Tuple[Step, ...]) -> Tuple[Step, ...]:
|
|
108
|
-
"""Ensure all step IDs are unique.
|
|
149
|
+
"""Ensure all step IDs are unique.
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
v: The tuple of steps to validate.
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
The validated tuple of steps.
|
|
156
|
+
|
|
157
|
+
Raises:
|
|
158
|
+
ValueError: If duplicate step IDs are found.
|
|
159
|
+
"""
|
|
109
160
|
ids = [step.id for step in v]
|
|
110
161
|
if len(ids) != len(set(ids)):
|
|
111
162
|
# Find duplicates
|
|
@@ -120,7 +171,12 @@ class AgentTopology(BaseModel):
|
|
|
120
171
|
|
|
121
172
|
|
|
122
173
|
class AgentDependencies(BaseModel):
|
|
123
|
-
"""External dependencies for the Agent.
|
|
174
|
+
"""External dependencies for the Agent.
|
|
175
|
+
|
|
176
|
+
Attributes:
|
|
177
|
+
tools: List of MCP capability URIs required.
|
|
178
|
+
libraries: List of Python packages required (if code execution is allowed).
|
|
179
|
+
"""
|
|
124
180
|
|
|
125
181
|
model_config = ConfigDict(extra="forbid", frozen=True)
|
|
126
182
|
|
|
@@ -133,7 +189,15 @@ class AgentDependencies(BaseModel):
|
|
|
133
189
|
|
|
134
190
|
|
|
135
191
|
class AgentDefinition(BaseModel):
|
|
136
|
-
"""The Root Object for the CoReason Agent Manifest.
|
|
192
|
+
"""The Root Object for the CoReason Agent Manifest.
|
|
193
|
+
|
|
194
|
+
Attributes:
|
|
195
|
+
metadata: Metadata for the Agent.
|
|
196
|
+
interface: Interface definition for the Agent.
|
|
197
|
+
topology: Topology of the Agent execution.
|
|
198
|
+
dependencies: External dependencies for the Agent.
|
|
199
|
+
integrity_hash: SHA256 hash of the source code.
|
|
200
|
+
"""
|
|
137
201
|
|
|
138
202
|
model_config = ConfigDict(
|
|
139
203
|
extra="forbid",
|
coreason_manifest/policy.py
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
# Prosperity-3.0
|
|
2
|
+
"""Policy enforcement functionality using Open Policy Agent (OPA).
|
|
3
|
+
|
|
4
|
+
This module provides the `PolicyEnforcer` class, which interacts with OPA to
|
|
5
|
+
evaluate agent definitions against compliance policies defined in Rego.
|
|
6
|
+
"""
|
|
7
|
+
|
|
2
8
|
from __future__ import annotations
|
|
3
9
|
|
|
4
10
|
import json
|
|
@@ -11,8 +17,7 @@ from coreason_manifest.errors import PolicyViolationError
|
|
|
11
17
|
|
|
12
18
|
|
|
13
19
|
class PolicyEnforcer:
|
|
14
|
-
"""
|
|
15
|
-
Component C: PolicyEnforcer (The Compliance Officer).
|
|
20
|
+
"""Component C: PolicyEnforcer (The Compliance Officer).
|
|
16
21
|
|
|
17
22
|
Responsibility:
|
|
18
23
|
- Evaluate the agent against the compliance.rego policy file using OPA.
|
|
@@ -24,13 +29,15 @@ class PolicyEnforcer:
|
|
|
24
29
|
opa_path: str = "opa",
|
|
25
30
|
data_paths: Optional[List[str | Path]] = None,
|
|
26
31
|
) -> None:
|
|
27
|
-
"""
|
|
28
|
-
Initialize the PolicyEnforcer.
|
|
32
|
+
"""Initialize the PolicyEnforcer.
|
|
29
33
|
|
|
30
34
|
Args:
|
|
31
35
|
policy_path: Path to the Rego policy file.
|
|
32
36
|
opa_path: Path to the OPA executable. Defaults to "opa" (expected in PATH).
|
|
33
37
|
data_paths: List of paths to data files (e.g. JSON/YAML) to be loaded by OPA.
|
|
38
|
+
|
|
39
|
+
Raises:
|
|
40
|
+
FileNotFoundError: If OPA, policy file, or data files are not found.
|
|
34
41
|
"""
|
|
35
42
|
self.policy_path = Path(policy_path)
|
|
36
43
|
self.data_paths = [Path(p) for p in data_paths] if data_paths else []
|
|
@@ -38,15 +45,15 @@ class PolicyEnforcer:
|
|
|
38
45
|
# Validate OPA executable
|
|
39
46
|
# If opa_path is a simple name (like "opa"), use shutil.which to find it
|
|
40
47
|
if "/" not in str(opa_path) and "\\" not in str(opa_path):
|
|
41
|
-
resolved_opa = shutil.which(opa_path)
|
|
48
|
+
resolved_opa: Optional[str] = shutil.which(opa_path)
|
|
42
49
|
if not resolved_opa:
|
|
43
50
|
raise FileNotFoundError(f"OPA executable not found in PATH: {opa_path}")
|
|
44
|
-
self.opa_path = resolved_opa
|
|
51
|
+
self.opa_path: str = resolved_opa
|
|
45
52
|
else:
|
|
46
53
|
# If it's a path, check existence
|
|
47
54
|
if not Path(opa_path).exists():
|
|
48
55
|
raise FileNotFoundError(f"OPA executable not found at: {opa_path}")
|
|
49
|
-
self.opa_path = opa_path
|
|
56
|
+
self.opa_path = str(opa_path)
|
|
50
57
|
|
|
51
58
|
if not self.policy_path.exists():
|
|
52
59
|
raise FileNotFoundError(f"Policy file not found: {self.policy_path}")
|
|
@@ -56,8 +63,7 @@ class PolicyEnforcer:
|
|
|
56
63
|
raise FileNotFoundError(f"Data file not found: {path}")
|
|
57
64
|
|
|
58
65
|
def evaluate(self, agent_data: dict[str, Any]) -> None:
|
|
59
|
-
"""
|
|
60
|
-
Evaluates the agent data against the policy.
|
|
66
|
+
"""Evaluates the agent data against the policy.
|
|
61
67
|
|
|
62
68
|
Args:
|
|
63
69
|
agent_data: The dictionary representation of the AgentDefinition.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$defs": {
|
|
3
3
|
"AgentDependencies": {
|
|
4
4
|
"additionalProperties": false,
|
|
5
|
-
"description": "External dependencies for the Agent.",
|
|
5
|
+
"description": "External dependencies for the Agent.\n\nAttributes:\n tools: List of MCP capability URIs required.\n libraries: List of Python packages required (if code execution is allowed).",
|
|
6
6
|
"properties": {
|
|
7
7
|
"tools": {
|
|
8
8
|
"description": "List of MCP capability URIs required.",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
},
|
|
29
29
|
"AgentInterface": {
|
|
30
30
|
"additionalProperties": false,
|
|
31
|
-
"description": "Interface definition for the Agent.",
|
|
31
|
+
"description": "Interface definition for the Agent.\n\nAttributes:\n inputs: Typed arguments the agent accepts (JSON Schema).\n outputs: Typed structure of the result.",
|
|
32
32
|
"properties": {
|
|
33
33
|
"inputs": {
|
|
34
34
|
"additionalProperties": true,
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
},
|
|
53
53
|
"AgentMetadata": {
|
|
54
54
|
"additionalProperties": false,
|
|
55
|
-
"description": "Metadata for the Agent.",
|
|
55
|
+
"description": "Metadata for the Agent.\n\nAttributes:\n id: Unique Identifier for the Agent (UUID).\n version: Semantic Version of the Agent.\n name: Name of the Agent.\n author: Author of the Agent.\n created_at: Creation timestamp (ISO 8601).",
|
|
56
56
|
"properties": {
|
|
57
57
|
"id": {
|
|
58
58
|
"description": "Unique Identifier for the Agent (UUID).",
|
|
@@ -97,7 +97,7 @@
|
|
|
97
97
|
},
|
|
98
98
|
"AgentTopology": {
|
|
99
99
|
"additionalProperties": false,
|
|
100
|
-
"description": "Topology of the Agent execution.",
|
|
100
|
+
"description": "Topology of the Agent execution.\n\nAttributes:\n steps: A directed acyclic graph (DAG) of execution steps.\n llm_config: Specific LLM parameters.",
|
|
101
101
|
"properties": {
|
|
102
102
|
"steps": {
|
|
103
103
|
"description": "A directed acyclic graph (DAG) of execution steps.",
|
|
@@ -121,7 +121,7 @@
|
|
|
121
121
|
},
|
|
122
122
|
"ModelConfig": {
|
|
123
123
|
"additionalProperties": false,
|
|
124
|
-
"description": "LLM Configuration parameters.",
|
|
124
|
+
"description": "LLM Configuration parameters.\n\nAttributes:\n model: The LLM model identifier.\n temperature: Temperature for generation.",
|
|
125
125
|
"properties": {
|
|
126
126
|
"model": {
|
|
127
127
|
"description": "The LLM model identifier.",
|
|
@@ -145,7 +145,7 @@
|
|
|
145
145
|
},
|
|
146
146
|
"Step": {
|
|
147
147
|
"additionalProperties": false,
|
|
148
|
-
"description": "A single step in the execution graph.",
|
|
148
|
+
"description": "A single step in the execution graph.\n\nAttributes:\n id: Unique identifier for the step.\n description: Description of the step.",
|
|
149
149
|
"properties": {
|
|
150
150
|
"id": {
|
|
151
151
|
"description": "Unique identifier for the step.",
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
from contextlib import asynccontextmanager
|
|
2
|
+
from importlib import resources
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import AsyncIterator, List, Optional, Union
|
|
5
|
+
|
|
6
|
+
from fastapi import FastAPI, HTTPException, Request, status
|
|
7
|
+
from fastapi.responses import JSONResponse
|
|
8
|
+
from pydantic import BaseModel, Field
|
|
9
|
+
|
|
10
|
+
from coreason_manifest.engine import ManifestConfig, ManifestEngineAsync
|
|
11
|
+
from coreason_manifest.errors import ManifestSyntaxError, PolicyViolationError
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# Response Model
|
|
15
|
+
class ValidationResponse(BaseModel):
|
|
16
|
+
valid: bool
|
|
17
|
+
agent_id: Optional[str] = None
|
|
18
|
+
version: Optional[str] = None
|
|
19
|
+
policy_violations: List[str] = Field(default_factory=list)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@asynccontextmanager
|
|
23
|
+
async def lifespan(app: FastAPI) -> AsyncIterator[None]:
|
|
24
|
+
# Locate policies
|
|
25
|
+
policy_path: Optional[Path] = None
|
|
26
|
+
tbom_path: Optional[Path] = None
|
|
27
|
+
|
|
28
|
+
# 1. Check local directory (Docker runtime with COPY or relative dev path)
|
|
29
|
+
# In Docker we will COPY to /app/policies/ or ./policies/ relative to WORKDIR
|
|
30
|
+
# We'll check common locations.
|
|
31
|
+
possible_dirs = [
|
|
32
|
+
Path("policies"),
|
|
33
|
+
Path("/app/policies"),
|
|
34
|
+
Path("src/coreason_manifest/policies"), # Dev from root
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
for d in possible_dirs:
|
|
38
|
+
if (d / "compliance.rego").exists():
|
|
39
|
+
policy_path = d / "compliance.rego"
|
|
40
|
+
if (d / "tbom.json").exists():
|
|
41
|
+
tbom_path = d / "tbom.json"
|
|
42
|
+
break
|
|
43
|
+
|
|
44
|
+
# 2. Fallback to package resources
|
|
45
|
+
resource_context = None
|
|
46
|
+
if not policy_path:
|
|
47
|
+
try:
|
|
48
|
+
# Check if it exists as a resource
|
|
49
|
+
ref = resources.files("coreason_manifest.policies").joinpath("compliance.rego")
|
|
50
|
+
if ref.is_file():
|
|
51
|
+
resource_context = resources.as_file(ref)
|
|
52
|
+
policy_path = resource_context.__enter__()
|
|
53
|
+
# Check for TBOM in same dir if possible, or ignore for fallback
|
|
54
|
+
except Exception:
|
|
55
|
+
pass
|
|
56
|
+
|
|
57
|
+
# If still not found, fail.
|
|
58
|
+
if not policy_path:
|
|
59
|
+
raise RuntimeError("Could not locate compliance.rego policy file.")
|
|
60
|
+
|
|
61
|
+
config = ManifestConfig(
|
|
62
|
+
policy_path=policy_path,
|
|
63
|
+
tbom_path=tbom_path,
|
|
64
|
+
opa_path="opa", # Assumes OPA is in PATH (installed via Dockerfile)
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
engine = ManifestEngineAsync(config)
|
|
68
|
+
|
|
69
|
+
try:
|
|
70
|
+
async with engine:
|
|
71
|
+
app.state.engine = engine
|
|
72
|
+
yield
|
|
73
|
+
finally:
|
|
74
|
+
if resource_context:
|
|
75
|
+
resource_context.__exit__(None, None, None)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
app = FastAPI(lifespan=lifespan)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@app.post("/validate", response_model=ValidationResponse) # type: ignore[misc]
|
|
82
|
+
async def validate_manifest(request: Request) -> Union[ValidationResponse, JSONResponse]:
|
|
83
|
+
engine: ManifestEngineAsync = app.state.engine
|
|
84
|
+
|
|
85
|
+
try:
|
|
86
|
+
raw_body = await request.json()
|
|
87
|
+
except Exception:
|
|
88
|
+
raise HTTPException(status_code=400, detail="Invalid JSON body") from None
|
|
89
|
+
|
|
90
|
+
try:
|
|
91
|
+
agent_def = await engine.validate_manifest_dict(raw_body)
|
|
92
|
+
return ValidationResponse(
|
|
93
|
+
valid=True,
|
|
94
|
+
agent_id=str(agent_def.metadata.id),
|
|
95
|
+
version=agent_def.metadata.version,
|
|
96
|
+
policy_violations=[],
|
|
97
|
+
)
|
|
98
|
+
except ManifestSyntaxError as e:
|
|
99
|
+
# Return 422 with the error
|
|
100
|
+
resp = ValidationResponse(valid=False, policy_violations=[f"Syntax Error: {str(e)}"])
|
|
101
|
+
return JSONResponse(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, content=resp.model_dump())
|
|
102
|
+
except PolicyViolationError as e:
|
|
103
|
+
resp = ValidationResponse(valid=False, policy_violations=e.violations)
|
|
104
|
+
return JSONResponse(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, content=resp.model_dump())
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@app.get("/health") # type: ignore[misc]
|
|
108
|
+
async def health_check() -> dict[str, str]:
|
|
109
|
+
engine: ManifestEngineAsync = app.state.engine
|
|
110
|
+
policy_version = "unknown"
|
|
111
|
+
try:
|
|
112
|
+
import hashlib
|
|
113
|
+
|
|
114
|
+
# policy_path is guaranteed to exist by lifespan check
|
|
115
|
+
policy_path = Path(engine.config.policy_path)
|
|
116
|
+
if policy_path.exists():
|
|
117
|
+
with open(policy_path, "rb") as f:
|
|
118
|
+
digest = hashlib.sha256(f.read()).hexdigest()[:8]
|
|
119
|
+
policy_version = digest
|
|
120
|
+
except Exception:
|
|
121
|
+
pass
|
|
122
|
+
|
|
123
|
+
return {"status": "active", "policy_version": policy_version}
|
coreason_manifest/validator.py
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
# Prosperity-3.0
|
|
2
|
+
"""Schema validation functionality.
|
|
3
|
+
|
|
4
|
+
This module provides the `SchemaValidator` class, which uses JSON Schema to
|
|
5
|
+
validate the structure and types of agent definitions.
|
|
6
|
+
"""
|
|
7
|
+
|
|
2
8
|
from __future__ import annotations
|
|
3
9
|
|
|
4
10
|
import json
|
|
@@ -11,8 +17,7 @@ from coreason_manifest.errors import ManifestSyntaxError
|
|
|
11
17
|
|
|
12
18
|
|
|
13
19
|
class SchemaValidator:
|
|
14
|
-
"""
|
|
15
|
-
Component B: SchemaValidator (The Structural Engineer).
|
|
20
|
+
"""Component B: SchemaValidator (The Structural Engineer).
|
|
16
21
|
|
|
17
22
|
Responsibility:
|
|
18
23
|
- Validate the dictionary against the Master JSON Schema.
|
|
@@ -24,7 +29,14 @@ class SchemaValidator:
|
|
|
24
29
|
self.schema = self._load_schema()
|
|
25
30
|
|
|
26
31
|
def _load_schema(self) -> dict[str, Any]:
|
|
27
|
-
"""Loads the JSON schema from the package resources.
|
|
32
|
+
"""Loads the JSON schema from the package resources.
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
The JSON schema dictionary.
|
|
36
|
+
|
|
37
|
+
Raises:
|
|
38
|
+
ManifestSyntaxError: If the schema file cannot be loaded or is invalid.
|
|
39
|
+
"""
|
|
28
40
|
try:
|
|
29
41
|
schema_path = files("coreason_manifest.schemas").joinpath("agent.schema.json")
|
|
30
42
|
with schema_path.open("r", encoding="utf-8") as f:
|
|
@@ -36,14 +48,13 @@ class SchemaValidator:
|
|
|
36
48
|
raise ManifestSyntaxError(f"Failed to load agent schema: {e}") from e
|
|
37
49
|
|
|
38
50
|
def validate(self, data: dict[str, Any]) -> bool:
|
|
39
|
-
"""
|
|
40
|
-
Validates the given dictionary against the agent schema.
|
|
51
|
+
"""Validates the given dictionary against the agent schema.
|
|
41
52
|
|
|
42
53
|
Args:
|
|
43
54
|
data: The raw dictionary to validate.
|
|
44
55
|
|
|
45
56
|
Returns:
|
|
46
|
-
|
|
57
|
+
True if validation passes.
|
|
47
58
|
|
|
48
59
|
Raises:
|
|
49
60
|
ManifestSyntaxError: If validation fails.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: coreason_manifest
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: This package is the definitive source of truth. If it isn't in the manifest, it doesn't exist. If it violates the manifest, it doesn't run.
|
|
5
5
|
License: # The Prosperity Public License 3.0.0
|
|
6
6
|
|
|
@@ -67,48 +67,74 @@ Requires-Python: >=3.11
|
|
|
67
67
|
Classifier: License :: Other/Proprietary License
|
|
68
68
|
Classifier: Programming Language :: Python :: 3.12
|
|
69
69
|
Classifier: Operating System :: OS Independent
|
|
70
|
+
Requires-Dist: aiofiles (>=23.2.1,<24.0.0)
|
|
71
|
+
Requires-Dist: anyio (>=4.3.0,<5.0.0)
|
|
72
|
+
Requires-Dist: fastapi (>=0.111.0,<0.112.0)
|
|
73
|
+
Requires-Dist: httpx (>=0.27.0,<0.28.0)
|
|
70
74
|
Requires-Dist: jsonschema (>=4.25.1,<5.0.0)
|
|
71
75
|
Requires-Dist: loguru (>=0.7.2,<0.8.0)
|
|
72
76
|
Requires-Dist: pydantic (>=2.12.5,<3.0.0)
|
|
73
77
|
Requires-Dist: pyyaml (>=6.0.3,<7.0.0)
|
|
78
|
+
Requires-Dist: uvicorn (>=0.30.1,<0.31.0)
|
|
74
79
|
Project-URL: Documentation, https://github.com/CoReason-AI/coreason_manifest
|
|
75
80
|
Project-URL: Homepage, https://github.com/CoReason-AI/coreason_manifest
|
|
76
81
|
Project-URL: Repository, https://github.com/CoReason-AI/coreason_manifest
|
|
77
82
|
Description-Content-Type: text/markdown
|
|
78
83
|
|
|
79
|
-
#
|
|
84
|
+
# Coreason Manifest
|
|
80
85
|
|
|
81
|
-
|
|
86
|
+
The definitive source of truth for CoReason-AI Asset definitions. "The Blueprint."
|
|
82
87
|
|
|
83
|
-
[](https://github.com/CoReason-AI/coreason-manifest)
|
|
89
|
+
[](https://github.com/CoReason-AI/coreason-manifest/actions)
|
|
90
|
+
[](https://github.com/astral-sh/ruff)
|
|
91
|
+
[](docs/product_requirements.md)
|
|
84
92
|
|
|
85
|
-
##
|
|
93
|
+
## Overview
|
|
86
94
|
|
|
87
|
-
|
|
95
|
+
`coreason-manifest` acts as the validator for the "Agent Development Lifecycle" (ADLC). It ensures that every Agent produced meets strict GxP and security standards. If it isn't in the manifest, it doesn't exist. If it violates the manifest, it doesn't run.
|
|
88
96
|
|
|
89
|
-
|
|
90
|
-
- Poetry
|
|
97
|
+
## Features
|
|
91
98
|
|
|
92
|
-
|
|
99
|
+
* **Open Agent Specification (OAS) Validation:** Parses and validates agent definitions against a strict schema.
|
|
100
|
+
* **Compliance Enforcement:** Uses Open Policy Agent (OPA) / Rego to enforce complex business rules and allowlists.
|
|
101
|
+
* **Integrity Verification:** Calculates and verifies SHA256 hashes of the agent's source code to prevent tampering.
|
|
102
|
+
* **Dependency Pinning:** Enforces strict version pinning for all library dependencies.
|
|
103
|
+
* **Trusted Bill of Materials (TBOM):** Validates libraries against an approved list.
|
|
104
|
+
* **Compliance Microservice:** Can be run as a standalone API server (Service C) for centralized validation.
|
|
93
105
|
|
|
94
|
-
|
|
95
|
-
```sh
|
|
96
|
-
git clone https://github.com/example/example.git
|
|
97
|
-
cd my_python_project
|
|
98
|
-
```
|
|
99
|
-
2. Install dependencies:
|
|
100
|
-
```sh
|
|
101
|
-
poetry install
|
|
102
|
-
```
|
|
106
|
+
## Installation
|
|
103
107
|
|
|
104
|
-
|
|
108
|
+
```bash
|
|
109
|
+
pip install coreason-manifest
|
|
110
|
+
```
|
|
105
111
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
112
|
+
## Usage
|
|
113
|
+
|
|
114
|
+
`coreason-manifest` supports two modes: **Library (CLI)** and **Server (Microservice)**.
|
|
115
|
+
|
|
116
|
+
### 1. Library Usage
|
|
117
|
+
|
|
118
|
+
Use the python library to validate local agent files and verify source integrity.
|
|
119
|
+
|
|
120
|
+
```python
|
|
121
|
+
from coreason_manifest import ManifestEngine, ManifestConfig
|
|
122
|
+
|
|
123
|
+
# Initialize and Validate
|
|
124
|
+
config = ManifestConfig(policy_path="./policies/compliance.rego")
|
|
125
|
+
engine = ManifestEngine(config)
|
|
126
|
+
agent_def = engine.load_and_validate("agent.yaml", "./src")
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### 2. Server Mode
|
|
130
|
+
|
|
131
|
+
Run the package as a FastAPI server to provide a centralized compliance API.
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
uvicorn coreason_manifest.server:app --host 0.0.0.0 --port 8000
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
For full details, see the [Usage Documentation](docs/usage.md).
|
|
138
|
+
|
|
139
|
+
For detailed requirements and architecture, please refer to the [Product Requirements](docs/product_requirements.md) or [Requirements](docs/requirements.md).
|
|
114
140
|
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
coreason_manifest/__init__.py,sha256=R07xUHw9RWdWQPh7OWbpMT8kkzkEWfEI5hizu_5UNck,1528
|
|
2
|
+
coreason_manifest/engine.py,sha256=ENAkRuGFd1uY_5u4EYEsTZUMrqw5HgijKtkF9lk-E-c,8199
|
|
3
|
+
coreason_manifest/errors.py,sha256=CKFWSucncoPLYUJcsuXtLIfneyr2d89oHRTiWPT4psU,1437
|
|
4
|
+
coreason_manifest/integrity.py,sha256=CMBaa2A8DpC9hDSO-aj-YOzss6if1CajaeLEmTC703c,5344
|
|
5
|
+
coreason_manifest/loader.py,sha256=cc1iV-yKfH3gyPaB-P_7AaTaPYkgj1NoDsJz4PReTgo,5479
|
|
6
|
+
coreason_manifest/main.py,sha256=YQO98w2wxkfNs-DpSLmiDTs9DtUeGsC48GEfbElXVPk,357
|
|
7
|
+
coreason_manifest/models.py,sha256=pJIuEtCiiXcxxi2zRwken4ph2GFa66YHnOqHyySR5sU,6828
|
|
8
|
+
coreason_manifest/policies/compliance.rego,sha256=-drMuno6YkGOXKjvdLWawCZWK8iUxY7OcXpoXa_9Oyo,3035
|
|
9
|
+
coreason_manifest/policies/tbom.json,sha256=rSn4V44_IdFqCC86J3Jc31qQKTV4J5BdmyO0CI4iOu0,167
|
|
10
|
+
coreason_manifest/policy.py,sha256=vvEivq5HSjv-bMSrZ5VMM3eTomYFp39SBtuFajG9RU4,5009
|
|
11
|
+
coreason_manifest/schemas/__init__.py,sha256=9TMs6jWKCIewAKkj-u0tq9c6N_-0CU6b5s9q6MTS6v4,17
|
|
12
|
+
coreason_manifest/schemas/agent.schema.json,sha256=wdNYmpyCUEKR1UgMmRV4ZSQOm7xvnmSg5lvhQ3JKvSg,6368
|
|
13
|
+
coreason_manifest/server.py,sha256=kHrReqoEx4sL5k5GQN7yNwLgvfnOl1kZf-dQehseuO4,4251
|
|
14
|
+
coreason_manifest/utils/__init__.py,sha256=Q9gXiBtX3mD9GTu4z0JDHSHkbXC-MRHagrOaOmRH_1Q,435
|
|
15
|
+
coreason_manifest/utils/logger.py,sha256=A7E6Hd_Jk1XDUajNEJQl-WtUv9M2LT76b4_TsbxnILw,1227
|
|
16
|
+
coreason_manifest/validator.py,sha256=v33EzKroRwLEjZeuRRpTB7cqB38op3DV2EZpZhJ80a0,2240
|
|
17
|
+
coreason_manifest-0.4.0.dist-info/METADATA,sha256=aQdNN6nn8Fi4g_eSj17mKrtf_SkO4l0XembzP85TXlE,7229
|
|
18
|
+
coreason_manifest-0.4.0.dist-info/WHEEL,sha256=3ny-bZhpXrU6vSQ1UPG34FoxZBp3lVcvK0LkgUz6VLk,88
|
|
19
|
+
coreason_manifest-0.4.0.dist-info/licenses/LICENSE,sha256=3tYb7ZQe7sVXcbNmX22fDESFjOSIlCZodUGpZMkuSlk,3063
|
|
20
|
+
coreason_manifest-0.4.0.dist-info/licenses/NOTICE,sha256=tqzUyP9VTCGxoHLgBI0AC1i0G7m_PSyESFL8Jwuw0dA,610
|
|
21
|
+
coreason_manifest-0.4.0.dist-info/RECORD,,
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
coreason_manifest/__init__.py,sha256=NeRYnUf8mbcBbAwplVfpqHv1LN9ElFbrGck0ZdGj0kA,897
|
|
2
|
-
coreason_manifest/engine.py,sha256=kY4TrtvHANvAK8yvB0CNjg9ZT5wjMrubykF6InYjujw,4212
|
|
3
|
-
coreason_manifest/errors.py,sha256=B3fEYR3jxR3iZulu4qeS3UzHFsZDTImBTQWZUvUQkKM,684
|
|
4
|
-
coreason_manifest/integrity.py,sha256=AYprazGTey4EY1tjH7774-pdL8trmc6jOPWf1atGV-8,5104
|
|
5
|
-
coreason_manifest/loader.py,sha256=5MGrlb9QYhV1-ovfGT5UdCW-t6Ar1HPUV6cHCSkSx6U,4670
|
|
6
|
-
coreason_manifest/main.py,sha256=2pj9LX91WQFvbsykANCSc0-5PP4peBYfIcxa8IMUiaA,549
|
|
7
|
-
coreason_manifest/models.py,sha256=C73chpjgUuvDxglEj0FURPoNhMoyVlyZpput7QAg1rk,5110
|
|
8
|
-
coreason_manifest/policies/compliance.rego,sha256=-drMuno6YkGOXKjvdLWawCZWK8iUxY7OcXpoXa_9Oyo,3035
|
|
9
|
-
coreason_manifest/policies/tbom.json,sha256=rSn4V44_IdFqCC86J3Jc31qQKTV4J5BdmyO0CI4iOu0,167
|
|
10
|
-
coreason_manifest/policy.py,sha256=Tu_FL1DwNfKky_Nash7KqzVs1oV8oZCgS4ppUV7tQbs,4687
|
|
11
|
-
coreason_manifest/schemas/__init__.py,sha256=9TMs6jWKCIewAKkj-u0tq9c6N_-0CU6b5s9q6MTS6v4,17
|
|
12
|
-
coreason_manifest/schemas/agent.schema.json,sha256=OqJlTp2_GocK05kPAfpyK807NELOQPDikfwn1yfl3kI,5561
|
|
13
|
-
coreason_manifest/utils/__init__.py,sha256=Q9gXiBtX3mD9GTu4z0JDHSHkbXC-MRHagrOaOmRH_1Q,435
|
|
14
|
-
coreason_manifest/utils/logger.py,sha256=A7E6Hd_Jk1XDUajNEJQl-WtUv9M2LT76b4_TsbxnILw,1227
|
|
15
|
-
coreason_manifest/validator.py,sha256=OzLLjqRHLxXfKQeOwEVj16fHWfdif8of5r4b_mCOR8g,1919
|
|
16
|
-
coreason_manifest-0.2.0.dist-info/METADATA,sha256=1eiB1e0RnGDKF4YFy0wQgoGgOGegxyvyEBABDYSAIr4,5238
|
|
17
|
-
coreason_manifest-0.2.0.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
|
|
18
|
-
coreason_manifest-0.2.0.dist-info/licenses/LICENSE,sha256=3tYb7ZQe7sVXcbNmX22fDESFjOSIlCZodUGpZMkuSlk,3063
|
|
19
|
-
coreason_manifest-0.2.0.dist-info/licenses/NOTICE,sha256=tqzUyP9VTCGxoHLgBI0AC1i0G7m_PSyESFL8Jwuw0dA,610
|
|
20
|
-
coreason_manifest-0.2.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|