coreason-manifest 0.6.0__py3-none-any.whl → 0.7.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 +11 -73
- coreason_manifest/definitions/__init__.py +0 -0
- coreason_manifest/definitions/audit.py +7 -0
- coreason_manifest/definitions/simulation.py +19 -0
- coreason_manifest/definitions/topology.py +140 -0
- coreason_manifest/recipes.py +3 -126
- {coreason_manifest-0.6.0.dist-info → coreason_manifest-0.7.0.dist-info}/METADATA +1 -7
- coreason_manifest-0.7.0.dist-info/RECORD +16 -0
- coreason_manifest/engine.py +0 -222
- coreason_manifest/errors.py +0 -53
- coreason_manifest/integrity.py +0 -141
- coreason_manifest/loader.py +0 -271
- coreason_manifest/main.py +0 -17
- coreason_manifest/policies/compliance.rego +0 -81
- coreason_manifest/policies/tbom.json +0 -14
- coreason_manifest/policy.py +0 -138
- coreason_manifest/server.py +0 -123
- coreason_manifest/validator.py +0 -67
- coreason_manifest-0.6.0.dist-info/RECORD +0 -22
- /coreason_manifest/{models.py → definitions/agent.py} +0 -0
- {coreason_manifest-0.6.0.dist-info → coreason_manifest-0.7.0.dist-info}/WHEEL +0 -0
- {coreason_manifest-0.6.0.dist-info → coreason_manifest-0.7.0.dist-info}/licenses/LICENSE +0 -0
- {coreason_manifest-0.6.0.dist-info → coreason_manifest-0.7.0.dist-info}/licenses/NOTICE +0 -0
coreason_manifest/errors.py
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
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
|
-
|
|
7
|
-
from __future__ import annotations
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class ManifestError(Exception):
|
|
11
|
-
"""Base exception for coreason_manifest errors."""
|
|
12
|
-
|
|
13
|
-
pass
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
class ManifestSyntaxError(ManifestError):
|
|
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
|
-
"""
|
|
21
|
-
|
|
22
|
-
pass
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
class PolicyViolationError(ManifestError):
|
|
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
|
-
"""
|
|
34
|
-
|
|
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
|
-
"""
|
|
42
|
-
super().__init__(message)
|
|
43
|
-
self.violations = violations or []
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
class IntegrityCompromisedError(ManifestError):
|
|
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
|
-
"""
|
|
52
|
-
|
|
53
|
-
pass
|
coreason_manifest/integrity.py
DELETED
|
@@ -1,141 +0,0 @@
|
|
|
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
|
-
|
|
9
|
-
from __future__ import annotations
|
|
10
|
-
|
|
11
|
-
import hashlib
|
|
12
|
-
import os
|
|
13
|
-
from pathlib import Path
|
|
14
|
-
from typing import List, Optional, Set, Union
|
|
15
|
-
|
|
16
|
-
from coreason_manifest.errors import IntegrityCompromisedError
|
|
17
|
-
from coreason_manifest.models import AgentDefinition
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
class IntegrityChecker:
|
|
21
|
-
"""Component D: IntegrityChecker (The Notary).
|
|
22
|
-
|
|
23
|
-
Responsibility:
|
|
24
|
-
- Calculate the SHA256 hash of the source code directory.
|
|
25
|
-
- Compare it against the integrity_hash defined in the manifest.
|
|
26
|
-
"""
|
|
27
|
-
|
|
28
|
-
IGNORED_DIRS = frozenset({".git", "__pycache__", ".venv", ".env", ".DS_Store"})
|
|
29
|
-
|
|
30
|
-
@staticmethod
|
|
31
|
-
def calculate_hash(source_dir: Union[Path, str], exclude_files: Optional[Set[Union[Path, str]]] = None) -> str:
|
|
32
|
-
"""Calculates a deterministic SHA256 hash of the source code directory.
|
|
33
|
-
|
|
34
|
-
It walks the directory using os.walk to efficiently prune ignored directories.
|
|
35
|
-
Sorts files by relative path, hashes each file, and then hashes the sequence.
|
|
36
|
-
|
|
37
|
-
Ignores hidden directories/files in IGNORED_DIRS.
|
|
38
|
-
Rejects symbolic links for security.
|
|
39
|
-
|
|
40
|
-
Args:
|
|
41
|
-
source_dir: The directory containing source code.
|
|
42
|
-
exclude_files: Optional set of file paths (absolute or relative to CWD)
|
|
43
|
-
to exclude from hashing.
|
|
44
|
-
|
|
45
|
-
Returns:
|
|
46
|
-
The hex digest of the SHA256 hash.
|
|
47
|
-
|
|
48
|
-
Raises:
|
|
49
|
-
FileNotFoundError: If source_dir does not exist.
|
|
50
|
-
IntegrityCompromisedError: If a symlink is found.
|
|
51
|
-
"""
|
|
52
|
-
path_obj = Path(source_dir)
|
|
53
|
-
if path_obj.is_symlink():
|
|
54
|
-
raise IntegrityCompromisedError(f"Symbolic links are forbidden: {path_obj}")
|
|
55
|
-
|
|
56
|
-
source_path = path_obj.resolve()
|
|
57
|
-
if not source_path.exists():
|
|
58
|
-
raise FileNotFoundError(f"Source directory not found: {source_path}")
|
|
59
|
-
|
|
60
|
-
# Normalize excluded files to absolute paths
|
|
61
|
-
excludes = set()
|
|
62
|
-
if exclude_files:
|
|
63
|
-
for ex_path in exclude_files:
|
|
64
|
-
excludes.add(Path(ex_path).resolve())
|
|
65
|
-
|
|
66
|
-
sha256 = hashlib.sha256()
|
|
67
|
-
file_paths: List[Path] = []
|
|
68
|
-
|
|
69
|
-
# Use os.walk for efficient traversal and pruning
|
|
70
|
-
for root, dirs, files in os.walk(source_path, topdown=True):
|
|
71
|
-
root_path = Path(root)
|
|
72
|
-
|
|
73
|
-
# Check for symlinks in directories before pruning
|
|
74
|
-
for d_name in dirs:
|
|
75
|
-
d_path = root_path / d_name
|
|
76
|
-
if d_path.is_symlink():
|
|
77
|
-
raise IntegrityCompromisedError(f"Symbolic links are forbidden: {d_path}") # pragma: no cover
|
|
78
|
-
|
|
79
|
-
# Prune directories efficiently using slice assignment
|
|
80
|
-
dirs[:] = [d for d in dirs if d not in IntegrityChecker.IGNORED_DIRS]
|
|
81
|
-
|
|
82
|
-
# Collect files
|
|
83
|
-
for f_name in files:
|
|
84
|
-
f_path = root_path / f_name
|
|
85
|
-
|
|
86
|
-
if f_path.is_symlink():
|
|
87
|
-
raise IntegrityCompromisedError(f"Symbolic links are forbidden: {f_path}")
|
|
88
|
-
|
|
89
|
-
if f_name in IntegrityChecker.IGNORED_DIRS:
|
|
90
|
-
continue
|
|
91
|
-
|
|
92
|
-
# Use resolved path for exclusion checking and inclusion
|
|
93
|
-
f_path_abs = f_path.resolve()
|
|
94
|
-
if f_path_abs in excludes:
|
|
95
|
-
continue
|
|
96
|
-
|
|
97
|
-
file_paths.append(f_path_abs)
|
|
98
|
-
|
|
99
|
-
# Sort to ensure deterministic order
|
|
100
|
-
# Use as_posix() to ensure ASCII sorting (case-sensitive) on all platforms (Windows vs Linux)
|
|
101
|
-
file_paths.sort(key=lambda p: p.relative_to(source_path).as_posix())
|
|
102
|
-
|
|
103
|
-
for path in file_paths:
|
|
104
|
-
# Update hash with relative path to ensure structure matters
|
|
105
|
-
# Use forward slashes for cross-platform consistency
|
|
106
|
-
rel_path = path.relative_to(source_path).as_posix().encode("utf-8")
|
|
107
|
-
sha256.update(rel_path)
|
|
108
|
-
|
|
109
|
-
# Update hash with file content
|
|
110
|
-
with open(path, "rb") as f:
|
|
111
|
-
while chunk := f.read(8192):
|
|
112
|
-
sha256.update(chunk)
|
|
113
|
-
|
|
114
|
-
return sha256.hexdigest()
|
|
115
|
-
|
|
116
|
-
@staticmethod
|
|
117
|
-
def verify(
|
|
118
|
-
agent_def: AgentDefinition,
|
|
119
|
-
source_dir: Union[Path, str],
|
|
120
|
-
manifest_path: Optional[Union[Path, str]] = None,
|
|
121
|
-
) -> None:
|
|
122
|
-
"""Verifies the integrity of the source code against the manifest.
|
|
123
|
-
|
|
124
|
-
Args:
|
|
125
|
-
agent_def: The AgentDefinition containing the expected hash.
|
|
126
|
-
source_dir: The directory containing source code.
|
|
127
|
-
manifest_path: Optional path to the manifest file to exclude from hashing.
|
|
128
|
-
|
|
129
|
-
Raises:
|
|
130
|
-
IntegrityCompromisedError: If the hash does not match or is missing.
|
|
131
|
-
FileNotFoundError: If source_dir does not exist.
|
|
132
|
-
"""
|
|
133
|
-
exclude_files = {manifest_path} if manifest_path else None
|
|
134
|
-
|
|
135
|
-
# agent_def.integrity_hash is now required by Pydantic model
|
|
136
|
-
calculated = IntegrityChecker.calculate_hash(source_dir, exclude_files=exclude_files)
|
|
137
|
-
|
|
138
|
-
if calculated != agent_def.integrity_hash:
|
|
139
|
-
raise IntegrityCompromisedError(
|
|
140
|
-
f"Integrity check failed. Expected {agent_def.integrity_hash}, got {calculated}"
|
|
141
|
-
)
|
coreason_manifest/loader.py
DELETED
|
@@ -1,271 +0,0 @@
|
|
|
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
|
-
|
|
8
|
-
from __future__ import annotations
|
|
9
|
-
|
|
10
|
-
import inspect
|
|
11
|
-
from pathlib import Path
|
|
12
|
-
from typing import Any, Callable, Dict, List, Union, get_type_hints
|
|
13
|
-
|
|
14
|
-
try:
|
|
15
|
-
from typing import get_args, get_origin
|
|
16
|
-
except ImportError: # pragma: no cover
|
|
17
|
-
# For Python < 3.8, though project requires 3.12+
|
|
18
|
-
from typing_extensions import get_args, get_origin # type: ignore
|
|
19
|
-
|
|
20
|
-
import aiofiles
|
|
21
|
-
import yaml
|
|
22
|
-
from coreason_identity import UserContext
|
|
23
|
-
from pydantic import ValidationError, create_model
|
|
24
|
-
|
|
25
|
-
from coreason_manifest.errors import ManifestSyntaxError
|
|
26
|
-
from coreason_manifest.models import AgentDefinition, AgentInterface
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
class ManifestLoader:
|
|
30
|
-
"""Component A: ManifestLoader (The Parser).
|
|
31
|
-
|
|
32
|
-
Responsibility:
|
|
33
|
-
- Load YAML safely.
|
|
34
|
-
- Convert raw data into a Pydantic AgentDefinition model.
|
|
35
|
-
- Normalization: Ensure all version strings follow SemVer and all IDs are canonical UUIDs.
|
|
36
|
-
"""
|
|
37
|
-
|
|
38
|
-
@staticmethod
|
|
39
|
-
def load_raw_from_file(path: Union[str, Path]) -> dict[str, Any]:
|
|
40
|
-
"""Loads the raw dict from a YAML file.
|
|
41
|
-
|
|
42
|
-
Args:
|
|
43
|
-
path: The path to the agent.yaml file.
|
|
44
|
-
|
|
45
|
-
Returns:
|
|
46
|
-
dict: The raw dictionary content.
|
|
47
|
-
|
|
48
|
-
Raises:
|
|
49
|
-
ManifestSyntaxError: If YAML is invalid.
|
|
50
|
-
FileNotFoundError: If the file does not exist.
|
|
51
|
-
"""
|
|
52
|
-
try:
|
|
53
|
-
path_obj = Path(path)
|
|
54
|
-
if not path_obj.exists():
|
|
55
|
-
raise FileNotFoundError(f"Manifest file not found: {path}")
|
|
56
|
-
|
|
57
|
-
with open(path_obj, "r", encoding="utf-8") as f:
|
|
58
|
-
# safe_load is recommended for untrusted input
|
|
59
|
-
data = yaml.safe_load(f)
|
|
60
|
-
|
|
61
|
-
if not isinstance(data, dict):
|
|
62
|
-
raise ManifestSyntaxError(f"Invalid YAML content in {path}: must be a dictionary.")
|
|
63
|
-
|
|
64
|
-
ManifestLoader._normalize_data(data)
|
|
65
|
-
|
|
66
|
-
return data
|
|
67
|
-
|
|
68
|
-
except yaml.YAMLError as e:
|
|
69
|
-
raise ManifestSyntaxError(f"Failed to parse YAML file {path}: {str(e)}") from e
|
|
70
|
-
except OSError as e:
|
|
71
|
-
if isinstance(e, FileNotFoundError):
|
|
72
|
-
raise
|
|
73
|
-
raise ManifestSyntaxError(f"Error reading file {path}: {str(e)}") from e
|
|
74
|
-
|
|
75
|
-
@staticmethod
|
|
76
|
-
async def load_raw_from_file_async(path: Union[str, Path]) -> dict[str, Any]:
|
|
77
|
-
"""Loads the raw dict from a YAML file asynchronously.
|
|
78
|
-
|
|
79
|
-
Args:
|
|
80
|
-
path: The path to the agent.yaml file.
|
|
81
|
-
|
|
82
|
-
Returns:
|
|
83
|
-
dict: The raw dictionary content.
|
|
84
|
-
|
|
85
|
-
Raises:
|
|
86
|
-
ManifestSyntaxError: If YAML is invalid.
|
|
87
|
-
FileNotFoundError: If the file does not exist.
|
|
88
|
-
"""
|
|
89
|
-
try:
|
|
90
|
-
path_obj = Path(path)
|
|
91
|
-
if not path_obj.exists():
|
|
92
|
-
raise FileNotFoundError(f"Manifest file not found: {path}")
|
|
93
|
-
|
|
94
|
-
async with aiofiles.open(path_obj, "r", encoding="utf-8") as f:
|
|
95
|
-
content = await f.read()
|
|
96
|
-
data = yaml.safe_load(content)
|
|
97
|
-
|
|
98
|
-
if not isinstance(data, dict):
|
|
99
|
-
raise ManifestSyntaxError(f"Invalid YAML content in {path}: must be a dictionary.")
|
|
100
|
-
|
|
101
|
-
ManifestLoader._normalize_data(data)
|
|
102
|
-
|
|
103
|
-
return data
|
|
104
|
-
|
|
105
|
-
except yaml.YAMLError as e:
|
|
106
|
-
raise ManifestSyntaxError(f"Failed to parse YAML file {path}: {str(e)}") from e
|
|
107
|
-
except OSError as e:
|
|
108
|
-
if isinstance(e, FileNotFoundError):
|
|
109
|
-
raise
|
|
110
|
-
raise ManifestSyntaxError(f"Error reading file {path}: {str(e)}") from e
|
|
111
|
-
|
|
112
|
-
@staticmethod
|
|
113
|
-
def load_from_file(path: Union[str, Path]) -> AgentDefinition:
|
|
114
|
-
"""Loads the agent manifest from a YAML file.
|
|
115
|
-
|
|
116
|
-
Args:
|
|
117
|
-
path: The path to the agent.yaml file.
|
|
118
|
-
|
|
119
|
-
Returns:
|
|
120
|
-
AgentDefinition: The validated Pydantic model.
|
|
121
|
-
|
|
122
|
-
Raises:
|
|
123
|
-
ManifestSyntaxError: If YAML is invalid or Pydantic validation fails.
|
|
124
|
-
FileNotFoundError: If the file does not exist.
|
|
125
|
-
"""
|
|
126
|
-
data = ManifestLoader.load_raw_from_file(path)
|
|
127
|
-
return ManifestLoader.load_from_dict(data)
|
|
128
|
-
|
|
129
|
-
@staticmethod
|
|
130
|
-
def load_from_dict(data: dict[str, Any]) -> AgentDefinition:
|
|
131
|
-
"""Converts a dictionary into an AgentDefinition model.
|
|
132
|
-
|
|
133
|
-
Args:
|
|
134
|
-
data: The raw dictionary.
|
|
135
|
-
|
|
136
|
-
Returns:
|
|
137
|
-
AgentDefinition: The validated Pydantic model.
|
|
138
|
-
|
|
139
|
-
Raises:
|
|
140
|
-
ManifestSyntaxError: If Pydantic validation fails.
|
|
141
|
-
"""
|
|
142
|
-
try:
|
|
143
|
-
# Ensure normalization happens before Pydantic validation
|
|
144
|
-
# We work on a copy to avoid side effects if possible, but deep copy is expensive.
|
|
145
|
-
# The input 'data' might be modified in place.
|
|
146
|
-
ManifestLoader._normalize_data(data)
|
|
147
|
-
|
|
148
|
-
return AgentDefinition.model_validate(data)
|
|
149
|
-
except ValidationError as e:
|
|
150
|
-
# Convert Pydantic ValidationError to ManifestSyntaxError
|
|
151
|
-
# We assume "normalization" happens via Pydantic validators (e.g. UUID, SemVer checks)
|
|
152
|
-
raise ManifestSyntaxError(f"Manifest validation failed: {str(e)}") from e
|
|
153
|
-
|
|
154
|
-
@staticmethod
|
|
155
|
-
def _normalize_data(data: dict[str, Any]) -> None:
|
|
156
|
-
"""Normalizes the data dictionary in place.
|
|
157
|
-
|
|
158
|
-
Specifically strips 'v' or 'V' from version strings recursively until clean.
|
|
159
|
-
"""
|
|
160
|
-
if "metadata" in data and isinstance(data["metadata"], dict):
|
|
161
|
-
version = data["metadata"].get("version")
|
|
162
|
-
if isinstance(version, str):
|
|
163
|
-
# Recursively strip leading 'v' or 'V'
|
|
164
|
-
while version and version[0] in ("v", "V"):
|
|
165
|
-
version = version[1:]
|
|
166
|
-
data["metadata"]["version"] = version
|
|
167
|
-
|
|
168
|
-
@staticmethod
|
|
169
|
-
def inspect_function(func: Callable[..., Any]) -> AgentInterface:
|
|
170
|
-
"""Generates an AgentInterface from a Python function.
|
|
171
|
-
|
|
172
|
-
Scans the function signature. If `user_context` (by name) or UserContext (by type)
|
|
173
|
-
is found, it is marked as injected and excluded from the public schema.
|
|
174
|
-
|
|
175
|
-
Args:
|
|
176
|
-
func: The function to inspect.
|
|
177
|
-
|
|
178
|
-
Returns:
|
|
179
|
-
AgentInterface: The generated interface definition.
|
|
180
|
-
|
|
181
|
-
Raises:
|
|
182
|
-
ManifestSyntaxError: If forbidden arguments are found.
|
|
183
|
-
"""
|
|
184
|
-
sig = inspect.signature(func)
|
|
185
|
-
try:
|
|
186
|
-
type_hints = get_type_hints(func)
|
|
187
|
-
except Exception:
|
|
188
|
-
# Fallback if get_type_hints fails (e.g. forward refs issues)
|
|
189
|
-
type_hints = {}
|
|
190
|
-
|
|
191
|
-
field_definitions: Dict[str, Any] = {}
|
|
192
|
-
injected: List[str] = []
|
|
193
|
-
|
|
194
|
-
for param_name, param in sig.parameters.items():
|
|
195
|
-
if param_name in ("self", "cls"):
|
|
196
|
-
continue
|
|
197
|
-
|
|
198
|
-
# Determine type annotation
|
|
199
|
-
annotation = type_hints.get(param_name, param.annotation)
|
|
200
|
-
if annotation is inspect.Parameter.empty:
|
|
201
|
-
annotation = Any
|
|
202
|
-
|
|
203
|
-
# Check for forbidden arguments
|
|
204
|
-
if param_name in ("api_key", "token"):
|
|
205
|
-
raise ManifestSyntaxError(f"Function argument '{param_name}' is forbidden. Use UserContext for auth.")
|
|
206
|
-
|
|
207
|
-
# Check for injection
|
|
208
|
-
is_injected = False
|
|
209
|
-
if param_name == "user_context":
|
|
210
|
-
is_injected = True
|
|
211
|
-
else:
|
|
212
|
-
# Check direct type
|
|
213
|
-
if annotation is UserContext:
|
|
214
|
-
is_injected = True
|
|
215
|
-
else:
|
|
216
|
-
# Check for Optional[UserContext], Annotated[UserContext, ...], Union[UserContext, ...]
|
|
217
|
-
origin = get_origin(annotation)
|
|
218
|
-
args = get_args(annotation)
|
|
219
|
-
if origin is not None:
|
|
220
|
-
# Recursively check if UserContext is in args (handles Optional/Union)
|
|
221
|
-
# or if this is Annotated (UserContext might be the first arg)
|
|
222
|
-
# We do a shallow check on args.
|
|
223
|
-
for arg in args:
|
|
224
|
-
if arg is UserContext:
|
|
225
|
-
is_injected = True
|
|
226
|
-
break
|
|
227
|
-
|
|
228
|
-
if is_injected:
|
|
229
|
-
if "user_context" not in injected:
|
|
230
|
-
injected.append("user_context")
|
|
231
|
-
continue
|
|
232
|
-
|
|
233
|
-
# Prepare for Pydantic model creation
|
|
234
|
-
default = param.default
|
|
235
|
-
if default is inspect.Parameter.empty:
|
|
236
|
-
default = ...
|
|
237
|
-
|
|
238
|
-
field_definitions[param_name] = (annotation, default)
|
|
239
|
-
|
|
240
|
-
# Create dynamic model to generate JSON Schema for inputs
|
|
241
|
-
# We assume strict mode or similar is handled by the consumer, here we just describe it.
|
|
242
|
-
try:
|
|
243
|
-
InputsModel = create_model("Inputs", **field_definitions)
|
|
244
|
-
inputs_schema = InputsModel.model_json_schema()
|
|
245
|
-
except Exception as e:
|
|
246
|
-
raise ManifestSyntaxError(f"Failed to generate schema from function signature: {e}") from e
|
|
247
|
-
|
|
248
|
-
# Handle return type for outputs
|
|
249
|
-
return_annotation = type_hints.get("return", sig.return_annotation)
|
|
250
|
-
outputs_schema = {}
|
|
251
|
-
if (
|
|
252
|
-
return_annotation is not inspect.Parameter.empty
|
|
253
|
-
and return_annotation is not None
|
|
254
|
-
and return_annotation is not type(None)
|
|
255
|
-
):
|
|
256
|
-
try:
|
|
257
|
-
# If return annotation is a Pydantic model, use its schema
|
|
258
|
-
if hasattr(return_annotation, "model_json_schema"):
|
|
259
|
-
outputs_schema = return_annotation.model_json_schema()
|
|
260
|
-
else:
|
|
261
|
-
# Wrap in a model
|
|
262
|
-
OutputsModel = create_model("Outputs", result=(return_annotation, ...))
|
|
263
|
-
outputs_schema = OutputsModel.model_json_schema()
|
|
264
|
-
except Exception:
|
|
265
|
-
pass
|
|
266
|
-
|
|
267
|
-
return AgentInterface(
|
|
268
|
-
inputs=inputs_schema,
|
|
269
|
-
outputs=outputs_schema,
|
|
270
|
-
injected_params=injected,
|
|
271
|
-
)
|
coreason_manifest/main.py
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
# Prosperity-3.0
|
|
2
|
-
"""Main module for coreason_manifest.
|
|
3
|
-
|
|
4
|
-
This module is primarily used for testing and demonstration purposes.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
from coreason_manifest.utils.logger import logger
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
def hello_world() -> str:
|
|
11
|
-
"""Returns a hello world string.
|
|
12
|
-
|
|
13
|
-
Returns:
|
|
14
|
-
"Hello World!" string.
|
|
15
|
-
"""
|
|
16
|
-
logger.info("Hello World!")
|
|
17
|
-
return "Hello World!"
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
package coreason.compliance
|
|
2
|
-
|
|
3
|
-
import rego.v1
|
|
4
|
-
|
|
5
|
-
# Do not import data.tbom to avoid namespace confusion, access via data.tbom directly if needed or via helper.
|
|
6
|
-
|
|
7
|
-
default allow := false
|
|
8
|
-
|
|
9
|
-
# Deny if 'pickle' is in libraries (matches "pickle", "pickle==1.0", "pickle>=2.0")
|
|
10
|
-
deny contains msg if {
|
|
11
|
-
some i
|
|
12
|
-
lib_str := input.dependencies.libraries[i]
|
|
13
|
-
# Check if the library name starts with 'pickle' followed by end of string or version specifier
|
|
14
|
-
regex.match("^pickle([<>=!@\\[].*)?$", lib_str)
|
|
15
|
-
msg := "Security Risk: 'pickle' library is strictly forbidden."
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
# Deny if 'os' is in libraries
|
|
19
|
-
deny contains msg if {
|
|
20
|
-
some i
|
|
21
|
-
lib_str := input.dependencies.libraries[i]
|
|
22
|
-
regex.match("^os([<>=!@\\[].*)?$", lib_str)
|
|
23
|
-
msg := "Security Risk: 'os' library is strictly forbidden."
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
# Deny if description is too short (Business Rule example)
|
|
27
|
-
# Iterates over ALL steps to ensure compliance.
|
|
28
|
-
deny contains msg if {
|
|
29
|
-
some step in input.topology.steps
|
|
30
|
-
count(step.description) < 5
|
|
31
|
-
msg := "Step description is too short."
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
# Rule 1 (Dependency Pinning): All library dependencies must have explicit version pins.
|
|
35
|
-
# Strictly enforces "name==version" (with optional extras).
|
|
36
|
-
# Rejects "name>=version", "name==version,>=other", etc.
|
|
37
|
-
deny contains msg if {
|
|
38
|
-
some i
|
|
39
|
-
lib := input.dependencies.libraries[i]
|
|
40
|
-
|
|
41
|
-
# Regex Explanation:
|
|
42
|
-
# ^ Start
|
|
43
|
-
# [a-zA-Z0-9_\-\.]+ Package name (alphanum, _, -, .)
|
|
44
|
-
# (\[[a-zA-Z0-9_\-\.,]+\])? Optional extras in brackets (e.g. [security,fast])
|
|
45
|
-
# == Must be strictly '=='
|
|
46
|
-
# [a-zA-Z0-9_\-\.\+]+ Version string (alphanum, _, -, ., + for metadata)
|
|
47
|
-
# $ End (No trailing constraints like ,>=2.0)
|
|
48
|
-
|
|
49
|
-
not regex.match("^[a-zA-Z0-9_\\-\\.]+(\\[[a-zA-Z0-9_\\-\\.,]+\\])?==[a-zA-Z0-9_\\-\\.\\+]+$", lib)
|
|
50
|
-
msg := sprintf("Compliance Violation: Library '%v' must be strictly pinned with '==' (e.g., 'pandas==2.0.1').", [lib])
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
# Rule 2 (Allowlist Enforcement): Libraries must be in TBOM
|
|
54
|
-
deny contains msg if {
|
|
55
|
-
some i
|
|
56
|
-
lib_str := input.dependencies.libraries[i]
|
|
57
|
-
|
|
58
|
-
# Extract library name using regex
|
|
59
|
-
# Pattern must support dots (for namespace packages) and stop before extras brackets or version specifiers.
|
|
60
|
-
parts := regex.find_all_string_submatch_n("^[a-zA-Z0-9_\\-\\.]+", lib_str, 1)
|
|
61
|
-
count(parts) > 0
|
|
62
|
-
lib_name := parts[0][0]
|
|
63
|
-
|
|
64
|
-
# Check if lib_name is in tbom (case-insensitive)
|
|
65
|
-
not is_in_tbom(lib_name)
|
|
66
|
-
|
|
67
|
-
msg := sprintf("Compliance Violation: Library '%v' is not in the Trusted Bill of Materials (TBOM).", [lib_name])
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
# Helper to safely check if lib is in TBOM
|
|
71
|
-
# Returns true if data.tbom exists AND contains the lib (case-insensitive)
|
|
72
|
-
is_in_tbom(lib) if {
|
|
73
|
-
# Lowercase the input library name
|
|
74
|
-
lower_lib := lower(lib)
|
|
75
|
-
|
|
76
|
-
# Check against TBOM
|
|
77
|
-
# If data.tbom is undefined, this rule body is undefined (false).
|
|
78
|
-
# Iterate through TBOM and compare lowercased versions
|
|
79
|
-
some tbom_lib in data.tbom
|
|
80
|
-
lower(tbom_lib) == lower_lib
|
|
81
|
-
}
|