coreason-manifest 0.1.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.1.0/LICENSE +57 -0
- coreason_manifest-0.1.0/NOTICE +8 -0
- coreason_manifest-0.1.0/PKG-INFO +114 -0
- coreason_manifest-0.1.0/README.md +35 -0
- coreason_manifest-0.1.0/pyproject.toml +71 -0
- coreason_manifest-0.1.0/src/coreason_manifest/__init__.py +41 -0
- coreason_manifest-0.1.0/src/coreason_manifest/engine.py +117 -0
- coreason_manifest-0.1.0/src/coreason_manifest/errors.py +28 -0
- coreason_manifest-0.1.0/src/coreason_manifest/integrity.py +136 -0
- coreason_manifest-0.1.0/src/coreason_manifest/loader.py +125 -0
- coreason_manifest-0.1.0/src/coreason_manifest/main.py +16 -0
- coreason_manifest-0.1.0/src/coreason_manifest/models.py +156 -0
- coreason_manifest-0.1.0/src/coreason_manifest/policies/compliance.rego +81 -0
- coreason_manifest-0.1.0/src/coreason_manifest/policies/tbom.json +14 -0
- coreason_manifest-0.1.0/src/coreason_manifest/policy.py +132 -0
- coreason_manifest-0.1.0/src/coreason_manifest/schemas/__init__.py +1 -0
- coreason_manifest-0.1.0/src/coreason_manifest/schemas/agent.schema.json +209 -0
- coreason_manifest-0.1.0/src/coreason_manifest/utils/__init__.py +13 -0
- coreason_manifest-0.1.0/src/coreason_manifest/utils/logger.py +48 -0
- coreason_manifest-0.1.0/src/coreason_manifest/validator.py +56 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# The Prosperity Public License 3.0.0
|
|
2
|
+
|
|
3
|
+
Contributor: CoReason, Inc.
|
|
4
|
+
|
|
5
|
+
Source Code: https://github.com/CoReason-AI/coreason_manifest
|
|
6
|
+
|
|
7
|
+
## Purpose
|
|
8
|
+
|
|
9
|
+
This license allows you to use and share this software for noncommercial purposes for free and to try this software for commercial purposes for thirty days.
|
|
10
|
+
|
|
11
|
+
## Agreement
|
|
12
|
+
|
|
13
|
+
In order to receive this license, you have to agree to its rules. Those rules are both obligations under that agreement and conditions to your license. Don't do anything with this software that triggers a rule you can't or won't follow.
|
|
14
|
+
|
|
15
|
+
## Notices
|
|
16
|
+
|
|
17
|
+
Make sure everyone who gets a copy of any part of this software from you, with or without changes, also gets the text of this license and the contributor and source code lines above.
|
|
18
|
+
|
|
19
|
+
## Commercial Trial
|
|
20
|
+
|
|
21
|
+
Limit your use of this software for commercial purposes to a thirty-day trial period. If you use this software for work, your company gets one trial period for all personnel, not one trial per person.
|
|
22
|
+
|
|
23
|
+
## Contributions Back
|
|
24
|
+
|
|
25
|
+
Developing feedback, changes, or additions that you contribute back to the contributor on the terms of a standardized public software license such as [the Blue Oak Model License 1.0.0](https://blueoakcouncil.org/license/1.0.0), [the Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0.html), [the MIT license](https://spdx.org/licenses/MIT.html), or [the two-clause BSD license](https://spdx.org/licenses/BSD-2-Clause.html) doesn't count as use for a commercial purpose.
|
|
26
|
+
|
|
27
|
+
## Personal Uses
|
|
28
|
+
|
|
29
|
+
Personal use for research, experiment, and testing for the benefit of public knowledge, personal study, private entertainment, hobby projects, amateur pursuits, or religious observance, without any anticipated commercial application, doesn't count as use for a commercial purpose.
|
|
30
|
+
|
|
31
|
+
## Noncommercial Organizations
|
|
32
|
+
|
|
33
|
+
Use by any charitable organization, educational institution, public research organization, public safety or health organization, environmental protection organization, or government institution doesn't count as use for a commercial purpose regardless of the source of funding or obligations resulting from the funding.
|
|
34
|
+
|
|
35
|
+
## Defense
|
|
36
|
+
|
|
37
|
+
Don't make any legal claim against anyone accusing this software, with or without changes, alone or with other technology, of infringing any patent.
|
|
38
|
+
|
|
39
|
+
## Copyright
|
|
40
|
+
|
|
41
|
+
The contributor licenses you to do everything with this software that would otherwise infringe their copyright in it.
|
|
42
|
+
|
|
43
|
+
## Patent
|
|
44
|
+
|
|
45
|
+
The contributor licenses you to do everything with this software that would otherwise infringe any patents they can license or become able to license.
|
|
46
|
+
|
|
47
|
+
## Reliability
|
|
48
|
+
|
|
49
|
+
The contributor can't revoke this license.
|
|
50
|
+
|
|
51
|
+
## Excuse
|
|
52
|
+
|
|
53
|
+
You're excused for unknowingly breaking [Notices](#notices) if you take all practical steps to comply within thirty days of learning you broke the rule.
|
|
54
|
+
|
|
55
|
+
## No Liability
|
|
56
|
+
|
|
57
|
+
***As far as the law allows, this software comes as is, without any warranty or condition, and the contributor won't be liable to anyone for any damages related to this software or this license, under any kind of legal claim.***
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
Copyright (c) 2025 CoReason, Inc.. All Rights Reserved
|
|
2
|
+
|
|
3
|
+
This software is licensed under the Prosperity Public License 3.0.0.
|
|
4
|
+
The issuer of the Prosperity Public License for this software is CoReason, Inc..
|
|
5
|
+
|
|
6
|
+
For a commercial version of this software, please contact us at gowtham.rao@coreason.ai.
|
|
7
|
+
|
|
8
|
+
GENESIS COMMIT: Initializing repository coreason_manifest per CoReason Clean Room Protocol PIP-001. This repository is established as an independently created De Novo development environment, commencing on 2026-01-01. I, Gowtham A Rao certify that this date is subsequent to my individual Temporal Firewall Date.
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: coreason_manifest
|
|
3
|
+
Version: 0.1.0
|
|
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
|
+
License: # The Prosperity Public License 3.0.0
|
|
6
|
+
|
|
7
|
+
Contributor: CoReason, Inc.
|
|
8
|
+
|
|
9
|
+
Source Code: https://github.com/CoReason-AI/coreason_manifest
|
|
10
|
+
|
|
11
|
+
## Purpose
|
|
12
|
+
|
|
13
|
+
This license allows you to use and share this software for noncommercial purposes for free and to try this software for commercial purposes for thirty days.
|
|
14
|
+
|
|
15
|
+
## Agreement
|
|
16
|
+
|
|
17
|
+
In order to receive this license, you have to agree to its rules. Those rules are both obligations under that agreement and conditions to your license. Don't do anything with this software that triggers a rule you can't or won't follow.
|
|
18
|
+
|
|
19
|
+
## Notices
|
|
20
|
+
|
|
21
|
+
Make sure everyone who gets a copy of any part of this software from you, with or without changes, also gets the text of this license and the contributor and source code lines above.
|
|
22
|
+
|
|
23
|
+
## Commercial Trial
|
|
24
|
+
|
|
25
|
+
Limit your use of this software for commercial purposes to a thirty-day trial period. If you use this software for work, your company gets one trial period for all personnel, not one trial per person.
|
|
26
|
+
|
|
27
|
+
## Contributions Back
|
|
28
|
+
|
|
29
|
+
Developing feedback, changes, or additions that you contribute back to the contributor on the terms of a standardized public software license such as [the Blue Oak Model License 1.0.0](https://blueoakcouncil.org/license/1.0.0), [the Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0.html), [the MIT license](https://spdx.org/licenses/MIT.html), or [the two-clause BSD license](https://spdx.org/licenses/BSD-2-Clause.html) doesn't count as use for a commercial purpose.
|
|
30
|
+
|
|
31
|
+
## Personal Uses
|
|
32
|
+
|
|
33
|
+
Personal use for research, experiment, and testing for the benefit of public knowledge, personal study, private entertainment, hobby projects, amateur pursuits, or religious observance, without any anticipated commercial application, doesn't count as use for a commercial purpose.
|
|
34
|
+
|
|
35
|
+
## Noncommercial Organizations
|
|
36
|
+
|
|
37
|
+
Use by any charitable organization, educational institution, public research organization, public safety or health organization, environmental protection organization, or government institution doesn't count as use for a commercial purpose regardless of the source of funding or obligations resulting from the funding.
|
|
38
|
+
|
|
39
|
+
## Defense
|
|
40
|
+
|
|
41
|
+
Don't make any legal claim against anyone accusing this software, with or without changes, alone or with other technology, of infringing any patent.
|
|
42
|
+
|
|
43
|
+
## Copyright
|
|
44
|
+
|
|
45
|
+
The contributor licenses you to do everything with this software that would otherwise infringe their copyright in it.
|
|
46
|
+
|
|
47
|
+
## Patent
|
|
48
|
+
|
|
49
|
+
The contributor licenses you to do everything with this software that would otherwise infringe any patents they can license or become able to license.
|
|
50
|
+
|
|
51
|
+
## Reliability
|
|
52
|
+
|
|
53
|
+
The contributor can't revoke this license.
|
|
54
|
+
|
|
55
|
+
## Excuse
|
|
56
|
+
|
|
57
|
+
You're excused for unknowingly breaking [Notices](#notices) if you take all practical steps to comply within thirty days of learning you broke the rule.
|
|
58
|
+
|
|
59
|
+
## No Liability
|
|
60
|
+
|
|
61
|
+
***As far as the law allows, this software comes as is, without any warranty or condition, and the contributor won't be liable to anyone for any damages related to this software or this license, under any kind of legal claim.***
|
|
62
|
+
License-File: LICENSE
|
|
63
|
+
License-File: NOTICE
|
|
64
|
+
Author: Gowtham A Rao
|
|
65
|
+
Author-email: gowtham.rao@coreason.ai
|
|
66
|
+
Requires-Python: >=3.11
|
|
67
|
+
Classifier: License :: Other/Proprietary License
|
|
68
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
69
|
+
Classifier: Operating System :: OS Independent
|
|
70
|
+
Requires-Dist: jsonschema (>=4.25.1,<5.0.0)
|
|
71
|
+
Requires-Dist: loguru (>=0.7.2,<0.8.0)
|
|
72
|
+
Requires-Dist: pydantic (>=2.12.5,<3.0.0)
|
|
73
|
+
Requires-Dist: pyyaml (>=6.0.3,<7.0.0)
|
|
74
|
+
Project-URL: Documentation, https://github.com/CoReason-AI/coreason_manifest
|
|
75
|
+
Project-URL: Homepage, https://github.com/CoReason-AI/coreason_manifest
|
|
76
|
+
Project-URL: Repository, https://github.com/CoReason-AI/coreason_manifest
|
|
77
|
+
Description-Content-Type: text/markdown
|
|
78
|
+
|
|
79
|
+
# coreason-manifest
|
|
80
|
+
|
|
81
|
+
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.
|
|
82
|
+
|
|
83
|
+
[](https://github.com/CoReason-AI/coreason_manifest/actions/workflows/ci.yml)
|
|
84
|
+
|
|
85
|
+
## Getting Started
|
|
86
|
+
|
|
87
|
+
### Prerequisites
|
|
88
|
+
|
|
89
|
+
- Python 3.12+
|
|
90
|
+
- Poetry
|
|
91
|
+
|
|
92
|
+
### Installation
|
|
93
|
+
|
|
94
|
+
1. Clone the repository:
|
|
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
|
+
```
|
|
103
|
+
|
|
104
|
+
### Usage
|
|
105
|
+
|
|
106
|
+
- Run the linter:
|
|
107
|
+
```sh
|
|
108
|
+
poetry run pre-commit run --all-files
|
|
109
|
+
```
|
|
110
|
+
- Run the tests:
|
|
111
|
+
```sh
|
|
112
|
+
poetry run pytest
|
|
113
|
+
```
|
|
114
|
+
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# coreason-manifest
|
|
2
|
+
|
|
3
|
+
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.
|
|
4
|
+
|
|
5
|
+
[](https://github.com/CoReason-AI/coreason_manifest/actions/workflows/ci.yml)
|
|
6
|
+
|
|
7
|
+
## Getting Started
|
|
8
|
+
|
|
9
|
+
### Prerequisites
|
|
10
|
+
|
|
11
|
+
- Python 3.12+
|
|
12
|
+
- Poetry
|
|
13
|
+
|
|
14
|
+
### Installation
|
|
15
|
+
|
|
16
|
+
1. Clone the repository:
|
|
17
|
+
```sh
|
|
18
|
+
git clone https://github.com/example/example.git
|
|
19
|
+
cd my_python_project
|
|
20
|
+
```
|
|
21
|
+
2. Install dependencies:
|
|
22
|
+
```sh
|
|
23
|
+
poetry install
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Usage
|
|
27
|
+
|
|
28
|
+
- Run the linter:
|
|
29
|
+
```sh
|
|
30
|
+
poetry run pre-commit run --all-files
|
|
31
|
+
```
|
|
32
|
+
- Run the tests:
|
|
33
|
+
```sh
|
|
34
|
+
poetry run pytest
|
|
35
|
+
```
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
name = "coreason_manifest"
|
|
3
|
+
version = "0.1.0"
|
|
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
|
+
authors = ["Gowtham A Rao <gowtham.rao@coreason.ai>"]
|
|
6
|
+
license = "Prosperity-3.0"
|
|
7
|
+
readme = "README.md"
|
|
8
|
+
packages = [{include = "coreason_manifest", from = "src"}]
|
|
9
|
+
|
|
10
|
+
[tool.poetry.dependencies]
|
|
11
|
+
python = ">=3.12, <3.15"
|
|
12
|
+
loguru = "^0.7.2"
|
|
13
|
+
pydantic = "^2.12.5"
|
|
14
|
+
jsonschema = "^4.25.1"
|
|
15
|
+
pyyaml = "^6.0.3"
|
|
16
|
+
|
|
17
|
+
[tool.poetry.group.dev.dependencies]
|
|
18
|
+
pytest = "^8.2.2"
|
|
19
|
+
ruff = "^0.4.8"
|
|
20
|
+
pre-commit = "^3.7.1"
|
|
21
|
+
pytest-cov = "^5.0.0"
|
|
22
|
+
mkdocs = "^1.6.0"
|
|
23
|
+
mkdocs-material = "^9.5.26"
|
|
24
|
+
pydantic = "^2.12.5"
|
|
25
|
+
mypy = "^1.19.1"
|
|
26
|
+
|
|
27
|
+
[build-system]
|
|
28
|
+
requires = ["poetry-core"]
|
|
29
|
+
build-backend = "poetry.core.masonry.api"
|
|
30
|
+
|
|
31
|
+
[project]
|
|
32
|
+
name = "coreason_manifest"
|
|
33
|
+
version = "0.1.0"
|
|
34
|
+
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."
|
|
35
|
+
readme = "README.md"
|
|
36
|
+
requires-python = ">=3.11"
|
|
37
|
+
authors = [
|
|
38
|
+
{ name = "Gowtham A Rao", email = "gowtham.rao@coreason.ai" },
|
|
39
|
+
]
|
|
40
|
+
license = { file = "LICENSE" }
|
|
41
|
+
classifiers = [
|
|
42
|
+
"License :: Other/Proprietary License",
|
|
43
|
+
"Programming Language :: Python :: 3.12",
|
|
44
|
+
"Operating System :: OS Independent",
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
[project.urls]
|
|
48
|
+
Homepage = "https://github.com/CoReason-AI/coreason_manifest"
|
|
49
|
+
Repository = "https://github.com/CoReason-AI/coreason_manifest"
|
|
50
|
+
Documentation = "https://github.com/CoReason-AI/coreason_manifest"
|
|
51
|
+
|
|
52
|
+
[tool.ruff]
|
|
53
|
+
line-length = 120
|
|
54
|
+
target-version = "py312"
|
|
55
|
+
|
|
56
|
+
[tool.ruff.lint]
|
|
57
|
+
select = ["E", "F", "B", "I"]
|
|
58
|
+
ignore = []
|
|
59
|
+
|
|
60
|
+
[tool.mypy]
|
|
61
|
+
python_version = "3.12"
|
|
62
|
+
strict = true
|
|
63
|
+
ignore_missing_imports = true
|
|
64
|
+
plugins = ["pydantic.mypy"]
|
|
65
|
+
|
|
66
|
+
[tool.pytest.ini_options]
|
|
67
|
+
addopts = "--cov=src --cov-report=term-missing --cov-fail-under=100"
|
|
68
|
+
testpaths = ["tests"]
|
|
69
|
+
|
|
70
|
+
[tool.coverage.run]
|
|
71
|
+
omit = ["tests/*", "/tmp/*"]
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Prosperity-3.0
|
|
2
|
+
from .engine import ManifestConfig, ManifestEngine
|
|
3
|
+
from .errors import (
|
|
4
|
+
IntegrityCompromisedError,
|
|
5
|
+
ManifestError,
|
|
6
|
+
ManifestSyntaxError,
|
|
7
|
+
PolicyViolationError,
|
|
8
|
+
)
|
|
9
|
+
from .integrity import IntegrityChecker
|
|
10
|
+
from .loader import ManifestLoader
|
|
11
|
+
from .models import (
|
|
12
|
+
AgentDefinition,
|
|
13
|
+
AgentDependencies,
|
|
14
|
+
AgentInterface,
|
|
15
|
+
AgentMetadata,
|
|
16
|
+
AgentTopology,
|
|
17
|
+
ModelConfig,
|
|
18
|
+
Step,
|
|
19
|
+
)
|
|
20
|
+
from .policy import PolicyEnforcer
|
|
21
|
+
from .validator import SchemaValidator
|
|
22
|
+
|
|
23
|
+
__all__ = [
|
|
24
|
+
"AgentDefinition",
|
|
25
|
+
"AgentDependencies",
|
|
26
|
+
"AgentInterface",
|
|
27
|
+
"AgentMetadata",
|
|
28
|
+
"AgentTopology",
|
|
29
|
+
"IntegrityChecker",
|
|
30
|
+
"IntegrityCompromisedError",
|
|
31
|
+
"ManifestConfig",
|
|
32
|
+
"ManifestEngine",
|
|
33
|
+
"ManifestError",
|
|
34
|
+
"ManifestLoader",
|
|
35
|
+
"ManifestSyntaxError",
|
|
36
|
+
"ModelConfig",
|
|
37
|
+
"PolicyEnforcer",
|
|
38
|
+
"PolicyViolationError",
|
|
39
|
+
"SchemaValidator",
|
|
40
|
+
"Step",
|
|
41
|
+
]
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# Prosperity-3.0
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import time
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import List, Optional, Union
|
|
8
|
+
|
|
9
|
+
from coreason_manifest.integrity import IntegrityChecker
|
|
10
|
+
from coreason_manifest.loader import ManifestLoader
|
|
11
|
+
from coreason_manifest.models import AgentDefinition
|
|
12
|
+
from coreason_manifest.policy import PolicyEnforcer
|
|
13
|
+
|
|
14
|
+
# Import logger from utils to ensure configuration is applied
|
|
15
|
+
from coreason_manifest.utils.logger import logger
|
|
16
|
+
from coreason_manifest.validator import SchemaValidator
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class ManifestConfig:
|
|
21
|
+
"""Configuration for the ManifestEngine."""
|
|
22
|
+
|
|
23
|
+
policy_path: Union[str, Path]
|
|
24
|
+
opa_path: str = "opa"
|
|
25
|
+
tbom_path: Optional[Union[str, Path]] = None
|
|
26
|
+
extra_data_paths: List[Union[str, Path]] = field(default_factory=list)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class ManifestEngine:
|
|
30
|
+
"""
|
|
31
|
+
The main entry point for verifying and loading Agent Manifests.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def __init__(self, config: ManifestConfig) -> None:
|
|
35
|
+
"""
|
|
36
|
+
Initialize the ManifestEngine.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
config: Configuration including policy path and OPA path.
|
|
40
|
+
"""
|
|
41
|
+
self.config = config
|
|
42
|
+
self.schema_validator = SchemaValidator()
|
|
43
|
+
|
|
44
|
+
# Collect data paths
|
|
45
|
+
data_paths = list(config.extra_data_paths)
|
|
46
|
+
if config.tbom_path:
|
|
47
|
+
data_paths.append(config.tbom_path)
|
|
48
|
+
|
|
49
|
+
self.policy_enforcer = PolicyEnforcer(
|
|
50
|
+
policy_path=config.policy_path,
|
|
51
|
+
opa_path=config.opa_path,
|
|
52
|
+
data_paths=data_paths,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
def load_and_validate(self, manifest_path: Union[str, Path], source_dir: Union[str, Path]) -> AgentDefinition:
|
|
56
|
+
"""
|
|
57
|
+
Loads, validates, and verifies an Agent Manifest.
|
|
58
|
+
|
|
59
|
+
Steps:
|
|
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).
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
manifest_path: Path to the agent.yaml file.
|
|
68
|
+
source_dir: Path to the source code directory.
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
AgentDefinition: The fully validated and verified agent definition.
|
|
72
|
+
|
|
73
|
+
Raises:
|
|
74
|
+
ManifestSyntaxError: If structure or schema is invalid.
|
|
75
|
+
PolicyViolationError: If business rules are violated.
|
|
76
|
+
IntegrityCompromisedError: If source code hash does not match.
|
|
77
|
+
FileNotFoundError: If files are missing.
|
|
78
|
+
"""
|
|
79
|
+
manifest_path = Path(manifest_path)
|
|
80
|
+
source_dir = Path(source_dir)
|
|
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)
|
|
86
|
+
|
|
87
|
+
# 2. Schema Validation
|
|
88
|
+
logger.debug("Running Schema Validation...")
|
|
89
|
+
self.schema_validator.validate(raw_data)
|
|
90
|
+
|
|
91
|
+
# 3. Model Conversion (Normalization)
|
|
92
|
+
logger.debug("Converting to AgentDefinition...")
|
|
93
|
+
agent_def = ManifestLoader.load_from_dict(raw_data)
|
|
94
|
+
logger.info(f"Validating Agent {agent_def.metadata.id} v{agent_def.metadata.version}")
|
|
95
|
+
|
|
96
|
+
# 4. Policy Enforcement
|
|
97
|
+
logger.debug("Enforcing Policies...")
|
|
98
|
+
# 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
|
+
normalized_data = agent_def.model_dump(mode="json")
|
|
102
|
+
start_time = time.perf_counter()
|
|
103
|
+
try:
|
|
104
|
+
self.policy_enforcer.evaluate(normalized_data)
|
|
105
|
+
duration_ms = (time.perf_counter() - start_time) * 1000
|
|
106
|
+
logger.info(f"Policy Check: Pass - {duration_ms:.2f}ms")
|
|
107
|
+
except Exception:
|
|
108
|
+
duration_ms = (time.perf_counter() - start_time) * 1000
|
|
109
|
+
logger.info(f"Policy Check: Fail - {duration_ms:.2f}ms")
|
|
110
|
+
raise
|
|
111
|
+
|
|
112
|
+
# 5. Integrity Check
|
|
113
|
+
logger.debug("Verifying Integrity...")
|
|
114
|
+
IntegrityChecker.verify(agent_def, source_dir, manifest_path=manifest_path)
|
|
115
|
+
|
|
116
|
+
logger.info("Agent validation successful.")
|
|
117
|
+
return agent_def
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Prosperity-3.0
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class ManifestError(Exception):
|
|
6
|
+
"""Base exception for coreason_manifest errors."""
|
|
7
|
+
|
|
8
|
+
pass
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ManifestSyntaxError(ManifestError):
|
|
12
|
+
"""Raised when the manifest YAML is invalid or missing required fields."""
|
|
13
|
+
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class PolicyViolationError(ManifestError):
|
|
18
|
+
"""Raised when the agent violates a compliance policy."""
|
|
19
|
+
|
|
20
|
+
def __init__(self, message: str, violations: list[str] | None = None) -> None:
|
|
21
|
+
super().__init__(message)
|
|
22
|
+
self.violations = violations or []
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class IntegrityCompromisedError(ManifestError):
|
|
26
|
+
"""Raised when the source code hash does not match the manifest."""
|
|
27
|
+
|
|
28
|
+
pass
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# Prosperity-3.0
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import hashlib
|
|
5
|
+
import os
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import List, Optional, Set, Union
|
|
8
|
+
|
|
9
|
+
from coreason_manifest.errors import IntegrityCompromisedError
|
|
10
|
+
from coreason_manifest.models import AgentDefinition
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class IntegrityChecker:
|
|
14
|
+
"""
|
|
15
|
+
Component D: IntegrityChecker (The Notary).
|
|
16
|
+
|
|
17
|
+
Responsibility:
|
|
18
|
+
- Calculate the SHA256 hash of the source code directory.
|
|
19
|
+
- Compare it against the integrity_hash defined in the manifest.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
IGNORED_DIRS = frozenset({".git", "__pycache__", ".venv", ".env", ".DS_Store"})
|
|
23
|
+
|
|
24
|
+
@staticmethod
|
|
25
|
+
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.
|
|
28
|
+
|
|
29
|
+
It walks the directory using os.walk to efficiently prune ignored directories.
|
|
30
|
+
Sorts files by relative path, hashes each file, and then hashes the sequence.
|
|
31
|
+
|
|
32
|
+
Ignores hidden directories/files in IGNORED_DIRS.
|
|
33
|
+
Rejects symbolic links for security.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
source_dir: The directory containing source code.
|
|
37
|
+
exclude_files: Optional set of file paths (absolute or relative to CWD) to exclude from hashing.
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
The hex digest of the SHA256 hash.
|
|
41
|
+
|
|
42
|
+
Raises:
|
|
43
|
+
FileNotFoundError: If source_dir does not exist.
|
|
44
|
+
IntegrityCompromisedError: If a symlink is found.
|
|
45
|
+
"""
|
|
46
|
+
path_obj = Path(source_dir)
|
|
47
|
+
if path_obj.is_symlink():
|
|
48
|
+
raise IntegrityCompromisedError(f"Symbolic links are forbidden: {path_obj}")
|
|
49
|
+
|
|
50
|
+
source_path = path_obj.resolve()
|
|
51
|
+
if not source_path.exists():
|
|
52
|
+
raise FileNotFoundError(f"Source directory not found: {source_path}")
|
|
53
|
+
|
|
54
|
+
# Normalize excluded files to absolute paths
|
|
55
|
+
excludes = set()
|
|
56
|
+
if exclude_files:
|
|
57
|
+
for ex_path in exclude_files:
|
|
58
|
+
excludes.add(Path(ex_path).resolve())
|
|
59
|
+
|
|
60
|
+
sha256 = hashlib.sha256()
|
|
61
|
+
file_paths: List[Path] = []
|
|
62
|
+
|
|
63
|
+
# Use os.walk for efficient traversal and pruning
|
|
64
|
+
for root, dirs, files in os.walk(source_path, topdown=True):
|
|
65
|
+
root_path = Path(root)
|
|
66
|
+
|
|
67
|
+
# Check for symlinks in directories before pruning
|
|
68
|
+
for d_name in dirs:
|
|
69
|
+
d_path = root_path / d_name
|
|
70
|
+
if d_path.is_symlink():
|
|
71
|
+
raise IntegrityCompromisedError(f"Symbolic links are forbidden: {d_path}") # pragma: no cover
|
|
72
|
+
|
|
73
|
+
# Prune directories efficiently using slice assignment
|
|
74
|
+
dirs[:] = [d for d in dirs if d not in IntegrityChecker.IGNORED_DIRS]
|
|
75
|
+
|
|
76
|
+
# Collect files
|
|
77
|
+
for f_name in files:
|
|
78
|
+
f_path = root_path / f_name
|
|
79
|
+
|
|
80
|
+
if f_path.is_symlink():
|
|
81
|
+
raise IntegrityCompromisedError(f"Symbolic links are forbidden: {f_path}")
|
|
82
|
+
|
|
83
|
+
if f_name in IntegrityChecker.IGNORED_DIRS:
|
|
84
|
+
continue
|
|
85
|
+
|
|
86
|
+
# Use resolved path for exclusion checking and inclusion
|
|
87
|
+
f_path_abs = f_path.resolve()
|
|
88
|
+
if f_path_abs in excludes:
|
|
89
|
+
continue
|
|
90
|
+
|
|
91
|
+
file_paths.append(f_path_abs)
|
|
92
|
+
|
|
93
|
+
# Sort to ensure deterministic order
|
|
94
|
+
# Use as_posix() to ensure ASCII sorting (case-sensitive) on all platforms (Windows vs Linux)
|
|
95
|
+
file_paths.sort(key=lambda p: p.relative_to(source_path).as_posix())
|
|
96
|
+
|
|
97
|
+
for path in file_paths:
|
|
98
|
+
# Update hash with relative path to ensure structure matters
|
|
99
|
+
# Use forward slashes for cross-platform consistency
|
|
100
|
+
rel_path = path.relative_to(source_path).as_posix().encode("utf-8")
|
|
101
|
+
sha256.update(rel_path)
|
|
102
|
+
|
|
103
|
+
# Update hash with file content
|
|
104
|
+
with open(path, "rb") as f:
|
|
105
|
+
while chunk := f.read(8192):
|
|
106
|
+
sha256.update(chunk)
|
|
107
|
+
|
|
108
|
+
return sha256.hexdigest()
|
|
109
|
+
|
|
110
|
+
@staticmethod
|
|
111
|
+
def verify(
|
|
112
|
+
agent_def: AgentDefinition,
|
|
113
|
+
source_dir: Union[Path, str],
|
|
114
|
+
manifest_path: Optional[Union[Path, str]] = None,
|
|
115
|
+
) -> None:
|
|
116
|
+
"""
|
|
117
|
+
Verifies the integrity of the source code against the manifest.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
agent_def: The AgentDefinition containing the expected hash.
|
|
121
|
+
source_dir: The directory containing source code.
|
|
122
|
+
manifest_path: Optional path to the manifest file to exclude from hashing.
|
|
123
|
+
|
|
124
|
+
Raises:
|
|
125
|
+
IntegrityCompromisedError: If the hash does not match or is missing.
|
|
126
|
+
FileNotFoundError: If source_dir does not exist.
|
|
127
|
+
"""
|
|
128
|
+
exclude_files = {manifest_path} if manifest_path else None
|
|
129
|
+
|
|
130
|
+
# agent_def.integrity_hash is now required by Pydantic model
|
|
131
|
+
calculated = IntegrityChecker.calculate_hash(source_dir, exclude_files=exclude_files)
|
|
132
|
+
|
|
133
|
+
if calculated != agent_def.integrity_hash:
|
|
134
|
+
raise IntegrityCompromisedError(
|
|
135
|
+
f"Integrity check failed. Expected {agent_def.integrity_hash}, got {calculated}"
|
|
136
|
+
)
|