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.
@@ -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
+ [![CI](https://github.com/CoReason-AI/coreason_manifest/actions/workflows/ci.yml/badge.svg)](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
+ [![CI](https://github.com/CoReason-AI/coreason_manifest/actions/workflows/ci.yml/badge.svg)](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
+ )