coreason-manifest 0.3.0__tar.gz → 0.5.0__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.
- {coreason_manifest-0.3.0 → coreason_manifest-0.5.0}/PKG-INFO +25 -20
- {coreason_manifest-0.3.0 → coreason_manifest-0.5.0}/README.md +19 -17
- {coreason_manifest-0.3.0 → coreason_manifest-0.5.0}/pyproject.toml +9 -6
- {coreason_manifest-0.3.0 → coreason_manifest-0.5.0}/src/coreason_manifest/engine.py +56 -15
- {coreason_manifest-0.3.0 → coreason_manifest-0.5.0}/src/coreason_manifest/loader.py +116 -3
- {coreason_manifest-0.3.0 → coreason_manifest-0.5.0}/src/coreason_manifest/models.py +11 -0
- {coreason_manifest-0.3.0 → coreason_manifest-0.5.0}/src/coreason_manifest/schemas/agent.schema.json +14 -0
- coreason_manifest-0.5.0/src/coreason_manifest/server.py +123 -0
- {coreason_manifest-0.3.0 → coreason_manifest-0.5.0}/LICENSE +0 -0
- {coreason_manifest-0.3.0 → coreason_manifest-0.5.0}/NOTICE +0 -0
- {coreason_manifest-0.3.0 → coreason_manifest-0.5.0}/src/coreason_manifest/__init__.py +0 -0
- {coreason_manifest-0.3.0 → coreason_manifest-0.5.0}/src/coreason_manifest/errors.py +0 -0
- {coreason_manifest-0.3.0 → coreason_manifest-0.5.0}/src/coreason_manifest/integrity.py +0 -0
- {coreason_manifest-0.3.0 → coreason_manifest-0.5.0}/src/coreason_manifest/main.py +0 -0
- {coreason_manifest-0.3.0 → coreason_manifest-0.5.0}/src/coreason_manifest/policies/compliance.rego +0 -0
- {coreason_manifest-0.3.0 → coreason_manifest-0.5.0}/src/coreason_manifest/policies/tbom.json +0 -0
- {coreason_manifest-0.3.0 → coreason_manifest-0.5.0}/src/coreason_manifest/policy.py +0 -0
- {coreason_manifest-0.3.0 → coreason_manifest-0.5.0}/src/coreason_manifest/schemas/__init__.py +0 -0
- {coreason_manifest-0.3.0 → coreason_manifest-0.5.0}/src/coreason_manifest/utils/__init__.py +0 -0
- {coreason_manifest-0.3.0 → coreason_manifest-0.5.0}/src/coreason_manifest/utils/logger.py +0 -0
- {coreason_manifest-0.3.0 → coreason_manifest-0.5.0}/src/coreason_manifest/validator.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: coreason_manifest
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.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
|
|
|
@@ -68,12 +68,15 @@ Classifier: License :: Other/Proprietary License
|
|
|
68
68
|
Classifier: Programming Language :: Python :: 3.12
|
|
69
69
|
Classifier: Operating System :: OS Independent
|
|
70
70
|
Requires-Dist: aiofiles (>=23.2.1,<24.0.0)
|
|
71
|
-
Requires-Dist: anyio (>=4.
|
|
72
|
-
Requires-Dist:
|
|
71
|
+
Requires-Dist: anyio (>=4.12.1,<5.0.0)
|
|
72
|
+
Requires-Dist: coreason-identity (>=0.1.0,<0.2.0)
|
|
73
|
+
Requires-Dist: fastapi (>=0.111.0,<0.112.0)
|
|
74
|
+
Requires-Dist: httpx (>=0.28.1,<0.29.0)
|
|
73
75
|
Requires-Dist: jsonschema (>=4.25.1,<5.0.0)
|
|
74
76
|
Requires-Dist: loguru (>=0.7.2,<0.8.0)
|
|
75
77
|
Requires-Dist: pydantic (>=2.12.5,<3.0.0)
|
|
76
78
|
Requires-Dist: pyyaml (>=6.0.3,<7.0.0)
|
|
79
|
+
Requires-Dist: uvicorn (>=0.30.1,<0.31.0)
|
|
77
80
|
Project-URL: Documentation, https://github.com/CoReason-AI/coreason_manifest
|
|
78
81
|
Project-URL: Homepage, https://github.com/CoReason-AI/coreason_manifest
|
|
79
82
|
Project-URL: Repository, https://github.com/CoReason-AI/coreason_manifest
|
|
@@ -97,8 +100,10 @@ The definitive source of truth for CoReason-AI Asset definitions. "The Blueprint
|
|
|
97
100
|
* **Open Agent Specification (OAS) Validation:** Parses and validates agent definitions against a strict schema.
|
|
98
101
|
* **Compliance Enforcement:** Uses Open Policy Agent (OPA) / Rego to enforce complex business rules and allowlists.
|
|
99
102
|
* **Integrity Verification:** Calculates and verifies SHA256 hashes of the agent's source code to prevent tampering.
|
|
103
|
+
* **Automatic Schema Generation:** Inspects Python functions to generate Agent Interfaces, automatically handling `UserContext` injection.
|
|
100
104
|
* **Dependency Pinning:** Enforces strict version pinning for all library dependencies.
|
|
101
105
|
* **Trusted Bill of Materials (TBOM):** Validates libraries against an approved list.
|
|
106
|
+
* **Compliance Microservice:** Can be run as a standalone API server (Service C) for centralized validation.
|
|
102
107
|
|
|
103
108
|
## Installation
|
|
104
109
|
|
|
@@ -108,30 +113,30 @@ pip install coreason-manifest
|
|
|
108
113
|
|
|
109
114
|
## Usage
|
|
110
115
|
|
|
111
|
-
|
|
116
|
+
`coreason-manifest` supports two modes: **Library (CLI)** and **Server (Microservice)**.
|
|
117
|
+
|
|
118
|
+
### 1. Library Usage
|
|
119
|
+
|
|
120
|
+
Use the python library to validate local agent files and verify source integrity.
|
|
112
121
|
|
|
113
122
|
```python
|
|
114
|
-
from coreason_manifest import ManifestEngine, ManifestConfig
|
|
123
|
+
from coreason_manifest import ManifestEngine, ManifestConfig
|
|
115
124
|
|
|
116
|
-
#
|
|
117
|
-
config = ManifestConfig(policy_path="./policies/
|
|
125
|
+
# Initialize and Validate
|
|
126
|
+
config = ManifestConfig(policy_path="./policies/compliance.rego")
|
|
118
127
|
engine = ManifestEngine(config)
|
|
128
|
+
agent_def = engine.load_and_validate("agent.yaml", "./src")
|
|
129
|
+
```
|
|
119
130
|
|
|
120
|
-
|
|
121
|
-
try:
|
|
122
|
-
# This runs Schema Validation, Policy Enforcement, and Integrity Checks
|
|
123
|
-
agent_def = engine.load_and_validate(
|
|
124
|
-
manifest_path="./agents/payer_war_game/agent.yaml",
|
|
125
|
-
source_dir="./agents/payer_war_game/src"
|
|
126
|
-
)
|
|
127
|
-
print(f"Agent {agent_def.metadata.name} is compliant and ready to run.")
|
|
131
|
+
### 2. Server Mode
|
|
128
132
|
|
|
129
|
-
|
|
130
|
-
print(f"Compliance Failure: {e.violations}")
|
|
133
|
+
Run the package as a FastAPI server to provide a centralized compliance API.
|
|
131
134
|
|
|
132
|
-
|
|
133
|
-
|
|
135
|
+
```bash
|
|
136
|
+
uvicorn coreason_manifest.server:app --host 0.0.0.0 --port 8000
|
|
134
137
|
```
|
|
135
138
|
|
|
136
|
-
For
|
|
139
|
+
For full details, see the [Usage Documentation](docs/usage.md).
|
|
140
|
+
|
|
141
|
+
For detailed requirements and architecture, please refer to the [Product Requirements](docs/product_requirements.md) or [Requirements](docs/requirements.md).
|
|
137
142
|
|
|
@@ -16,8 +16,10 @@ The definitive source of truth for CoReason-AI Asset definitions. "The Blueprint
|
|
|
16
16
|
* **Open Agent Specification (OAS) Validation:** Parses and validates agent definitions against a strict schema.
|
|
17
17
|
* **Compliance Enforcement:** Uses Open Policy Agent (OPA) / Rego to enforce complex business rules and allowlists.
|
|
18
18
|
* **Integrity Verification:** Calculates and verifies SHA256 hashes of the agent's source code to prevent tampering.
|
|
19
|
+
* **Automatic Schema Generation:** Inspects Python functions to generate Agent Interfaces, automatically handling `UserContext` injection.
|
|
19
20
|
* **Dependency Pinning:** Enforces strict version pinning for all library dependencies.
|
|
20
21
|
* **Trusted Bill of Materials (TBOM):** Validates libraries against an approved list.
|
|
22
|
+
* **Compliance Microservice:** Can be run as a standalone API server (Service C) for centralized validation.
|
|
21
23
|
|
|
22
24
|
## Installation
|
|
23
25
|
|
|
@@ -27,29 +29,29 @@ pip install coreason-manifest
|
|
|
27
29
|
|
|
28
30
|
## Usage
|
|
29
31
|
|
|
30
|
-
|
|
32
|
+
`coreason-manifest` supports two modes: **Library (CLI)** and **Server (Microservice)**.
|
|
33
|
+
|
|
34
|
+
### 1. Library Usage
|
|
35
|
+
|
|
36
|
+
Use the python library to validate local agent files and verify source integrity.
|
|
31
37
|
|
|
32
38
|
```python
|
|
33
|
-
from coreason_manifest import ManifestEngine, ManifestConfig
|
|
39
|
+
from coreason_manifest import ManifestEngine, ManifestConfig
|
|
34
40
|
|
|
35
|
-
#
|
|
36
|
-
config = ManifestConfig(policy_path="./policies/
|
|
41
|
+
# Initialize and Validate
|
|
42
|
+
config = ManifestConfig(policy_path="./policies/compliance.rego")
|
|
37
43
|
engine = ManifestEngine(config)
|
|
44
|
+
agent_def = engine.load_and_validate("agent.yaml", "./src")
|
|
45
|
+
```
|
|
38
46
|
|
|
39
|
-
|
|
40
|
-
try:
|
|
41
|
-
# This runs Schema Validation, Policy Enforcement, and Integrity Checks
|
|
42
|
-
agent_def = engine.load_and_validate(
|
|
43
|
-
manifest_path="./agents/payer_war_game/agent.yaml",
|
|
44
|
-
source_dir="./agents/payer_war_game/src"
|
|
45
|
-
)
|
|
46
|
-
print(f"Agent {agent_def.metadata.name} is compliant and ready to run.")
|
|
47
|
+
### 2. Server Mode
|
|
47
48
|
|
|
48
|
-
|
|
49
|
-
print(f"Compliance Failure: {e.violations}")
|
|
49
|
+
Run the package as a FastAPI server to provide a centralized compliance API.
|
|
50
50
|
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
```bash
|
|
52
|
+
uvicorn coreason_manifest.server:app --host 0.0.0.0 --port 8000
|
|
53
53
|
```
|
|
54
54
|
|
|
55
|
-
For
|
|
55
|
+
For full details, see the [Usage Documentation](docs/usage.md).
|
|
56
|
+
|
|
57
|
+
For detailed requirements and architecture, please refer to the [Product Requirements](docs/product_requirements.md) or [Requirements](docs/requirements.md).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "coreason_manifest"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.5.0"
|
|
4
4
|
description = "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
|
authors = ["Gowtham A Rao <gowtham.rao@coreason.ai>"]
|
|
6
6
|
license = "Prosperity-3.0"
|
|
@@ -13,13 +13,16 @@ loguru = "^0.7.2"
|
|
|
13
13
|
pydantic = "^2.12.5"
|
|
14
14
|
jsonschema = "^4.25.1"
|
|
15
15
|
pyyaml = "^6.0.3"
|
|
16
|
-
anyio = "^4.
|
|
17
|
-
|
|
16
|
+
anyio = "^4.12.1"
|
|
17
|
+
coreason-identity = "^0.1.0"
|
|
18
|
+
httpx = "^0.28.1"
|
|
18
19
|
aiofiles = "^23.2.1"
|
|
20
|
+
fastapi = "^0.111.0"
|
|
21
|
+
uvicorn = "^0.30.1"
|
|
19
22
|
|
|
20
23
|
[tool.poetry.group.dev.dependencies]
|
|
21
24
|
pytest = "^8.2.2"
|
|
22
|
-
ruff = "^0.
|
|
25
|
+
ruff = "^0.5.0"
|
|
23
26
|
pre-commit = "^3.7.1"
|
|
24
27
|
pytest-cov = "^5.0.0"
|
|
25
28
|
mkdocs = "^1.6.0"
|
|
@@ -27,7 +30,7 @@ mkdocs-material = "^9.5.26"
|
|
|
27
30
|
pydantic = "^2.12.5"
|
|
28
31
|
mypy = "^1.19.1"
|
|
29
32
|
types-aiofiles = "^23.2.0"
|
|
30
|
-
pytest-asyncio = "^0.23.
|
|
33
|
+
pytest-asyncio = "^0.23.7"
|
|
31
34
|
|
|
32
35
|
[build-system]
|
|
33
36
|
requires = ["poetry-core"]
|
|
@@ -35,7 +38,7 @@ build-backend = "poetry.core.masonry.api"
|
|
|
35
38
|
|
|
36
39
|
[project]
|
|
37
40
|
name = "coreason_manifest"
|
|
38
|
-
version = "0.
|
|
41
|
+
version = "0.5.0"
|
|
39
42
|
description = "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."
|
|
40
43
|
readme = "README.md"
|
|
41
44
|
requires-python = ">=3.11"
|
|
@@ -80,29 +80,30 @@ class ManifestEngineAsync:
|
|
|
80
80
|
# Clean up resources if necessary.
|
|
81
81
|
pass
|
|
82
82
|
|
|
83
|
-
async def
|
|
84
|
-
"""
|
|
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
|
|
91
|
+
|
|
92
|
+
Does NOT perform Integrity Check (hashing).
|
|
85
93
|
|
|
86
94
|
Args:
|
|
87
|
-
|
|
88
|
-
source_dir: Path to the source code directory.
|
|
95
|
+
raw_data: The raw dictionary of the manifest.
|
|
89
96
|
|
|
90
97
|
Returns:
|
|
91
|
-
AgentDefinition: The fully validated
|
|
98
|
+
AgentDefinition: The fully validated agent definition.
|
|
92
99
|
|
|
93
100
|
Raises:
|
|
94
101
|
ManifestSyntaxError: If structure or schema is invalid.
|
|
95
102
|
PolicyViolationError: If business rules are violated.
|
|
96
|
-
IntegrityCompromisedError: If source code hash does not match.
|
|
97
|
-
FileNotFoundError: If files are missing.
|
|
98
103
|
"""
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
logger.info(f"Validating Agent Manifest: {manifest_path}")
|
|
103
|
-
|
|
104
|
-
# 1. Load Raw YAML (I/O)
|
|
105
|
-
raw_data = await ManifestLoader.load_raw_from_file_async(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)
|
|
106
107
|
|
|
107
108
|
# 2. Schema Validation
|
|
108
109
|
logger.debug("Running Schema Validation...")
|
|
@@ -128,13 +129,42 @@ class ManifestEngineAsync:
|
|
|
128
129
|
logger.info(f"Policy Check: Fail - {duration_ms:.2f}ms")
|
|
129
130
|
raise
|
|
130
131
|
|
|
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
|
+
|
|
131
161
|
# 5. Integrity Check (Heavy I/O and CPU)
|
|
132
162
|
logger.debug("Verifying Integrity...")
|
|
133
163
|
# IntegrityChecker.verify is synchronous and does heavy IO, so we wrap it.
|
|
134
164
|
await anyio.to_thread.run_sync(IntegrityChecker.verify, agent_def, source_dir, manifest_path)
|
|
135
165
|
|
|
136
166
|
logger.info("Agent validation successful.")
|
|
137
|
-
return
|
|
167
|
+
return agent_def
|
|
138
168
|
|
|
139
169
|
|
|
140
170
|
class ManifestEngine:
|
|
@@ -179,3 +209,14 @@ class ManifestEngine:
|
|
|
179
209
|
AgentDefinition: The fully validated and verified agent definition.
|
|
180
210
|
"""
|
|
181
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))
|
|
@@ -7,15 +7,23 @@ dictionaries, normalizing the data, and converting it into Pydantic models.
|
|
|
7
7
|
|
|
8
8
|
from __future__ import annotations
|
|
9
9
|
|
|
10
|
+
import inspect
|
|
10
11
|
from pathlib import Path
|
|
11
|
-
from typing import Any, Union
|
|
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
|
|
12
19
|
|
|
13
20
|
import aiofiles
|
|
14
21
|
import yaml
|
|
15
|
-
from
|
|
22
|
+
from coreason_identity import UserContext
|
|
23
|
+
from pydantic import ValidationError, create_model
|
|
16
24
|
|
|
17
25
|
from coreason_manifest.errors import ManifestSyntaxError
|
|
18
|
-
from coreason_manifest.models import AgentDefinition
|
|
26
|
+
from coreason_manifest.models import AgentDefinition, AgentInterface
|
|
19
27
|
|
|
20
28
|
|
|
21
29
|
class ManifestLoader:
|
|
@@ -156,3 +164,108 @@ class ManifestLoader:
|
|
|
156
164
|
while version and version[0] in ("v", "V"):
|
|
157
165
|
version = version[1:]
|
|
158
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
|
+
)
|
|
@@ -20,6 +20,7 @@ from pydantic import (
|
|
|
20
20
|
Field,
|
|
21
21
|
PlainSerializer,
|
|
22
22
|
field_validator,
|
|
23
|
+
model_validator,
|
|
23
24
|
)
|
|
24
25
|
from typing_extensions import Annotated
|
|
25
26
|
|
|
@@ -86,6 +87,7 @@ class AgentMetadata(BaseModel):
|
|
|
86
87
|
name: str = Field(..., min_length=1, description="Name of the Agent.")
|
|
87
88
|
author: str = Field(..., min_length=1, description="Author of the Agent.")
|
|
88
89
|
created_at: datetime = Field(..., description="Creation timestamp (ISO 8601).")
|
|
90
|
+
requires_auth: bool = Field(default=False, description="Whether the agent requires user authentication.")
|
|
89
91
|
|
|
90
92
|
|
|
91
93
|
class AgentInterface(BaseModel):
|
|
@@ -100,6 +102,7 @@ class AgentInterface(BaseModel):
|
|
|
100
102
|
|
|
101
103
|
inputs: ImmutableDict = Field(..., description="Typed arguments the agent accepts (JSON Schema).")
|
|
102
104
|
outputs: ImmutableDict = Field(..., description="Typed structure of the result.")
|
|
105
|
+
injected_params: List[str] = Field(default_factory=list, description="List of parameters injected by the system.")
|
|
103
106
|
|
|
104
107
|
|
|
105
108
|
class Step(BaseModel):
|
|
@@ -218,3 +221,11 @@ class AgentDefinition(BaseModel):
|
|
|
218
221
|
pattern=r"^[a-fA-F0-9]{64}$",
|
|
219
222
|
description="SHA256 hash of the source code.",
|
|
220
223
|
)
|
|
224
|
+
|
|
225
|
+
@model_validator(mode="after")
|
|
226
|
+
def validate_auth_requirements(self) -> AgentDefinition:
|
|
227
|
+
"""Validate that agents requiring auth have user_context injected."""
|
|
228
|
+
if self.metadata.requires_auth:
|
|
229
|
+
if "user_context" not in self.interface.injected_params:
|
|
230
|
+
raise ValueError("Agent requires authentication but 'user_context' is not an injected parameter.")
|
|
231
|
+
return self
|
{coreason_manifest-0.3.0 → coreason_manifest-0.5.0}/src/coreason_manifest/schemas/agent.schema.json
RENAMED
|
@@ -41,6 +41,14 @@
|
|
|
41
41
|
"description": "Typed structure of the result.",
|
|
42
42
|
"title": "Outputs",
|
|
43
43
|
"type": "object"
|
|
44
|
+
},
|
|
45
|
+
"injected_params": {
|
|
46
|
+
"description": "List of parameters injected by the system.",
|
|
47
|
+
"items": {
|
|
48
|
+
"type": "string"
|
|
49
|
+
},
|
|
50
|
+
"title": "Injected Params",
|
|
51
|
+
"type": "array"
|
|
44
52
|
}
|
|
45
53
|
},
|
|
46
54
|
"required": [
|
|
@@ -83,6 +91,12 @@
|
|
|
83
91
|
"format": "date-time",
|
|
84
92
|
"title": "Created At",
|
|
85
93
|
"type": "string"
|
|
94
|
+
},
|
|
95
|
+
"requires_auth": {
|
|
96
|
+
"default": false,
|
|
97
|
+
"description": "Whether the agent requires user authentication.",
|
|
98
|
+
"title": "Requires Auth",
|
|
99
|
+
"type": "boolean"
|
|
86
100
|
}
|
|
87
101
|
},
|
|
88
102
|
"required": [
|
|
@@ -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}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{coreason_manifest-0.3.0 → coreason_manifest-0.5.0}/src/coreason_manifest/policies/compliance.rego
RENAMED
|
File without changes
|
{coreason_manifest-0.3.0 → coreason_manifest-0.5.0}/src/coreason_manifest/policies/tbom.json
RENAMED
|
File without changes
|
|
File without changes
|
{coreason_manifest-0.3.0 → coreason_manifest-0.5.0}/src/coreason_manifest/schemas/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|