open-code-review-toolkit 0.4.7__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.
- {open_code_review_toolkit-0.4.7 → open_code_review_toolkit-0.5.0}/PKG-INFO +3 -3
- {open_code_review_toolkit-0.4.7 → open_code_review_toolkit-0.5.0}/README.md +2 -2
- {open_code_review_toolkit-0.4.7 → open_code_review_toolkit-0.5.0}/src/ocr_toolkit/_version.py +2 -2
- {open_code_review_toolkit-0.4.7 → open_code_review_toolkit-0.5.0}/src/ocr_toolkit/evidence/collect.py +14 -7
- {open_code_review_toolkit-0.4.7 → open_code_review_toolkit-0.5.0}/src/ocr_toolkit/evidence/collectors.py +255 -38
- open_code_review_toolkit-0.5.0/src/ocr_toolkit/evidence/ecosystems/__init__.py +1 -0
- open_code_review_toolkit-0.5.0/src/ocr_toolkit/evidence/ecosystems/ansible/__init__.py +1 -0
- open_code_review_toolkit-0.4.7/src/ocr_toolkit/evidence/go_manifests.py → open_code_review_toolkit-0.5.0/src/ocr_toolkit/evidence/ecosystems/go.py +1 -1
- open_code_review_toolkit-0.4.7/src/ocr_toolkit/evidence/javascript_manifests.py → open_code_review_toolkit-0.5.0/src/ocr_toolkit/evidence/ecosystems/javascript.py +1 -1
- open_code_review_toolkit-0.4.7/src/ocr_toolkit/evidence/composer_manifests.py → open_code_review_toolkit-0.5.0/src/ocr_toolkit/evidence/ecosystems/php.py +1 -1
- open_code_review_toolkit-0.4.7/src/ocr_toolkit/evidence/python_manifests.py → open_code_review_toolkit-0.5.0/src/ocr_toolkit/evidence/ecosystems/python.py +1 -1
- open_code_review_toolkit-0.5.0/src/ocr_toolkit/evidence/frameworks/__init__.py +42 -0
- open_code_review_toolkit-0.5.0/src/ocr_toolkit/evidence/frameworks/contracts.py +81 -0
- open_code_review_toolkit-0.5.0/src/ocr_toolkit/evidence/frameworks/detection.py +635 -0
- open_code_review_toolkit-0.5.0/src/ocr_toolkit/evidence/frameworks/providers/__init__.py +8 -0
- open_code_review_toolkit-0.5.0/src/ocr_toolkit/evidence/frameworks/providers/frontend.py +35 -0
- open_code_review_toolkit-0.5.0/src/ocr_toolkit/evidence/frameworks/providers/go.py +22 -0
- open_code_review_toolkit-0.5.0/src/ocr_toolkit/evidence/frameworks/providers/php.py +25 -0
- open_code_review_toolkit-0.5.0/src/ocr_toolkit/evidence/frameworks/providers/python.py +19 -0
- open_code_review_toolkit-0.5.0/src/ocr_toolkit/evidence/frameworks/registry.py +165 -0
- open_code_review_toolkit-0.5.0/src/ocr_toolkit/evidence/frameworks/schema.py +275 -0
- open_code_review_toolkit-0.5.0/src/ocr_toolkit/evidence/frameworks/templates.py +144 -0
- {open_code_review_toolkit-0.4.7 → open_code_review_toolkit-0.5.0}/src/ocr_toolkit/evidence/infrastructure.py +1 -1
- {open_code_review_toolkit-0.4.7 → open_code_review_toolkit-0.5.0}/src/ocr_toolkit/evidence/mcp.py +56 -19
- {open_code_review_toolkit-0.4.7 → open_code_review_toolkit-0.5.0}/src/ocr_toolkit/evidence/model.py +51 -2
- {open_code_review_toolkit-0.4.7 → open_code_review_toolkit-0.5.0}/src/ocr_toolkit/evidence/project.py +8 -1
- {open_code_review_toolkit-0.4.7 → open_code_review_toolkit-0.5.0}/src/ocr_toolkit/evidence/store.py +68 -5
- {open_code_review_toolkit-0.4.7 → open_code_review_toolkit-0.5.0}/src/ocr_toolkit/preflight.py +1 -1
- {open_code_review_toolkit-0.4.7 → open_code_review_toolkit-0.5.0}/.gitignore +0 -0
- {open_code_review_toolkit-0.4.7 → open_code_review_toolkit-0.5.0}/LICENSE +0 -0
- {open_code_review_toolkit-0.4.7 → open_code_review_toolkit-0.5.0}/pyproject.toml +0 -0
- {open_code_review_toolkit-0.4.7 → open_code_review_toolkit-0.5.0}/src/ocr_toolkit/__init__.py +0 -0
- {open_code_review_toolkit-0.4.7 → open_code_review_toolkit-0.5.0}/src/ocr_toolkit/cli.py +0 -0
- {open_code_review_toolkit-0.4.7 → open_code_review_toolkit-0.5.0}/src/ocr_toolkit/common/__init__.py +0 -0
- {open_code_review_toolkit-0.4.7 → open_code_review_toolkit-0.5.0}/src/ocr_toolkit/common/git.py +0 -0
- {open_code_review_toolkit-0.4.7 → open_code_review_toolkit-0.5.0}/src/ocr_toolkit/common/language.py +0 -0
- {open_code_review_toolkit-0.4.7 → open_code_review_toolkit-0.5.0}/src/ocr_toolkit/common/markdown.py +0 -0
- {open_code_review_toolkit-0.4.7 → open_code_review_toolkit-0.5.0}/src/ocr_toolkit/common/redaction.py +0 -0
- {open_code_review_toolkit-0.4.7 → open_code_review_toolkit-0.5.0}/src/ocr_toolkit/config_writer.py +0 -0
- {open_code_review_toolkit-0.4.7 → open_code_review_toolkit-0.5.0}/src/ocr_toolkit/configure.py +0 -0
- {open_code_review_toolkit-0.4.7 → open_code_review_toolkit-0.5.0}/src/ocr_toolkit/evidence/__init__.py +0 -0
- {open_code_review_toolkit-0.4.7 → open_code_review_toolkit-0.5.0}/src/ocr_toolkit/evidence/__main__.py +0 -0
- {open_code_review_toolkit-0.4.7 → open_code_review_toolkit-0.5.0}/src/ocr_toolkit/evidence/artifacts.py +0 -0
- {open_code_review_toolkit-0.4.7 → open_code_review_toolkit-0.5.0}/src/ocr_toolkit/evidence/categorize.py +0 -0
- {open_code_review_toolkit-0.4.7 → open_code_review_toolkit-0.5.0}/src/ocr_toolkit/evidence/coverage.py +0 -0
- /open_code_review_toolkit-0.4.7/src/ocr_toolkit/evidence/ansible_requirements.py → /open_code_review_toolkit-0.5.0/src/ocr_toolkit/evidence/ecosystems/ansible/requirements.py +0 -0
- /open_code_review_toolkit-0.4.7/src/ocr_toolkit/evidence/ansible.py → /open_code_review_toolkit-0.5.0/src/ocr_toolkit/evidence/ecosystems/ansible/topology.py +0 -0
- /open_code_review_toolkit-0.4.7/src/ocr_toolkit/evidence/manifest_model.py → /open_code_review_toolkit-0.5.0/src/ocr_toolkit/evidence/ecosystems/contracts.py +0 -0
- {open_code_review_toolkit-0.4.7 → open_code_review_toolkit-0.5.0}/src/ocr_toolkit/evidence/invocation.py +0 -0
- {open_code_review_toolkit-0.4.7 → open_code_review_toolkit-0.5.0}/src/ocr_toolkit/evidence/repository.py +0 -0
- {open_code_review_toolkit-0.4.7 → open_code_review_toolkit-0.5.0}/src/ocr_toolkit/mcp_config.py +0 -0
- {open_code_review_toolkit-0.4.7 → open_code_review_toolkit-0.5.0}/src/ocr_toolkit/ocr_result.py +0 -0
- {open_code_review_toolkit-0.4.7 → open_code_review_toolkit-0.5.0}/src/ocr_toolkit/posting/__init__.py +0 -0
- {open_code_review_toolkit-0.4.7 → open_code_review_toolkit-0.5.0}/src/ocr_toolkit/posting/__main__.py +0 -0
- {open_code_review_toolkit-0.4.7 → open_code_review_toolkit-0.5.0}/src/ocr_toolkit/posting/approval.py +0 -0
- {open_code_review_toolkit-0.4.7 → open_code_review_toolkit-0.5.0}/src/ocr_toolkit/posting/comments.py +0 -0
- {open_code_review_toolkit-0.4.7 → open_code_review_toolkit-0.5.0}/src/ocr_toolkit/posting/formatting.py +0 -0
- {open_code_review_toolkit-0.4.7 → open_code_review_toolkit-0.5.0}/src/ocr_toolkit/posting/gitlab.py +0 -0
- {open_code_review_toolkit-0.4.7 → open_code_review_toolkit-0.5.0}/src/ocr_toolkit/posting/gitlab_approval.py +0 -0
- {open_code_review_toolkit-0.4.7 → open_code_review_toolkit-0.5.0}/src/ocr_toolkit/posting/markers.py +0 -0
- {open_code_review_toolkit-0.4.7 → open_code_review_toolkit-0.5.0}/src/ocr_toolkit/posting/payloads.py +0 -0
- {open_code_review_toolkit-0.4.7 → open_code_review_toolkit-0.5.0}/src/ocr_toolkit/posting/result.py +0 -0
- {open_code_review_toolkit-0.4.7 → open_code_review_toolkit-0.5.0}/src/ocr_toolkit/posting/settings.py +0 -0
- {open_code_review_toolkit-0.4.7 → open_code_review_toolkit-0.5.0}/src/ocr_toolkit/posting/snapshot.py +0 -0
- {open_code_review_toolkit-0.4.7 → open_code_review_toolkit-0.5.0}/src/ocr_toolkit/posting/suggestions.py +0 -0
- {open_code_review_toolkit-0.4.7 → open_code_review_toolkit-0.5.0}/src/ocr_toolkit/posting/workflow.py +0 -0
- {open_code_review_toolkit-0.4.7 → open_code_review_toolkit-0.5.0}/src/ocr_toolkit/providers/__init__.py +0 -0
- {open_code_review_toolkit-0.4.7 → open_code_review_toolkit-0.5.0}/src/ocr_toolkit/providers/gitlab.py +0 -0
- {open_code_review_toolkit-0.4.7 → open_code_review_toolkit-0.5.0}/src/ocr_toolkit/py.typed +0 -0
- {open_code_review_toolkit-0.4.7 → open_code_review_toolkit-0.5.0}/src/ocr_toolkit/result_contract.py +0 -0
- {open_code_review_toolkit-0.4.7 → open_code_review_toolkit-0.5.0}/src/ocr_toolkit/review_runner.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: open-code-review-toolkit
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: Unofficial GitLab CI integration layer for Open Code Review
|
|
5
5
|
Project-URL: Homepage, https://github.com/xeonvs/open-code-review-toolkit
|
|
6
6
|
Project-URL: Repository, https://github.com/xeonvs/open-code-review-toolkit
|
|
@@ -243,7 +243,7 @@ ocr --version
|
|
|
243
243
|
ocr-ci --help
|
|
244
244
|
```
|
|
245
245
|
|
|
246
|
-
The
|
|
246
|
+
The exact recommended OCR release and its verified asset checksums live in the [versioned compatibility manifest](compatibility/ocr-support.json). CI should pin that release and checksum before execution.
|
|
247
247
|
The [versioned compatibility policy](docs/compatibility.md) records tested assets and evidence and describes the conservative Dependabot-like qualification workflow for later upstream releases.
|
|
248
248
|
Review output defaults to English. `OCR_REVIEW_LANGUAGE` accepts another explicit language name when a project needs localized review output; for example, `OCR_REVIEW_LANGUAGE=Russian`.
|
|
249
249
|
|
|
@@ -266,7 +266,7 @@ Project-wide accepted tradeoffs can be recorded separately in `.opencodereview/a
|
|
|
266
266
|
|
|
267
267
|
## Project architecture
|
|
268
268
|
|
|
269
|
-
The shipped Repository Evidence Engine reads immutable base/head Git objects, stores bounded typed facts and deltas, creates the compact bootstrap used by OCR, and exposes detailed
|
|
269
|
+
The shipped Repository Evidence Engine reads immutable base/head Git objects, stores bounded typed facts and deltas, creates the compact bootstrap used by OCR, and exposes detailed facts, scoped completeness, and base/head changes through the mandatory built-in read-only MCP server. Reviewed external stdio or native HTTPS MCP servers compose alongside it without replacing the built-in evidence boundary.
|
|
270
270
|
|
|
271
271
|
- [Toolkit strategy](docs/engineering/toolkit_strategy.md) - durable product boundaries, architecture, invariants, and non-goals.
|
|
272
272
|
- [Roadmap](ROADMAP.md) - milestone status, dependencies, outcomes, and completion signals.
|
|
@@ -17,7 +17,7 @@ ocr --version
|
|
|
17
17
|
ocr-ci --help
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
-
The
|
|
20
|
+
The exact recommended OCR release and its verified asset checksums live in the [versioned compatibility manifest](compatibility/ocr-support.json). CI should pin that release and checksum before execution.
|
|
21
21
|
The [versioned compatibility policy](docs/compatibility.md) records tested assets and evidence and describes the conservative Dependabot-like qualification workflow for later upstream releases.
|
|
22
22
|
Review output defaults to English. `OCR_REVIEW_LANGUAGE` accepts another explicit language name when a project needs localized review output; for example, `OCR_REVIEW_LANGUAGE=Russian`.
|
|
23
23
|
|
|
@@ -40,7 +40,7 @@ Project-wide accepted tradeoffs can be recorded separately in `.opencodereview/a
|
|
|
40
40
|
|
|
41
41
|
## Project architecture
|
|
42
42
|
|
|
43
|
-
The shipped Repository Evidence Engine reads immutable base/head Git objects, stores bounded typed facts and deltas, creates the compact bootstrap used by OCR, and exposes detailed
|
|
43
|
+
The shipped Repository Evidence Engine reads immutable base/head Git objects, stores bounded typed facts and deltas, creates the compact bootstrap used by OCR, and exposes detailed facts, scoped completeness, and base/head changes through the mandatory built-in read-only MCP server. Reviewed external stdio or native HTTPS MCP servers compose alongside it without replacing the built-in evidence boundary.
|
|
44
44
|
|
|
45
45
|
- [Toolkit strategy](docs/engineering/toolkit_strategy.md) - durable product boundaries, architecture, invariants, and non-goals.
|
|
46
46
|
- [Roadmap](ROADMAP.md) - milestone status, dependencies, outcomes, and completion signals.
|
{open_code_review_toolkit-0.4.7 → open_code_review_toolkit-0.5.0}/src/ocr_toolkit/_version.py
RENAMED
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '0.
|
|
22
|
-
__version_tuple__ = version_tuple = (0,
|
|
21
|
+
__version__ = version = '0.5.0'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 5, 0)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -86,14 +86,9 @@ def collect_repository_evidence(
|
|
|
86
86
|
coverage=tuple(head_coverage),
|
|
87
87
|
)
|
|
88
88
|
all_coverage = tuple((*base.coverage, *head.coverage))
|
|
89
|
-
|
|
89
|
+
snapshot_deltas = file_deltas(base, head)
|
|
90
|
+
store = EvidenceStore(base=base, head=head)
|
|
90
91
|
typed_facts = [*base_facts, *head_facts]
|
|
91
|
-
store.deltas = tuple(
|
|
92
|
-
sorted(
|
|
93
|
-
(*store.deltas, *fact_deltas(typed_facts), *coverage_deltas(all_coverage)),
|
|
94
|
-
key=lambda item: (item.kind, item.component, item.identity),
|
|
95
|
-
)
|
|
96
|
-
)
|
|
97
92
|
rejected_snapshot_records = [
|
|
98
93
|
record for record in (*base.records, *head.records) if not store.add(record)
|
|
99
94
|
]
|
|
@@ -125,6 +120,18 @@ def collect_repository_evidence(
|
|
|
125
120
|
)
|
|
126
121
|
store.add_diagnostic("typed evidence was truncated by store limits")
|
|
127
122
|
break
|
|
123
|
+
# Deltas are projections of canonical accepted store records, never raw facts
|
|
124
|
+
# or references to values that redaction, deduplication, or a budget omitted.
|
|
125
|
+
store.deltas = tuple(
|
|
126
|
+
sorted(
|
|
127
|
+
(
|
|
128
|
+
*snapshot_deltas,
|
|
129
|
+
*fact_deltas(store.records),
|
|
130
|
+
*coverage_deltas(all_coverage),
|
|
131
|
+
),
|
|
132
|
+
key=lambda item: (item.kind, item.component, item.identity),
|
|
133
|
+
)
|
|
134
|
+
)
|
|
128
135
|
categories = categorize_paths(list(changed))
|
|
129
136
|
categories_truncated = False
|
|
130
137
|
for category, paths in sorted(categories.items()):
|
|
@@ -8,7 +8,9 @@ from collections.abc import Callable, Iterable, Mapping
|
|
|
8
8
|
from dataclasses import dataclass
|
|
9
9
|
from pathlib import PurePosixPath
|
|
10
10
|
|
|
11
|
-
from ocr_toolkit.evidence.
|
|
11
|
+
from ocr_toolkit.evidence.coverage import CoverageObservation, compose_coverage
|
|
12
|
+
from ocr_toolkit.evidence.ecosystems.ansible.requirements import parse_galaxy_requirements
|
|
13
|
+
from ocr_toolkit.evidence.ecosystems.ansible.topology import (
|
|
12
14
|
collect_topology,
|
|
13
15
|
inventory_scope,
|
|
14
16
|
role_coverage_scope,
|
|
@@ -16,22 +18,36 @@ from ocr_toolkit.evidence.ansible import (
|
|
|
16
18
|
topology_candidate,
|
|
17
19
|
topology_coverage,
|
|
18
20
|
)
|
|
19
|
-
from ocr_toolkit.evidence.
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
from ocr_toolkit.evidence.
|
|
21
|
+
from ocr_toolkit.evidence.ecosystems.contracts import (
|
|
22
|
+
MAX_MANIFEST_ITEMS,
|
|
23
|
+
ManifestFact,
|
|
24
|
+
ManifestParseResult,
|
|
25
|
+
)
|
|
26
|
+
from ocr_toolkit.evidence.ecosystems.go import parse_go_mod, parse_go_sum
|
|
27
|
+
from ocr_toolkit.evidence.ecosystems.javascript import (
|
|
25
28
|
parse_package_json,
|
|
26
29
|
parse_package_lock,
|
|
27
30
|
parse_pnpm_lock,
|
|
28
31
|
parse_yarn_lock,
|
|
29
32
|
)
|
|
30
|
-
from ocr_toolkit.evidence.
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
33
|
+
from ocr_toolkit.evidence.ecosystems.php import parse_composer_json, parse_composer_lock
|
|
34
|
+
from ocr_toolkit.evidence.ecosystems.python import (
|
|
35
|
+
parse_pipfile_lock,
|
|
36
|
+
parse_poetry_lock,
|
|
37
|
+
parse_pylock,
|
|
38
|
+
parse_pyproject,
|
|
39
|
+
parse_requirements,
|
|
40
|
+
parse_uv_lock,
|
|
41
|
+
)
|
|
42
|
+
from ocr_toolkit.evidence.frameworks import (
|
|
43
|
+
FrameworkPluginContext,
|
|
44
|
+
PluginCoverage,
|
|
45
|
+
PluginFact,
|
|
46
|
+
PluginSourceStatus,
|
|
47
|
+
collect_framework_plugins,
|
|
48
|
+
collect_template_files,
|
|
34
49
|
)
|
|
50
|
+
from ocr_toolkit.evidence.infrastructure import infrastructure_candidate, parse_infrastructure_pins
|
|
35
51
|
from ocr_toolkit.evidence.model import (
|
|
36
52
|
Confidence,
|
|
37
53
|
CoverageRecord,
|
|
@@ -42,14 +58,6 @@ from ocr_toolkit.evidence.model import (
|
|
|
42
58
|
RefRole,
|
|
43
59
|
TrustClass,
|
|
44
60
|
)
|
|
45
|
-
from ocr_toolkit.evidence.python_manifests import (
|
|
46
|
-
parse_pipfile_lock,
|
|
47
|
-
parse_poetry_lock,
|
|
48
|
-
parse_pylock,
|
|
49
|
-
parse_pyproject,
|
|
50
|
-
parse_requirements,
|
|
51
|
-
parse_uv_lock,
|
|
52
|
-
)
|
|
53
61
|
from ocr_toolkit.evidence.repository import (
|
|
54
62
|
GitRepositoryReader,
|
|
55
63
|
RepositoryEvidenceError,
|
|
@@ -82,29 +90,32 @@ CONTEXT_YAML_DIRECTORIES = (
|
|
|
82
90
|
|
|
83
91
|
@dataclass(frozen=True, slots=True)
|
|
84
92
|
class ManifestCollector:
|
|
85
|
-
"""Bind manifest path matching, ecosystem metadata, and
|
|
93
|
+
"""Bind manifest path matching, ecosystem metadata, role, and bounded parser."""
|
|
86
94
|
|
|
87
95
|
ecosystem: str
|
|
96
|
+
source_roles: tuple[str, ...]
|
|
88
97
|
matches: Callable[[str], bool]
|
|
89
98
|
parse: Callable[[str], ManifestParseResult]
|
|
90
99
|
|
|
91
100
|
|
|
92
101
|
@dataclass(frozen=True, slots=True)
|
|
93
102
|
class ManifestBlobSet:
|
|
94
|
-
"""Return immutable Galaxy blobs and
|
|
103
|
+
"""Return immutable Galaxy blobs, diagnostics, and affected graph roots."""
|
|
95
104
|
|
|
96
105
|
blobs: dict[str, bytes]
|
|
97
106
|
galaxy_paths: tuple[str, ...]
|
|
98
107
|
diagnostics: tuple[str, ...]
|
|
108
|
+
degraded_roots: tuple[tuple[str, str], ...] = ()
|
|
99
109
|
|
|
100
110
|
|
|
101
111
|
@dataclass(frozen=True, slots=True)
|
|
102
112
|
class PythonRequirementBlobSet:
|
|
103
|
-
"""Return immutable requirements blobs and
|
|
113
|
+
"""Return immutable requirements blobs, diagnostics, and affected roots."""
|
|
104
114
|
|
|
105
115
|
blobs: dict[str, bytes]
|
|
106
116
|
requirement_paths: tuple[str, ...]
|
|
107
117
|
diagnostics: tuple[str, ...]
|
|
118
|
+
degraded_roots: tuple[tuple[str, str], ...] = ()
|
|
108
119
|
|
|
109
120
|
|
|
110
121
|
def _parse_ansible_requirements(text: str) -> ManifestParseResult:
|
|
@@ -155,26 +166,28 @@ def _is_pylock(path: str) -> bool:
|
|
|
155
166
|
|
|
156
167
|
|
|
157
168
|
MANIFEST_COLLECTORS = (
|
|
158
|
-
ManifestCollector("python", _name_is("pyproject.toml"), parse_pyproject),
|
|
159
|
-
ManifestCollector("python", _is_python_requirements, parse_requirements),
|
|
160
|
-
ManifestCollector("python", _name_is("uv.lock"), parse_uv_lock),
|
|
161
|
-
ManifestCollector("python", _name_is("poetry.lock"), parse_poetry_lock),
|
|
162
|
-
ManifestCollector("python", _name_is("Pipfile.lock"), parse_pipfile_lock),
|
|
163
|
-
ManifestCollector("python", _is_pylock, parse_pylock),
|
|
164
|
-
ManifestCollector("javascript", _name_is("package.json"), parse_package_json),
|
|
169
|
+
ManifestCollector("python", ("declaration",), _name_is("pyproject.toml"), parse_pyproject),
|
|
170
|
+
ManifestCollector("python", ("declaration",), _is_python_requirements, parse_requirements),
|
|
171
|
+
ManifestCollector("python", ("resolution",), _name_is("uv.lock"), parse_uv_lock),
|
|
172
|
+
ManifestCollector("python", ("resolution",), _name_is("poetry.lock"), parse_poetry_lock),
|
|
173
|
+
ManifestCollector("python", ("resolution",), _name_is("Pipfile.lock"), parse_pipfile_lock),
|
|
174
|
+
ManifestCollector("python", ("resolution",), _is_pylock, parse_pylock),
|
|
175
|
+
ManifestCollector("javascript", ("declaration",), _name_is("package.json"), parse_package_json),
|
|
165
176
|
ManifestCollector(
|
|
166
177
|
"javascript",
|
|
178
|
+
("resolution",),
|
|
167
179
|
_name_is("package-lock.json"),
|
|
168
180
|
parse_package_lock,
|
|
169
181
|
),
|
|
170
|
-
ManifestCollector("javascript", _name_is("yarn.lock"), parse_yarn_lock),
|
|
171
|
-
ManifestCollector("javascript", _name_is("pnpm-lock.yaml"), parse_pnpm_lock),
|
|
172
|
-
ManifestCollector("go", _name_is("go.mod"), parse_go_mod),
|
|
173
|
-
ManifestCollector("go", _name_is("go.sum"), parse_go_sum),
|
|
174
|
-
ManifestCollector("php", _name_is("composer.json"), parse_composer_json),
|
|
175
|
-
ManifestCollector("php", _name_is("composer.lock"), parse_composer_lock),
|
|
182
|
+
ManifestCollector("javascript", ("resolution",), _name_is("yarn.lock"), parse_yarn_lock),
|
|
183
|
+
ManifestCollector("javascript", ("resolution",), _name_is("pnpm-lock.yaml"), parse_pnpm_lock),
|
|
184
|
+
ManifestCollector("go", ("declaration", "resolution"), _name_is("go.mod"), parse_go_mod),
|
|
185
|
+
ManifestCollector("go", ("checksum",), _name_is("go.sum"), parse_go_sum),
|
|
186
|
+
ManifestCollector("php", ("declaration",), _name_is("composer.json"), parse_composer_json),
|
|
187
|
+
ManifestCollector("php", ("resolution",), _name_is("composer.lock"), parse_composer_lock),
|
|
176
188
|
ManifestCollector(
|
|
177
189
|
"ansible",
|
|
190
|
+
("declaration",),
|
|
178
191
|
_name_is("requirements.yml", "requirements.yaml"),
|
|
179
192
|
_parse_ansible_requirements,
|
|
180
193
|
),
|
|
@@ -366,6 +379,42 @@ def _bound_include_diagnostics(
|
|
|
366
379
|
)
|
|
367
380
|
|
|
368
381
|
|
|
382
|
+
def _roots_reaching_graph_degradation(
|
|
383
|
+
roots: tuple[str, ...],
|
|
384
|
+
edges: Mapping[str, tuple[str, ...]],
|
|
385
|
+
degraded_paths: Mapping[str, str],
|
|
386
|
+
) -> tuple[tuple[str, str], ...]:
|
|
387
|
+
"""Return roots whose accepted graph reaches a bounded degraded source."""
|
|
388
|
+
|
|
389
|
+
supported_reasons = {"bounded-source-omission", "include-graph-truncation"}
|
|
390
|
+
if any(reason not in supported_reasons for reason in degraded_paths.values()):
|
|
391
|
+
raise ValueError("include graph has an unsupported degradation reason")
|
|
392
|
+
affected: list[tuple[str, str]] = []
|
|
393
|
+
for root in sorted(set(roots)):
|
|
394
|
+
pending = [root]
|
|
395
|
+
visited: set[str] = set()
|
|
396
|
+
reasons: set[str] = set()
|
|
397
|
+
while pending:
|
|
398
|
+
path = pending.pop()
|
|
399
|
+
if path in visited:
|
|
400
|
+
continue
|
|
401
|
+
visited.add(path)
|
|
402
|
+
reason = degraded_paths.get(path)
|
|
403
|
+
if reason is not None:
|
|
404
|
+
reasons.add(reason)
|
|
405
|
+
pending.extend(reversed(edges.get(path, ())))
|
|
406
|
+
if reasons:
|
|
407
|
+
# A bounded omission is stronger than a traversal/item limit because
|
|
408
|
+
# the source itself was never parsed.
|
|
409
|
+
reason = (
|
|
410
|
+
"bounded-source-omission"
|
|
411
|
+
if "bounded-source-omission" in reasons
|
|
412
|
+
else "include-graph-truncation"
|
|
413
|
+
)
|
|
414
|
+
affected.append((root, reason))
|
|
415
|
+
return tuple(affected)
|
|
416
|
+
|
|
417
|
+
|
|
369
418
|
def _read_manifest_graph(
|
|
370
419
|
reader: GitRepositoryReader,
|
|
371
420
|
entries_by_path: Mapping[str, RepositoryObject],
|
|
@@ -380,6 +429,7 @@ def _read_manifest_graph(
|
|
|
380
429
|
admitted = set(initial_paths)
|
|
381
430
|
root_paths = set(initial_paths)
|
|
382
431
|
edges: dict[str, list[str]] = {}
|
|
432
|
+
degraded_paths: dict[str, str] = {}
|
|
383
433
|
pending = [(path, "") for path in initial_paths]
|
|
384
434
|
included_files = 0
|
|
385
435
|
file_limit_reported = False
|
|
@@ -415,6 +465,7 @@ def _read_manifest_graph(
|
|
|
415
465
|
f"{MAX_MANIFEST_INCLUDE_FILES} files"
|
|
416
466
|
)
|
|
417
467
|
file_limit_reported = True
|
|
468
|
+
degraded_paths[path] = "include-graph-truncation"
|
|
418
469
|
continue
|
|
419
470
|
admitted.add(path)
|
|
420
471
|
included_files += 1
|
|
@@ -425,6 +476,8 @@ def _read_manifest_graph(
|
|
|
425
476
|
read = reader.read_candidate_blobs(tuple(to_read))
|
|
426
477
|
blobs.update(read.blobs)
|
|
427
478
|
diagnostics.extend(read.diagnostics)
|
|
479
|
+
for path in (entry.path for entry in to_read if entry.path not in read.blobs):
|
|
480
|
+
degraded_paths[path] = "bounded-source-omission"
|
|
428
481
|
for path in process_paths:
|
|
429
482
|
visited.add(path)
|
|
430
483
|
blob = blobs.get(path)
|
|
@@ -432,6 +485,8 @@ def _read_manifest_graph(
|
|
|
432
485
|
continue
|
|
433
486
|
try:
|
|
434
487
|
parsed = parse_galaxy_requirements(blob.decode("utf-8"))
|
|
488
|
+
if any("truncated" in notice for notice in parsed.notices):
|
|
489
|
+
degraded_paths[path] = "include-graph-truncation"
|
|
435
490
|
except UnicodeDecodeError:
|
|
436
491
|
diagnostics.append(f"{path}: Ansible Galaxy include is not UTF-8")
|
|
437
492
|
continue
|
|
@@ -447,6 +502,7 @@ def _read_manifest_graph(
|
|
|
447
502
|
f"{MAX_MANIFEST_INCLUDE_EDGES} edges"
|
|
448
503
|
)
|
|
449
504
|
edge_limit_reported = True
|
|
505
|
+
degraded_paths[path] = "include-graph-truncation"
|
|
450
506
|
continue
|
|
451
507
|
included_edges += 1
|
|
452
508
|
edges.setdefault(path, []).append(resolved)
|
|
@@ -454,12 +510,18 @@ def _read_manifest_graph(
|
|
|
454
510
|
diagnostics.append(
|
|
455
511
|
f"{path}: Ansible Galaxy include depth exceeded at {resolved}"
|
|
456
512
|
)
|
|
513
|
+
degraded_paths[path] = "include-graph-truncation"
|
|
457
514
|
else:
|
|
458
515
|
pending.append((resolved, path))
|
|
459
516
|
normalized_edges = {path: tuple(dict.fromkeys(targets)) for path, targets in edges.items()}
|
|
460
517
|
diagnostics.extend(_include_cycle_diagnostics(normalized_edges))
|
|
461
518
|
galaxy_paths = tuple(sorted(path for path in visited if path in blobs))
|
|
462
|
-
return ManifestBlobSet(
|
|
519
|
+
return ManifestBlobSet(
|
|
520
|
+
blobs,
|
|
521
|
+
galaxy_paths,
|
|
522
|
+
_bound_include_diagnostics(diagnostics),
|
|
523
|
+
_roots_reaching_graph_degradation(initial_paths, normalized_edges, degraded_paths),
|
|
524
|
+
)
|
|
463
525
|
|
|
464
526
|
|
|
465
527
|
def _read_python_requirement_graph(
|
|
@@ -474,6 +536,8 @@ def _read_python_requirement_graph(
|
|
|
474
536
|
diagnostics: list[str] = []
|
|
475
537
|
visited: set[str] = set()
|
|
476
538
|
admitted = set(initial_paths)
|
|
539
|
+
edges: dict[str, list[str]] = {}
|
|
540
|
+
degraded_paths: dict[str, str] = {}
|
|
477
541
|
pending = [(path, "") for path in initial_paths]
|
|
478
542
|
included_files = 0
|
|
479
543
|
included_edges = 0
|
|
@@ -506,6 +570,7 @@ def _read_python_requirement_graph(
|
|
|
506
570
|
f"{MAX_MANIFEST_INCLUDE_FILES} files"
|
|
507
571
|
)
|
|
508
572
|
file_limit_reported = True
|
|
573
|
+
degraded_paths[path] = "include-graph-truncation"
|
|
509
574
|
continue
|
|
510
575
|
admitted.add(path)
|
|
511
576
|
included_files += 1
|
|
@@ -515,12 +580,16 @@ def _read_python_requirement_graph(
|
|
|
515
580
|
read = reader.read_candidate_blobs(tuple(sorted(to_read, key=lambda item: item.path)))
|
|
516
581
|
blobs.update(read.blobs)
|
|
517
582
|
diagnostics.extend(read.diagnostics)
|
|
583
|
+
for path in (entry.path for entry in to_read if entry.path not in read.blobs):
|
|
584
|
+
degraded_paths[path] = "bounded-source-omission"
|
|
518
585
|
for path in process_paths:
|
|
519
586
|
visited.add(path)
|
|
520
587
|
if path not in blobs:
|
|
521
588
|
continue
|
|
522
589
|
try:
|
|
523
590
|
parsed = parse_requirements(blobs[path].decode("utf-8"))
|
|
591
|
+
if any("truncated" in notice for notice in parsed.notices):
|
|
592
|
+
degraded_paths[path] = "include-graph-truncation"
|
|
524
593
|
except UnicodeDecodeError:
|
|
525
594
|
diagnostics.append(f"{path}: Python requirements include is not UTF-8")
|
|
526
595
|
continue
|
|
@@ -538,15 +607,19 @@ def _read_python_requirement_graph(
|
|
|
538
607
|
f"{MAX_MANIFEST_INCLUDE_EDGES} edges"
|
|
539
608
|
)
|
|
540
609
|
edge_limit_reported = True
|
|
610
|
+
degraded_paths[path] = "include-graph-truncation"
|
|
541
611
|
continue
|
|
542
612
|
included_edges += 1
|
|
613
|
+
edges.setdefault(path, []).append(resolved)
|
|
543
614
|
if depth == MAX_MANIFEST_INCLUDE_DEPTH:
|
|
544
615
|
diagnostics.append(
|
|
545
616
|
f"{path}: Python requirements include depth exceeded at {resolved}"
|
|
546
617
|
)
|
|
618
|
+
degraded_paths[path] = "include-graph-truncation"
|
|
547
619
|
else:
|
|
548
620
|
pending.append((resolved, path))
|
|
549
621
|
requirement_paths = tuple(sorted(path for path in visited if path in blobs))
|
|
622
|
+
normalized_edges = {path: tuple(dict.fromkeys(targets)) for path, targets in edges.items()}
|
|
550
623
|
return PythonRequirementBlobSet(
|
|
551
624
|
blobs,
|
|
552
625
|
requirement_paths,
|
|
@@ -554,9 +627,56 @@ def _read_python_requirement_graph(
|
|
|
554
627
|
diagnostics,
|
|
555
628
|
truncation_notice="Python requirements include diagnostics were truncated",
|
|
556
629
|
),
|
|
630
|
+
_roots_reaching_graph_degradation(initial_paths, normalized_edges, degraded_paths),
|
|
557
631
|
)
|
|
558
632
|
|
|
559
633
|
|
|
634
|
+
def _plugin_records(
|
|
635
|
+
facts: tuple[PluginFact, ...],
|
|
636
|
+
*,
|
|
637
|
+
ref: RefRole,
|
|
638
|
+
commit_sha: str,
|
|
639
|
+
trust: TrustClass,
|
|
640
|
+
) -> list[EvidenceRecord]:
|
|
641
|
+
"""Attach immutable ref provenance to validated static plugin facts."""
|
|
642
|
+
|
|
643
|
+
return [
|
|
644
|
+
EvidenceRecord(
|
|
645
|
+
kind=fact.kind,
|
|
646
|
+
value={"identity": fact.identity, "fact": fact.value},
|
|
647
|
+
source_path=fact.source_path,
|
|
648
|
+
ref=ref,
|
|
649
|
+
commit_sha=commit_sha,
|
|
650
|
+
component=fact.component,
|
|
651
|
+
provenance=f"framework plugin:{fact.value['plugin']}",
|
|
652
|
+
confidence=Confidence.EXACT,
|
|
653
|
+
trust=trust,
|
|
654
|
+
)
|
|
655
|
+
for fact in facts
|
|
656
|
+
]
|
|
657
|
+
|
|
658
|
+
|
|
659
|
+
def _plugin_coverage(
|
|
660
|
+
observations: tuple[PluginCoverage, ...], *, ref: RefRole, commit_sha: str
|
|
661
|
+
) -> list[CoverageRecord]:
|
|
662
|
+
"""Compose plugin coverage by semantic component/domain/scope identity."""
|
|
663
|
+
|
|
664
|
+
grouped: dict[tuple[str, str, str], list[CoverageObservation]] = {}
|
|
665
|
+
for item in observations:
|
|
666
|
+
grouped.setdefault((item.component, item.domain, item.scope), []).append(item.observation)
|
|
667
|
+
return [
|
|
668
|
+
compose_coverage(
|
|
669
|
+
component=component,
|
|
670
|
+
domain=domain,
|
|
671
|
+
scope=scope,
|
|
672
|
+
observations=tuple(values),
|
|
673
|
+
ref=ref,
|
|
674
|
+
commit_sha=commit_sha,
|
|
675
|
+
)
|
|
676
|
+
for (component, domain, scope), values in sorted(grouped.items())
|
|
677
|
+
]
|
|
678
|
+
|
|
679
|
+
|
|
560
680
|
def collect_ref_facts(
|
|
561
681
|
reader: GitRepositoryReader,
|
|
562
682
|
commit_sha: str,
|
|
@@ -615,6 +735,16 @@ def collect_ref_facts(
|
|
|
615
735
|
and not entry.is_submodule
|
|
616
736
|
and entry.object_type == "blob"
|
|
617
737
|
)
|
|
738
|
+
source_statuses: dict[str, PluginSourceStatus] = {
|
|
739
|
+
entry.path: PluginSourceStatus(
|
|
740
|
+
entry.path,
|
|
741
|
+
collector.ecosystem,
|
|
742
|
+
collector.source_roles,
|
|
743
|
+
"pending",
|
|
744
|
+
)
|
|
745
|
+
for entry in candidates
|
|
746
|
+
if (collector := manifest_collector(entry.path)) is not None
|
|
747
|
+
}
|
|
618
748
|
try:
|
|
619
749
|
read = reader.read_candidate_blobs(candidates)
|
|
620
750
|
except RepositoryEvidenceError as exc:
|
|
@@ -635,6 +765,13 @@ def collect_ref_facts(
|
|
|
635
765
|
)
|
|
636
766
|
return records, diagnostics
|
|
637
767
|
blobs = read.blobs
|
|
768
|
+
for path, status in tuple(source_statuses.items()):
|
|
769
|
+
source_statuses[path] = PluginSourceStatus(
|
|
770
|
+
status.path,
|
|
771
|
+
status.ecosystem,
|
|
772
|
+
status.roles,
|
|
773
|
+
"accepted" if path in blobs else "omitted",
|
|
774
|
+
)
|
|
638
775
|
diagnostics.extend(f"{ref.value}:{message}" for message in read.diagnostics)
|
|
639
776
|
galaxy_roots = tuple(
|
|
640
777
|
entry.path
|
|
@@ -656,9 +793,39 @@ def collect_ref_facts(
|
|
|
656
793
|
except RepositoryEvidenceError as exc:
|
|
657
794
|
diagnostics.append(f"Python requirements include batch read failed: {exc}")
|
|
658
795
|
python_graph = PythonRequirementBlobSet(graph.blobs, python_roots, ())
|
|
796
|
+
# Included requirements may use arbitrary .txt/.in names that the initial
|
|
797
|
+
# manifest registry intentionally does not match. They still feed framework
|
|
798
|
+
# declarations, so register their exact source state before graph/parser
|
|
799
|
+
# degradation is projected into completeness.
|
|
800
|
+
python_declaration = manifest_collector("requirements.txt")
|
|
801
|
+
if python_declaration is None: # pragma: no cover - static registry invariant
|
|
802
|
+
raise ValueError("Python requirements collector is unavailable")
|
|
803
|
+
for path in python_graph.requirement_paths:
|
|
804
|
+
source_statuses.setdefault(
|
|
805
|
+
path,
|
|
806
|
+
PluginSourceStatus(
|
|
807
|
+
path,
|
|
808
|
+
python_declaration.ecosystem,
|
|
809
|
+
python_declaration.source_roles,
|
|
810
|
+
"accepted",
|
|
811
|
+
),
|
|
812
|
+
)
|
|
813
|
+
degraded_roots = dict((*graph.degraded_roots, *python_graph.degraded_roots))
|
|
814
|
+
for path, reason in sorted(degraded_roots.items()):
|
|
815
|
+
status = source_statuses.get(path)
|
|
816
|
+
if status is not None and status.state not in {"omitted", "unavailable"}:
|
|
817
|
+
source_statuses[path] = PluginSourceStatus(
|
|
818
|
+
status.path,
|
|
819
|
+
status.ecosystem,
|
|
820
|
+
status.roles,
|
|
821
|
+
"partial",
|
|
822
|
+
reason,
|
|
823
|
+
)
|
|
659
824
|
blobs = python_graph.blobs
|
|
660
|
-
|
|
661
|
-
|
|
825
|
+
for message in (*graph.diagnostics, *python_graph.diagnostics):
|
|
826
|
+
qualified = f"{ref.value}:{message}"
|
|
827
|
+
if qualified not in diagnostics:
|
|
828
|
+
diagnostics.append(qualified)
|
|
662
829
|
paths = dict.fromkeys(
|
|
663
830
|
(
|
|
664
831
|
*tuple(entry.path for entry in candidates),
|
|
@@ -724,6 +891,23 @@ def collect_ref_facts(
|
|
|
724
891
|
raise ValueError("supported manifest has no collector")
|
|
725
892
|
parsed = collector.parse(text)
|
|
726
893
|
diagnostics.extend(f"{ref.value}:{path}: {notice}" for notice in parsed.notices)
|
|
894
|
+
source_status = source_statuses.get(path)
|
|
895
|
+
if any("truncated" in notice for notice in parsed.notices):
|
|
896
|
+
if source_status is not None and source_status.state != "partial":
|
|
897
|
+
source_statuses[path] = PluginSourceStatus(
|
|
898
|
+
source_status.path,
|
|
899
|
+
source_status.ecosystem,
|
|
900
|
+
source_status.roles,
|
|
901
|
+
"partial",
|
|
902
|
+
"source-item-limit",
|
|
903
|
+
)
|
|
904
|
+
elif source_status is not None and source_status.state != "partial":
|
|
905
|
+
source_statuses[path] = PluginSourceStatus(
|
|
906
|
+
source_status.path,
|
|
907
|
+
source_status.ecosystem,
|
|
908
|
+
source_status.roles,
|
|
909
|
+
"complete",
|
|
910
|
+
)
|
|
727
911
|
facts = [
|
|
728
912
|
ManifestFact(
|
|
729
913
|
"repository.manifest",
|
|
@@ -781,6 +965,14 @@ def collect_ref_facts(
|
|
|
781
965
|
)
|
|
782
966
|
if topology_source and entry is not None:
|
|
783
967
|
unavailable_topology(entry, "parse-unavailable")
|
|
968
|
+
source_status = source_statuses.get(path)
|
|
969
|
+
if source_status is not None:
|
|
970
|
+
source_statuses[path] = PluginSourceStatus(
|
|
971
|
+
source_status.path,
|
|
972
|
+
source_status.ecosystem,
|
|
973
|
+
source_status.roles,
|
|
974
|
+
"unavailable",
|
|
975
|
+
)
|
|
784
976
|
continue
|
|
785
977
|
for fact in facts:
|
|
786
978
|
if fact.kind.startswith("ansible.") and fact.kind != "dependency.declared":
|
|
@@ -820,6 +1012,24 @@ def collect_ref_facts(
|
|
|
820
1012
|
trust=trust,
|
|
821
1013
|
)
|
|
822
1014
|
)
|
|
1015
|
+
plugin_context = FrameworkPluginContext(
|
|
1016
|
+
records=tuple(records),
|
|
1017
|
+
entries=entries,
|
|
1018
|
+
source_statuses=tuple(sorted(source_statuses.values(), key=lambda item: item.path)),
|
|
1019
|
+
ref=ref,
|
|
1020
|
+
commit_sha=commit_sha,
|
|
1021
|
+
)
|
|
1022
|
+
plugin_facts, plugin_observations, plugin_notices = collect_framework_plugins(plugin_context)
|
|
1023
|
+
template_facts, template_observations, template_notices = collect_template_files(plugin_context)
|
|
1024
|
+
records.extend(
|
|
1025
|
+
_plugin_records(
|
|
1026
|
+
(*plugin_facts, *template_facts),
|
|
1027
|
+
ref=ref,
|
|
1028
|
+
commit_sha=commit_sha,
|
|
1029
|
+
trust=trust,
|
|
1030
|
+
)
|
|
1031
|
+
)
|
|
1032
|
+
diagnostics.extend(f"{ref.value}:{notice}" for notice in (*plugin_notices, *template_notices))
|
|
823
1033
|
if coverage_sink is not None:
|
|
824
1034
|
for (domain, scope), observations in sorted(coverage_observations.items()):
|
|
825
1035
|
coverage_sink.append(
|
|
@@ -832,6 +1042,13 @@ def collect_ref_facts(
|
|
|
832
1042
|
commit_sha=commit_sha,
|
|
833
1043
|
)
|
|
834
1044
|
)
|
|
1045
|
+
coverage_sink.extend(
|
|
1046
|
+
_plugin_coverage(
|
|
1047
|
+
(*plugin_observations, *template_observations),
|
|
1048
|
+
ref=ref,
|
|
1049
|
+
commit_sha=commit_sha,
|
|
1050
|
+
)
|
|
1051
|
+
)
|
|
835
1052
|
return records, diagnostics
|
|
836
1053
|
|
|
837
1054
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Bounded source adapters that normalize supported repository ecosystems."""
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Bounded Ansible Galaxy and topology source adapters."""
|
|
@@ -7,7 +7,7 @@ from collections.abc import Mapping
|
|
|
7
7
|
from urllib.parse import urlsplit
|
|
8
8
|
|
|
9
9
|
from ocr_toolkit.common.redaction import redact_url_userinfo
|
|
10
|
-
from ocr_toolkit.evidence.
|
|
10
|
+
from ocr_toolkit.evidence.ecosystems.contracts import (
|
|
11
11
|
MAX_MANIFEST_ITEMS,
|
|
12
12
|
ManifestFact,
|
|
13
13
|
ManifestParseResult,
|
|
@@ -7,7 +7,7 @@ from collections.abc import Mapping
|
|
|
7
7
|
from urllib.parse import urlsplit
|
|
8
8
|
|
|
9
9
|
from ocr_toolkit.common.redaction import redact_url_userinfo
|
|
10
|
-
from ocr_toolkit.evidence.
|
|
10
|
+
from ocr_toolkit.evidence.ecosystems.contracts import (
|
|
11
11
|
MAX_MANIFEST_ITEMS,
|
|
12
12
|
ManifestFact,
|
|
13
13
|
ManifestParseResult,
|
|
@@ -9,7 +9,7 @@ import shlex
|
|
|
9
9
|
import tomllib # type: ignore[import-untyped]
|
|
10
10
|
|
|
11
11
|
from ocr_toolkit.common.redaction import redact_url_userinfo
|
|
12
|
-
from ocr_toolkit.evidence.
|
|
12
|
+
from ocr_toolkit.evidence.ecosystems.contracts import (
|
|
13
13
|
MAX_MANIFEST_ITEMS,
|
|
14
14
|
ManifestFact,
|
|
15
15
|
ManifestParseResult,
|