open-code-review-toolkit 0.8.3__tar.gz → 0.8.4__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.8.3 → open_code_review_toolkit-0.8.4}/PKG-INFO +2 -2
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/README.md +1 -1
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/_version.py +2 -2
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/context/dlp.py +7 -4
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/ocr_result.py +79 -1
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/posting/approval.py +65 -2
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/posting/formatting.py +29 -5
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/posting/result.py +5 -2
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/posting/workflow.py +48 -9
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/preflight.py +1 -1
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/review_runner.py +81 -50
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/.gitignore +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/LICENSE +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/pyproject.toml +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/__init__.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/cli.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/common/__init__.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/common/filesystem.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/common/git.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/common/language.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/common/markdown.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/common/redaction.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/config_writer.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/configure.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/context/__init__.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/context/adapters.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/context/broker.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/context/contracts.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/context/mcp.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/context/policy.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/context/recognizers.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/context/store.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/evidence/__init__.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/evidence/__main__.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/evidence/actions.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/evidence/artifacts.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/evidence/categorize.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/evidence/collect.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/evidence/collectors/__init__.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/evidence/collectors/graphs.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/evidence/collectors/orchestration.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/evidence/collectors/projections.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/evidence/collectors/registry.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/evidence/collectors/sources.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/evidence/coverage.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/evidence/ecosystems/__init__.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/evidence/ecosystems/ansible/__init__.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/evidence/ecosystems/ansible/requirements.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/evidence/ecosystems/ansible/topology.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/evidence/ecosystems/contracts.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/evidence/ecosystems/go.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/evidence/ecosystems/javascript.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/evidence/ecosystems/php.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/evidence/ecosystems/python.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/evidence/frameworks/__init__.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/evidence/frameworks/contracts.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/evidence/frameworks/detection.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/evidence/frameworks/providers/__init__.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/evidence/frameworks/providers/frontend.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/evidence/frameworks/providers/go.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/evidence/frameworks/providers/php.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/evidence/frameworks/providers/python.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/evidence/frameworks/registry.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/evidence/frameworks/schema.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/evidence/frameworks/templates.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/evidence/infrastructure.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/evidence/invocation.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/evidence/mcp.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/evidence/model.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/evidence/policy/__init__.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/evidence/policy/contracts.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/evidence/policy/decisions.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/evidence/policy/guidance.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/evidence/policy/registry.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/evidence/policy/schema.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/evidence/policy/scopes.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/evidence/project.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/evidence/repository.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/evidence/review_context.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/evidence/store/__init__.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/evidence/store/atomic.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/evidence/store/contracts.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/evidence/store/core.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/evidence/store/readback.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/evidence/store/values.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/mcp_config.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/posting/__init__.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/posting/__main__.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/posting/comments.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/posting/gitlab.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/posting/gitlab_approval.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/posting/markers.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/posting/payloads.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/posting/reconciliation.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/posting/settings.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/posting/snapshot.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/posting/suggestions.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/posting/transaction.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/pre_execution.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/provider_config.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/provider_failure.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/providers/__init__.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/providers/gitlab.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/providers/gitlab_context.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/providers/gitlab_discussions.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/providers/gitlab_identity.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/providers/gitlab_remediation.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/py.typed +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/result_contract.py +0 -0
- {open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/result_usage.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: open-code-review-toolkit
|
|
3
|
-
Version: 0.8.
|
|
3
|
+
Version: 0.8.4
|
|
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
|
|
@@ -249,7 +249,7 @@ ocr-ci --help
|
|
|
249
249
|
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.
|
|
250
250
|
The [versioned compatibility policy](docs/compatibility.md) records tested assets and evidence and describes the conservative Dependabot-like qualification workflow for later upstream releases.
|
|
251
251
|
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`.
|
|
252
|
-
The current OCR 1.10.
|
|
252
|
+
The current OCR 1.10.1 integration defaults `OCR_REVIEW_EFFORT` to `medium` for two review rounds. `low` and `high` are explicit one- and three-round alternatives; see the [configuration reference](docs/configuration.md#review-effort) for cost, budget, and precedence boundaries.
|
|
253
253
|
|
|
254
254
|
Stable distributions are published to [PyPI](https://pypi.org/project/open-code-review-toolkit/) and mirrored as checksum-listed, provenance-attested assets in the corresponding [GitHub Release](https://github.com/xeonvs/open-code-review-toolkit/releases). Development snapshots are published only to TestPyPI.
|
|
255
255
|
|
|
@@ -22,7 +22,7 @@ ocr-ci --help
|
|
|
22
22
|
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.
|
|
23
23
|
The [versioned compatibility policy](docs/compatibility.md) records tested assets and evidence and describes the conservative Dependabot-like qualification workflow for later upstream releases.
|
|
24
24
|
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`.
|
|
25
|
-
The current OCR 1.10.
|
|
25
|
+
The current OCR 1.10.1 integration defaults `OCR_REVIEW_EFFORT` to `medium` for two review rounds. `low` and `high` are explicit one- and three-round alternatives; see the [configuration reference](docs/configuration.md#review-effort) for cost, budget, and precedence boundaries.
|
|
26
26
|
|
|
27
27
|
Stable distributions are published to [PyPI](https://pypi.org/project/open-code-review-toolkit/) and mirrored as checksum-listed, provenance-attested assets in the corresponding [GitHub Release](https://github.com/xeonvs/open-code-review-toolkit/releases). Development snapshots are published only to TestPyPI.
|
|
28
28
|
|
{open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/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.8.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 8,
|
|
21
|
+
__version__ = version = '0.8.4'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 8, 4)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
{open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/context/dlp.py
RENAMED
|
@@ -109,7 +109,7 @@ class ForbiddenMatcher:
|
|
|
109
109
|
exact: list[str] = []
|
|
110
110
|
seen: set[str] = set()
|
|
111
111
|
for value in values:
|
|
112
|
-
candidate = normalize_text(value)
|
|
112
|
+
candidate = normalize_text(value, allow_horizontal_tabs=True)
|
|
113
113
|
if not candidate:
|
|
114
114
|
continue
|
|
115
115
|
for representation in (_display_normalize(candidate), _source_normalize(candidate)):
|
|
@@ -157,14 +157,16 @@ class ForbiddenMatcher:
|
|
|
157
157
|
return self.match_reason(value) is not None
|
|
158
158
|
|
|
159
159
|
|
|
160
|
-
def normalize_text(value: object) -> str | None:
|
|
160
|
+
def normalize_text(value: object, *, allow_horizontal_tabs: bool = False) -> str | None:
|
|
161
161
|
"""Normalize NFC/newlines and reject unsupported controls."""
|
|
162
162
|
|
|
163
163
|
if not isinstance(value, str):
|
|
164
164
|
return None
|
|
165
165
|
normalized = unicodedata.normalize("NFC", value.replace("\r\n", "\n").replace("\r", "\n"))
|
|
166
|
+
allowed_controls = "\n\t" if allow_horizontal_tabs else "\n"
|
|
166
167
|
if any(
|
|
167
|
-
unicodedata.category(character) in {"Cc", "Cf", "Cs", "Zl", "Zp"}
|
|
168
|
+
unicodedata.category(character) in {"Cc", "Cf", "Cs", "Zl", "Zp"}
|
|
169
|
+
and character not in allowed_controls
|
|
168
170
|
for character in normalized
|
|
169
171
|
):
|
|
170
172
|
return None
|
|
@@ -178,10 +180,11 @@ def check_text(
|
|
|
178
180
|
publication: bool = False,
|
|
179
181
|
forbidden: tuple[str, ...] = (),
|
|
180
182
|
forbidden_matcher: ForbiddenMatcher | None = None,
|
|
183
|
+
allow_horizontal_tabs: bool = False,
|
|
181
184
|
) -> DLPResult:
|
|
182
185
|
"""Apply independent units, redaction, PII, and optional publication checks."""
|
|
183
186
|
|
|
184
|
-
normalized = normalize_text(value)
|
|
187
|
+
normalized = normalize_text(value, allow_horizontal_tabs=allow_horizontal_tabs)
|
|
185
188
|
if normalized is None:
|
|
186
189
|
return DLPResult(False, None, "invalid_text", "type_or_control")
|
|
187
190
|
normalized_bytes = normalized.encode("utf-8")
|
{open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/ocr_result.py
RENAMED
|
@@ -9,6 +9,7 @@ import secrets
|
|
|
9
9
|
import stat
|
|
10
10
|
import sys
|
|
11
11
|
from collections.abc import Callable, Mapping
|
|
12
|
+
from dataclasses import dataclass
|
|
12
13
|
from pathlib import Path
|
|
13
14
|
from typing import Any
|
|
14
15
|
|
|
@@ -20,6 +21,11 @@ MAX_RESULT_BYTES_HARD_LIMIT = 20_000_000
|
|
|
20
21
|
TOOLKIT_RESULT_KEY = "_ocr_toolkit"
|
|
21
22
|
TOOLKIT_RESULT_SCHEMA_VERSION = 5
|
|
22
23
|
SUPPORTED_TOOLKIT_RESULT_SCHEMA_VERSIONS = frozenset({TOOLKIT_RESULT_SCHEMA_VERSION})
|
|
24
|
+
TOOLKIT_ADVISORY_KEY = "_ocr_toolkit_advisory"
|
|
25
|
+
TOOLKIT_ADVISORY_SCHEMA_VERSION = "ocr.toolkit-advisory/v1"
|
|
26
|
+
TOOLKIT_ADVISORY_KIND = "background_recommended_limit"
|
|
27
|
+
TOOLKIT_ADVISORY_UNIT = "characters"
|
|
28
|
+
MAX_TOOLKIT_ADVISORY_VALUE = 999_999_999_999
|
|
23
29
|
# The receipt can name the 16 configured external servers plus the mandatory built-in.
|
|
24
30
|
MAX_TOOLKIT_MCP_USAGE_SERVERS = 17
|
|
25
31
|
MAX_TOOLKIT_MCP_TOOLS_PER_SERVER = 128
|
|
@@ -57,6 +63,69 @@ class OcrResultTooLarge(Exception):
|
|
|
57
63
|
"""The OCR result artifact exceeds the configured safety limit."""
|
|
58
64
|
|
|
59
65
|
|
|
66
|
+
@dataclass(frozen=True, slots=True)
|
|
67
|
+
class OcrToolkitAdvisory:
|
|
68
|
+
"""Carry one validated toolkit-authored numeric OCR advisory."""
|
|
69
|
+
|
|
70
|
+
actual: int
|
|
71
|
+
recommended: int
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def parse_toolkit_advisory(value: Any) -> OcrToolkitAdvisory:
|
|
75
|
+
"""Parse the exact private toolkit advisory without accepting extensions."""
|
|
76
|
+
|
|
77
|
+
if not isinstance(value, dict) or set(value) != {
|
|
78
|
+
"schema_version",
|
|
79
|
+
"kind",
|
|
80
|
+
"actual",
|
|
81
|
+
"recommended",
|
|
82
|
+
"unit",
|
|
83
|
+
}:
|
|
84
|
+
raise OcrResultMalformed("OCR toolkit advisory has an unsupported schema")
|
|
85
|
+
actual = value.get("actual")
|
|
86
|
+
recommended = value.get("recommended")
|
|
87
|
+
if (
|
|
88
|
+
value.get("schema_version") != TOOLKIT_ADVISORY_SCHEMA_VERSION
|
|
89
|
+
or value.get("kind") != TOOLKIT_ADVISORY_KIND
|
|
90
|
+
or value.get("unit") != TOOLKIT_ADVISORY_UNIT
|
|
91
|
+
or not isinstance(actual, int)
|
|
92
|
+
or isinstance(actual, bool)
|
|
93
|
+
or not isinstance(recommended, int)
|
|
94
|
+
or isinstance(recommended, bool)
|
|
95
|
+
or not 0 < recommended < actual <= MAX_TOOLKIT_ADVISORY_VALUE
|
|
96
|
+
):
|
|
97
|
+
raise OcrResultMalformed("OCR toolkit advisory has invalid closed values")
|
|
98
|
+
return OcrToolkitAdvisory(actual=actual, recommended=recommended)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def background_recommended_advisory(*, actual: int, recommended: int) -> OcrToolkitAdvisory:
|
|
102
|
+
"""Construct one validated background recommendation advisory."""
|
|
103
|
+
|
|
104
|
+
return parse_toolkit_advisory(
|
|
105
|
+
{
|
|
106
|
+
"schema_version": TOOLKIT_ADVISORY_SCHEMA_VERSION,
|
|
107
|
+
"kind": TOOLKIT_ADVISORY_KIND,
|
|
108
|
+
"actual": actual,
|
|
109
|
+
"recommended": recommended,
|
|
110
|
+
"unit": TOOLKIT_ADVISORY_UNIT,
|
|
111
|
+
}
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def toolkit_advisory_payload(advisory: OcrToolkitAdvisory) -> dict[str, object]:
|
|
116
|
+
"""Serialize a validated advisory into its exact private result shape."""
|
|
117
|
+
|
|
118
|
+
payload: dict[str, object] = {
|
|
119
|
+
"schema_version": TOOLKIT_ADVISORY_SCHEMA_VERSION,
|
|
120
|
+
"kind": TOOLKIT_ADVISORY_KIND,
|
|
121
|
+
"actual": advisory.actual,
|
|
122
|
+
"recommended": advisory.recommended,
|
|
123
|
+
"unit": TOOLKIT_ADVISORY_UNIT,
|
|
124
|
+
}
|
|
125
|
+
parse_toolkit_advisory(payload)
|
|
126
|
+
return payload
|
|
127
|
+
|
|
128
|
+
|
|
60
129
|
def max_result_bytes() -> int:
|
|
61
130
|
"""Return the maximum OCR JSON artifact size to read into memory."""
|
|
62
131
|
|
|
@@ -234,7 +303,16 @@ def _decode_result(data: bytes) -> Any:
|
|
|
234
303
|
except UnicodeDecodeError as exc:
|
|
235
304
|
raise OcrResultMalformed(str(exc)) from exc
|
|
236
305
|
try:
|
|
237
|
-
|
|
306
|
+
|
|
307
|
+
def reject_duplicate_advisory(pairs: list[tuple[str, Any]]) -> dict[str, Any]:
|
|
308
|
+
result: dict[str, Any] = {}
|
|
309
|
+
for key, value in pairs:
|
|
310
|
+
if key == TOOLKIT_ADVISORY_KEY and key in result:
|
|
311
|
+
raise OcrResultMalformed("OCR result repeats the reserved toolkit advisory")
|
|
312
|
+
result[key] = value
|
|
313
|
+
return result
|
|
314
|
+
|
|
315
|
+
return json.loads(text, object_pairs_hook=reject_duplicate_advisory)
|
|
238
316
|
except (json.JSONDecodeError, RecursionError) as exc:
|
|
239
317
|
raise OcrResultMalformed(str(exc)) from exc
|
|
240
318
|
|
|
@@ -14,10 +14,11 @@ from ocr_toolkit.ocr_result import (
|
|
|
14
14
|
TOOLKIT_MCP_SERVER_NAME_RE,
|
|
15
15
|
)
|
|
16
16
|
from ocr_toolkit.posting.settings import BooleanSetting
|
|
17
|
-
from ocr_toolkit.result_contract import ReviewOutcome
|
|
17
|
+
from ocr_toolkit.result_contract import OcrResultContractError, ReviewOutcome
|
|
18
18
|
|
|
19
19
|
ALLOWED_CATEGORIES = frozenset({"style", "documentation", "maintainability"})
|
|
20
20
|
MAX_APPROVABLE_FINDINGS = 3
|
|
21
|
+
INVALID_APPROVAL_RECEIPT_REASON = "the review-time approval receipt is missing or invalid"
|
|
21
22
|
|
|
22
23
|
|
|
23
24
|
class ApprovalStatus(str, Enum):
|
|
@@ -218,6 +219,23 @@ def publication_dlp_state(value: Any) -> str | None:
|
|
|
218
219
|
)
|
|
219
220
|
):
|
|
220
221
|
return None
|
|
222
|
+
selected = original["selected"]
|
|
223
|
+
completed = original["completed"]
|
|
224
|
+
reused = original["reused"]
|
|
225
|
+
failed = original["failed"]
|
|
226
|
+
waived = original["waived"]
|
|
227
|
+
outcome = original["outcome"]
|
|
228
|
+
derived_outcomes = {"failed"} | (
|
|
229
|
+
{"skipped"}
|
|
230
|
+
if selected == 0
|
|
231
|
+
else {"clean", "warning"}
|
|
232
|
+
if failed == 0
|
|
233
|
+
else {"failed"}
|
|
234
|
+
if failed == selected
|
|
235
|
+
else {"partial"}
|
|
236
|
+
)
|
|
237
|
+
if selected != completed + reused + failed + waived or outcome not in derived_outcomes:
|
|
238
|
+
return None
|
|
221
239
|
return "publication-filtered"
|
|
222
240
|
|
|
223
241
|
|
|
@@ -237,7 +255,7 @@ def _valid_dlp_reason_counts(value: Any) -> bool:
|
|
|
237
255
|
def automatic_approval_metadata_reason(toolkit_metadata: Any) -> str:
|
|
238
256
|
"""Return the closed review-time receipt blocker for automatic approval."""
|
|
239
257
|
|
|
240
|
-
invalid =
|
|
258
|
+
invalid = INVALID_APPROVAL_RECEIPT_REASON
|
|
241
259
|
if not isinstance(toolkit_metadata, dict):
|
|
242
260
|
return invalid
|
|
243
261
|
if toolkit_metadata.get("schema_version") != 5 or set(toolkit_metadata) != {
|
|
@@ -433,6 +451,51 @@ def automatic_approval_metadata_reason(toolkit_metadata: Any) -> str:
|
|
|
433
451
|
return ""
|
|
434
452
|
|
|
435
453
|
|
|
454
|
+
def toolkit_receipt_is_valid(toolkit_metadata: Any) -> bool:
|
|
455
|
+
"""Return whether metadata is an exact receipt v5, including valid blockers."""
|
|
456
|
+
|
|
457
|
+
return automatic_approval_metadata_reason(toolkit_metadata) != INVALID_APPROVAL_RECEIPT_REASON
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
def publication_outcome_for_summary(outcome: ReviewOutcome, publication: Any) -> ReviewOutcome:
|
|
461
|
+
"""Recover only validated original coverage facts from a filtered receipt."""
|
|
462
|
+
|
|
463
|
+
if publication_dlp_state(publication) != "publication-filtered":
|
|
464
|
+
return outcome
|
|
465
|
+
if outcome.kind != "partial" or outcome.manifest_present:
|
|
466
|
+
raise OcrResultContractError(
|
|
467
|
+
"publication-filtered receipt is not bound to a safe result projection"
|
|
468
|
+
)
|
|
469
|
+
original = publication["original"]
|
|
470
|
+
kind = original["outcome"]
|
|
471
|
+
if outcome.budget_exceeded and kind != "partial":
|
|
472
|
+
raise OcrResultContractError(
|
|
473
|
+
"publication-filtered receipt contradicts the result budget state"
|
|
474
|
+
)
|
|
475
|
+
counts = {
|
|
476
|
+
field: original[field] for field in ("selected", "completed", "reused", "failed", "waived")
|
|
477
|
+
}
|
|
478
|
+
manifest_present = any(counts.values())
|
|
479
|
+
status = {
|
|
480
|
+
"clean": "complete" if manifest_present else "success",
|
|
481
|
+
"warning": "completed_with_warnings",
|
|
482
|
+
"partial": "budget_exceeded" if outcome.budget_exceeded else "completed_with_errors",
|
|
483
|
+
"failed": "failed",
|
|
484
|
+
"skipped": "skipped",
|
|
485
|
+
}[kind]
|
|
486
|
+
return ReviewOutcome(
|
|
487
|
+
status=status,
|
|
488
|
+
kind=kind,
|
|
489
|
+
budget_exceeded=outcome.budget_exceeded and kind == "partial",
|
|
490
|
+
manifest_present=manifest_present,
|
|
491
|
+
selected_count=counts["selected"],
|
|
492
|
+
completed_count=counts["completed"],
|
|
493
|
+
reused_count=counts["reused"],
|
|
494
|
+
failed_count=counts["failed"],
|
|
495
|
+
waived_count=counts["waived"],
|
|
496
|
+
)
|
|
497
|
+
|
|
498
|
+
|
|
436
499
|
def _valid_evidence_actions(value: Any, evidence_calls: Any) -> bool:
|
|
437
500
|
"""Validate verified counts or an explicit unavailable attribution state."""
|
|
438
501
|
|
|
@@ -23,6 +23,7 @@ from ocr_toolkit.ocr_result import (
|
|
|
23
23
|
PUBLIC_REVIEW_TOOL_CALL_NAMES,
|
|
24
24
|
SUPPORTED_TOOLKIT_RESULT_SCHEMA_VERSIONS,
|
|
25
25
|
TOOLKIT_MCP_SERVER_NAME_RE,
|
|
26
|
+
OcrToolkitAdvisory,
|
|
26
27
|
)
|
|
27
28
|
from ocr_toolkit.posting.approval import (
|
|
28
29
|
ApprovalResult,
|
|
@@ -599,10 +600,10 @@ def format_publication_dlp_details(signal: dict[str, Any] | None) -> str:
|
|
|
599
600
|
omitted = signal["omitted"]
|
|
600
601
|
carried = signal["carried_forward_comments"]
|
|
601
602
|
completeness = (
|
|
602
|
-
"One or more
|
|
603
|
-
"
|
|
603
|
+
"One or more public projection units were omitted. OCR coverage is reported "
|
|
604
|
+
"separately, and automatic approval remains unavailable."
|
|
604
605
|
if omitted["comments"] or omitted["warnings"]
|
|
605
|
-
else "The
|
|
606
|
+
else "The public projection changed, so automatic approval remains unavailable."
|
|
606
607
|
)
|
|
607
608
|
return "\n".join(
|
|
608
609
|
[
|
|
@@ -830,7 +831,11 @@ def format_reviewer_guide(
|
|
|
830
831
|
enumerate(comments),
|
|
831
832
|
key=lambda item: _guide_comment_rank(item[1], item[0]),
|
|
832
833
|
)
|
|
833
|
-
guide_comments =
|
|
834
|
+
guide_comments = (
|
|
835
|
+
[comment for _, comment in ranked_comments[:MAX_REVIEWER_GUIDE_COMMENTS]]
|
|
836
|
+
if len(comments) >= 2
|
|
837
|
+
else []
|
|
838
|
+
)
|
|
834
839
|
if guide_comments:
|
|
835
840
|
lines.append("")
|
|
836
841
|
lines.append("### Recommended focus areas")
|
|
@@ -879,6 +884,8 @@ def _review_outcome_line(
|
|
|
879
884
|
marker, status_text = "⚠️", "Review stopped at token budget"
|
|
880
885
|
elif partial_result:
|
|
881
886
|
marker, status_text = "⚠️", "Review incomplete"
|
|
887
|
+
elif outcome_status == "publication-filtered":
|
|
888
|
+
marker, status_text = "⚠️", "Review complete with publication filtering"
|
|
882
889
|
elif outcome_status in {"warning", "completed_with_warnings"} or warning_count:
|
|
883
890
|
marker, status_text = "⚠️", "Review complete with warnings"
|
|
884
891
|
elif has_finding_state:
|
|
@@ -922,6 +929,17 @@ def _review_outcome_line(
|
|
|
922
929
|
return f"{prefix}**{status_text} — {result_text}**"
|
|
923
930
|
|
|
924
931
|
|
|
932
|
+
def format_ocr_core_advisory(advisory: OcrToolkitAdvisory | None) -> str:
|
|
933
|
+
"""Render one validated numeric OCR advisory for Technical details only."""
|
|
934
|
+
|
|
935
|
+
if advisory is None:
|
|
936
|
+
return ""
|
|
937
|
+
return (
|
|
938
|
+
f"- OCR core advisory: background {advisory.actual} characters; recommended "
|
|
939
|
+
f"{advisory.recommended} characters; accepted by OCR core"
|
|
940
|
+
)
|
|
941
|
+
|
|
942
|
+
|
|
925
943
|
def summarize_result(
|
|
926
944
|
total: int,
|
|
927
945
|
inline_count: int,
|
|
@@ -933,6 +951,7 @@ def summarize_result(
|
|
|
933
951
|
tool_calls_summary: str = "",
|
|
934
952
|
mcp_usage_summary: str = "",
|
|
935
953
|
token_usage_summary: str = "",
|
|
954
|
+
ocr_core_advisory_summary: str = "",
|
|
936
955
|
publication_dlp_details: str = "",
|
|
937
956
|
reviewer_guide: str = "",
|
|
938
957
|
fallback_reasons: Mapping[str, int] | None = None,
|
|
@@ -1044,7 +1063,12 @@ def summarize_result(
|
|
|
1044
1063
|
)
|
|
1045
1064
|
if reasons:
|
|
1046
1065
|
technical.append(f"- Fallback reasons: {reasons}")
|
|
1047
|
-
for summary in (
|
|
1066
|
+
for summary in (
|
|
1067
|
+
mcp_usage_summary,
|
|
1068
|
+
tool_calls_summary,
|
|
1069
|
+
token_usage_summary,
|
|
1070
|
+
ocr_core_advisory_summary,
|
|
1071
|
+
):
|
|
1048
1072
|
if summary:
|
|
1049
1073
|
technical.append(summary)
|
|
1050
1074
|
technical.append(f"- Review mode: `{post_mode()}`")
|
{open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/posting/result.py
RENAMED
|
@@ -122,7 +122,10 @@ def _safe_detail(value: object, reason: str) -> str:
|
|
|
122
122
|
|
|
123
123
|
|
|
124
124
|
def normalize_coverage_diagnostics(
|
|
125
|
-
outcome: ReviewOutcome,
|
|
125
|
+
outcome: ReviewOutcome,
|
|
126
|
+
warnings: Sequence[Any],
|
|
127
|
+
*,
|
|
128
|
+
legacy_warning_fallback: bool = True,
|
|
126
129
|
) -> CoverageDiagnostics:
|
|
127
130
|
"""Normalize manifest failures or legacy warnings once at the posting boundary."""
|
|
128
131
|
|
|
@@ -136,7 +139,7 @@ def normalize_coverage_diagnostics(
|
|
|
136
139
|
)
|
|
137
140
|
for item in outcome.failed_items
|
|
138
141
|
)
|
|
139
|
-
elif outcome.kind == "partial":
|
|
142
|
+
elif outcome.kind == "partial" and legacy_warning_fallback:
|
|
140
143
|
for warning in warnings:
|
|
141
144
|
path = warning.get("file") or warning.get("path") if isinstance(warning, dict) else None
|
|
142
145
|
candidates.append((path, _legacy_reason(warning), ocr_warning_text(warning)))
|
|
@@ -17,11 +17,13 @@ from ocr_toolkit.common.git import isolated_git_environment, read_only_git_prefi
|
|
|
17
17
|
from ocr_toolkit.common.markdown import markdown_code_block, neutralize_quick_actions
|
|
18
18
|
from ocr_toolkit.evidence.artifacts import repository_artifacts
|
|
19
19
|
from ocr_toolkit.ocr_result import (
|
|
20
|
+
TOOLKIT_ADVISORY_KEY,
|
|
20
21
|
TOOLKIT_RESULT_KEY,
|
|
21
22
|
OcrResultMalformed,
|
|
22
23
|
OcrResultMissing,
|
|
23
24
|
OcrResultTooLarge,
|
|
24
25
|
load_ocr_result,
|
|
26
|
+
parse_toolkit_advisory,
|
|
25
27
|
)
|
|
26
28
|
from ocr_toolkit.posting import gitlab as gitlab_api
|
|
27
29
|
from ocr_toolkit.posting.approval import (
|
|
@@ -31,6 +33,8 @@ from ocr_toolkit.posting.approval import (
|
|
|
31
33
|
evaluate_approval_policy,
|
|
32
34
|
provisional_approval_result,
|
|
33
35
|
publication_dlp_state,
|
|
36
|
+
publication_outcome_for_summary,
|
|
37
|
+
toolkit_receipt_is_valid,
|
|
34
38
|
)
|
|
35
39
|
from ocr_toolkit.posting.comments import (
|
|
36
40
|
clean_text,
|
|
@@ -41,6 +45,7 @@ from ocr_toolkit.posting.formatting import (
|
|
|
41
45
|
format_fallback_comment_chunks,
|
|
42
46
|
format_inline_comment,
|
|
43
47
|
format_mcp_usage_summary,
|
|
48
|
+
format_ocr_core_advisory,
|
|
44
49
|
format_omitted_comments_summary,
|
|
45
50
|
format_publication_dlp_details,
|
|
46
51
|
format_reviewer_guide,
|
|
@@ -625,6 +630,19 @@ def post_results(config: GitLabConfig, result: dict[str, Any]) -> int:
|
|
|
625
630
|
title="**Open Code Review publication policy error**",
|
|
626
631
|
)
|
|
627
632
|
|
|
633
|
+
advisory = None
|
|
634
|
+
if TOOLKIT_ADVISORY_KEY in result:
|
|
635
|
+
try:
|
|
636
|
+
advisory = parse_toolkit_advisory(result[TOOLKIT_ADVISORY_KEY])
|
|
637
|
+
except OcrResultMalformed as exc:
|
|
638
|
+
return invalid_ocr_schema_exit(config, str(exc))
|
|
639
|
+
if not toolkit_receipt_is_valid(toolkit_metadata):
|
|
640
|
+
return invalid_ocr_schema_exit(
|
|
641
|
+
config,
|
|
642
|
+
"OCR toolkit advisory is not bound to a valid receipt v5",
|
|
643
|
+
)
|
|
644
|
+
ocr_core_advisory_summary = format_ocr_core_advisory(advisory)
|
|
645
|
+
|
|
628
646
|
comments_value = result.get("comments", [])
|
|
629
647
|
warnings_value = result.get("warnings", [])
|
|
630
648
|
tool_calls_summary = format_tool_calls_summary(result.get("tool_calls"))
|
|
@@ -651,16 +669,25 @@ def post_results(config: GitLabConfig, result: dict[str, Any]) -> int:
|
|
|
651
669
|
approval_comments = list(comments)
|
|
652
670
|
|
|
653
671
|
warnings = warnings_value
|
|
654
|
-
|
|
655
|
-
|
|
672
|
+
try:
|
|
673
|
+
summary_outcome = publication_outcome_for_summary(outcome, publication)
|
|
674
|
+
except OcrResultContractError as exc:
|
|
675
|
+
return invalid_ocr_schema_exit(config, str(exc))
|
|
676
|
+
coverage_diagnostics = normalize_coverage_diagnostics(
|
|
677
|
+
summary_outcome,
|
|
678
|
+
warnings,
|
|
679
|
+
legacy_warning_fallback=publication_state != "publication-filtered",
|
|
680
|
+
)
|
|
681
|
+
if summary_outcome.kind == "failed":
|
|
656
682
|
return post_manifest_failure(
|
|
657
683
|
config,
|
|
658
|
-
|
|
684
|
+
summary_outcome,
|
|
659
685
|
outcome_message,
|
|
660
686
|
warnings,
|
|
661
687
|
tool_calls_summary=tool_calls_summary,
|
|
662
688
|
mcp_usage_summary=mcp_usage_summary,
|
|
663
689
|
token_usage_summary=token_usage_summary,
|
|
690
|
+
ocr_core_advisory_summary=ocr_core_advisory_summary,
|
|
664
691
|
)
|
|
665
692
|
|
|
666
693
|
billing_reason = llm_billing_failure_reason(warnings)
|
|
@@ -733,11 +760,19 @@ def post_results(config: GitLabConfig, result: dict[str, Any]) -> int:
|
|
|
733
760
|
summary_run_id = secrets.token_hex(16)
|
|
734
761
|
receipt_sha, reviewed_author_id = approval_receipt_identity(result.get(TOOLKIT_RESULT_KEY))
|
|
735
762
|
reviewed_commit = receipt_sha or reviewed_sha()
|
|
763
|
+
summary_status = (
|
|
764
|
+
"publication-filtered"
|
|
765
|
+
if publication_state == "publication-filtered"
|
|
766
|
+
and summary_outcome.kind in {"clean", "warning"}
|
|
767
|
+
else "budget_exceeded"
|
|
768
|
+
if summary_outcome.budget_exceeded
|
|
769
|
+
else summary_outcome.kind
|
|
770
|
+
)
|
|
736
771
|
reviewer_guide = format_reviewer_guide(
|
|
737
772
|
comments,
|
|
738
773
|
omitted_count,
|
|
739
|
-
outcome_status=
|
|
740
|
-
coverage_summary=
|
|
774
|
+
outcome_status=summary_status,
|
|
775
|
+
coverage_summary=summary_outcome.coverage_summary,
|
|
741
776
|
)
|
|
742
777
|
|
|
743
778
|
if publishable_comment_count == 0:
|
|
@@ -754,13 +789,14 @@ def post_results(config: GitLabConfig, result: dict[str, Any]) -> int:
|
|
|
754
789
|
tool_calls_summary=tool_calls_summary,
|
|
755
790
|
mcp_usage_summary=mcp_usage_summary,
|
|
756
791
|
token_usage_summary=token_usage_summary,
|
|
792
|
+
ocr_core_advisory_summary=ocr_core_advisory_summary,
|
|
757
793
|
publication_dlp_details=dlp_details,
|
|
758
794
|
reviewer_guide=reviewer_guide,
|
|
759
795
|
reviewed_sha=reviewed_commit,
|
|
760
796
|
mr_head_sha=mr_head_sha(),
|
|
761
|
-
outcome_status=
|
|
797
|
+
outcome_status=summary_status,
|
|
762
798
|
outcome_message=outcome_message,
|
|
763
|
-
coverage_summary=
|
|
799
|
+
coverage_summary=summary_outcome.coverage_summary,
|
|
764
800
|
coverage_diagnostics=coverage_diagnostics,
|
|
765
801
|
warnings=warnings,
|
|
766
802
|
suppressed_count=suppressed_count,
|
|
@@ -940,14 +976,15 @@ def post_results(config: GitLabConfig, result: dict[str, Any]) -> int:
|
|
|
940
976
|
tool_calls_summary=tool_calls_summary,
|
|
941
977
|
mcp_usage_summary=mcp_usage_summary,
|
|
942
978
|
token_usage_summary=token_usage_summary,
|
|
979
|
+
ocr_core_advisory_summary=ocr_core_advisory_summary,
|
|
943
980
|
publication_dlp_details=dlp_details,
|
|
944
981
|
reviewer_guide=reviewer_guide,
|
|
945
982
|
fallback_reasons=fallback_reasons,
|
|
946
983
|
reviewed_sha=reviewed_commit,
|
|
947
984
|
mr_head_sha=mr_head_sha(),
|
|
948
|
-
outcome_status=
|
|
985
|
+
outcome_status=summary_status,
|
|
949
986
|
outcome_message=outcome_message,
|
|
950
|
-
coverage_summary=
|
|
987
|
+
coverage_summary=summary_outcome.coverage_summary,
|
|
951
988
|
coverage_diagnostics=coverage_diagnostics,
|
|
952
989
|
warnings=warnings,
|
|
953
990
|
suppressed_count=suppressed_count,
|
|
@@ -1060,6 +1097,7 @@ def post_manifest_failure(
|
|
|
1060
1097
|
tool_calls_summary: str = "",
|
|
1061
1098
|
mcp_usage_summary: str = "",
|
|
1062
1099
|
token_usage_summary: str = "",
|
|
1100
|
+
ocr_core_advisory_summary: str = "",
|
|
1063
1101
|
) -> int:
|
|
1064
1102
|
"""Post a manifest-declared run failure while preserving prior review notes."""
|
|
1065
1103
|
|
|
@@ -1072,6 +1110,7 @@ def post_manifest_failure(
|
|
|
1072
1110
|
tool_calls_summary=tool_calls_summary,
|
|
1073
1111
|
mcp_usage_summary=mcp_usage_summary,
|
|
1074
1112
|
token_usage_summary=token_usage_summary,
|
|
1113
|
+
ocr_core_advisory_summary=ocr_core_advisory_summary,
|
|
1075
1114
|
outcome_status="failed",
|
|
1076
1115
|
outcome_message=message,
|
|
1077
1116
|
coverage_summary=outcome.coverage_summary,
|
{open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/review_runner.py
RENAMED
|
@@ -76,13 +76,17 @@ from ocr_toolkit.evidence.store import EvidenceStore, EvidenceStoreError
|
|
|
76
76
|
from ocr_toolkit.ocr_result import (
|
|
77
77
|
MAX_TOOLKIT_MCP_USAGE_COUNT,
|
|
78
78
|
PUBLIC_REVIEW_TOOL_CALL_NAMES,
|
|
79
|
+
TOOLKIT_ADVISORY_KEY,
|
|
79
80
|
TOOLKIT_RESULT_KEY,
|
|
80
81
|
TOOLKIT_RESULT_SCHEMA_VERSION,
|
|
81
82
|
OcrResultMalformed,
|
|
82
83
|
OcrResultMissing,
|
|
83
84
|
OcrResultTooLarge,
|
|
85
|
+
OcrToolkitAdvisory,
|
|
86
|
+
background_recommended_advisory,
|
|
84
87
|
inspect_ocr_result,
|
|
85
88
|
load_ocr_result,
|
|
89
|
+
toolkit_advisory_payload,
|
|
86
90
|
transform_ocr_result,
|
|
87
91
|
)
|
|
88
92
|
from ocr_toolkit.posting.result import ocr_warning_text
|
|
@@ -156,7 +160,7 @@ class ReviewRunnerError(Exception):
|
|
|
156
160
|
class BackgroundQualification:
|
|
157
161
|
"""Carry closed toolkit-authored projections of installed OCR diagnostics."""
|
|
158
162
|
|
|
159
|
-
|
|
163
|
+
advisory: OcrToolkitAdvisory | None = None
|
|
160
164
|
operator_notices: tuple[str, ...] = ()
|
|
161
165
|
|
|
162
166
|
|
|
@@ -441,7 +445,13 @@ def _review_receipt(
|
|
|
441
445
|
}
|
|
442
446
|
|
|
443
447
|
|
|
444
|
-
def _dlp_reasons(
|
|
448
|
+
def _dlp_reasons(
|
|
449
|
+
value: object,
|
|
450
|
+
*,
|
|
451
|
+
budgets: TextBudgets,
|
|
452
|
+
matcher: ForbiddenMatcher,
|
|
453
|
+
allow_horizontal_tabs: bool = False,
|
|
454
|
+
) -> Counter[str]:
|
|
445
455
|
"""Count closed DLP failures without retaining hostile strings or locations."""
|
|
446
456
|
|
|
447
457
|
reasons: Counter[str] = Counter()
|
|
@@ -459,12 +469,24 @@ def _dlp_reasons(value: object, *, budgets: TextBudgets, matcher: ForbiddenMatch
|
|
|
459
469
|
budgets=budgets,
|
|
460
470
|
publication=True,
|
|
461
471
|
forbidden_matcher=matcher,
|
|
472
|
+
allow_horizontal_tabs=allow_horizontal_tabs,
|
|
462
473
|
)
|
|
463
474
|
if not checked.admitted:
|
|
464
475
|
reasons[checked.reason] += 1
|
|
465
476
|
return reasons
|
|
466
477
|
|
|
467
478
|
|
|
479
|
+
def _code_field_allows_horizontal_tabs(path: tuple[object, ...]) -> bool:
|
|
480
|
+
"""Allow HTAB only in the two closed code-bearing finding fields."""
|
|
481
|
+
|
|
482
|
+
return bool(
|
|
483
|
+
len(path) == 3
|
|
484
|
+
and path[0] == "comments"
|
|
485
|
+
and isinstance(path[1], int)
|
|
486
|
+
and path[2] in {"existing_code", "suggestion_code"}
|
|
487
|
+
)
|
|
488
|
+
|
|
489
|
+
|
|
468
490
|
def _private_dlp_decisions(
|
|
469
491
|
payload: dict[str, object], *, forbidden: tuple[str, ...]
|
|
470
492
|
) -> dict[str, object]:
|
|
@@ -527,6 +549,7 @@ def _private_dlp_decisions(
|
|
|
527
549
|
budgets=budgets,
|
|
528
550
|
publication=True,
|
|
529
551
|
forbidden_matcher=matcher,
|
|
552
|
+
allow_horizontal_tabs=_code_field_allows_horizontal_tabs(path),
|
|
530
553
|
)
|
|
531
554
|
if checked.admitted:
|
|
532
555
|
continue
|
|
@@ -582,32 +605,36 @@ def _write_private_dlp_decisions(
|
|
|
582
605
|
raise ReviewRunnerError("OCR private DLP diagnostics could not be written") from exc
|
|
583
606
|
|
|
584
607
|
|
|
585
|
-
def _publication_sinks(payload: dict[str, object]) -> list[object]:
|
|
608
|
+
def _publication_sinks(payload: dict[str, object]) -> list[tuple[object, bool]]:
|
|
586
609
|
"""Select only OCR-controlled values that the posting owner can render."""
|
|
587
610
|
|
|
588
|
-
sinks: list[object] = []
|
|
611
|
+
sinks: list[tuple[object, bool]] = []
|
|
589
612
|
message = payload.get("message")
|
|
590
613
|
if message is not None:
|
|
591
|
-
sinks.append(message)
|
|
614
|
+
sinks.append((message, False))
|
|
592
615
|
comments = payload.get("comments")
|
|
593
616
|
if isinstance(comments, list):
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
617
|
+
for item in comments:
|
|
618
|
+
if not isinstance(item, dict):
|
|
619
|
+
continue
|
|
620
|
+
sinks.extend(
|
|
621
|
+
(
|
|
622
|
+
value,
|
|
623
|
+
isinstance(value, str) and key in {"existing_code", "suggestion_code"},
|
|
624
|
+
)
|
|
625
|
+
for key, value in item.items()
|
|
626
|
+
if key in QUARANTINE_COMMENT_FIELDS
|
|
627
|
+
)
|
|
599
628
|
warnings = payload.get("warnings")
|
|
600
629
|
if warnings is not None:
|
|
601
|
-
sinks.append(warnings)
|
|
630
|
+
sinks.append((warnings, False))
|
|
602
631
|
manifest = payload.get("manifest")
|
|
603
632
|
coverage = manifest.get("coverage") if isinstance(manifest, dict) else None
|
|
604
633
|
failed = coverage.get("failed") if isinstance(coverage, dict) else None
|
|
605
634
|
if isinstance(failed, list):
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
if isinstance(item, dict)
|
|
610
|
-
)
|
|
635
|
+
for item in failed:
|
|
636
|
+
if isinstance(item, dict):
|
|
637
|
+
sinks.extend((item[key], False) for key in ("path", "reason") if key in item)
|
|
611
638
|
return sinks
|
|
612
639
|
|
|
613
640
|
|
|
@@ -729,7 +756,14 @@ def _safe_publication_comments(
|
|
|
729
756
|
for key, field_value in item.items():
|
|
730
757
|
if not isinstance(key, str) or key not in QUARANTINE_COMMENT_FIELDS:
|
|
731
758
|
continue
|
|
732
|
-
if _dlp_reasons(
|
|
759
|
+
if _dlp_reasons(
|
|
760
|
+
field_value,
|
|
761
|
+
budgets=budgets,
|
|
762
|
+
matcher=matcher,
|
|
763
|
+
allow_horizontal_tabs=(
|
|
764
|
+
isinstance(field_value, str) and key in {"existing_code", "suggestion_code"}
|
|
765
|
+
),
|
|
766
|
+
):
|
|
733
767
|
omitted_fields += 1
|
|
734
768
|
content_unsafe = content_unsafe or key == "content"
|
|
735
769
|
continue
|
|
@@ -844,8 +878,15 @@ def _publication_projection(
|
|
|
844
878
|
budgets = TextBudgets(max_chars=2_000_000, max_bytes=8_000_000, max_lines=100_000)
|
|
845
879
|
matcher = ForbiddenMatcher.compile(forbidden)
|
|
846
880
|
sink_reasons: Counter[str] = Counter()
|
|
847
|
-
for sink in _publication_sinks(payload):
|
|
848
|
-
sink_reasons.update(
|
|
881
|
+
for sink, allow_horizontal_tabs in _publication_sinks(payload):
|
|
882
|
+
sink_reasons.update(
|
|
883
|
+
_dlp_reasons(
|
|
884
|
+
sink,
|
|
885
|
+
budgets=budgets,
|
|
886
|
+
matcher=matcher,
|
|
887
|
+
allow_horizontal_tabs=allow_horizontal_tabs,
|
|
888
|
+
)
|
|
889
|
+
)
|
|
849
890
|
sanitized, private_reasons, redacted_fields = _sanitize_nonpublication_fields(
|
|
850
891
|
payload, budgets=budgets, matcher=matcher
|
|
851
892
|
)
|
|
@@ -871,7 +912,7 @@ def _publication_projection(
|
|
|
871
912
|
payload.get("warnings"), budgets=budgets, matcher=matcher
|
|
872
913
|
)
|
|
873
914
|
projected: dict[str, object] = {
|
|
874
|
-
"status": "completed_with_errors",
|
|
915
|
+
"status": "budget_exceeded" if outcome.budget_exceeded else "completed_with_errors",
|
|
875
916
|
"message": (
|
|
876
917
|
"Publication policy produced a safe partial OCR result. Independently safe "
|
|
877
918
|
"findings may be published, but the result must not be treated as a complete "
|
|
@@ -883,6 +924,8 @@ def _publication_projection(
|
|
|
883
924
|
payload.get("tool_calls"), allowed_tools=allowed_tools
|
|
884
925
|
),
|
|
885
926
|
}
|
|
927
|
+
if outcome.budget_exceeded:
|
|
928
|
+
projected["summary"] = {"budget_exceeded": True}
|
|
886
929
|
else:
|
|
887
930
|
reasons = sink_reasons + private_reasons
|
|
888
931
|
return (
|
|
@@ -924,7 +967,7 @@ def _finalize_ocr_result(
|
|
|
924
967
|
evidence_action_counts: dict[str, int] | None = None,
|
|
925
968
|
*,
|
|
926
969
|
forbidden: tuple[str, ...],
|
|
927
|
-
|
|
970
|
+
toolkit_advisory: OcrToolkitAdvisory | None = None,
|
|
928
971
|
) -> tuple[dict[str, int], bool, dict[str, object]]:
|
|
929
972
|
"""Validate, DLP-project, and receipt-bind one result in one atomic read/replace."""
|
|
930
973
|
|
|
@@ -935,25 +978,12 @@ def _finalize_ocr_result(
|
|
|
935
978
|
|
|
936
979
|
def finalize(payload: dict[str, object]) -> dict[str, object]:
|
|
937
980
|
nonlocal filtered, publication, usage
|
|
938
|
-
|
|
939
|
-
|
|
981
|
+
for reserved in (TOOLKIT_RESULT_KEY, TOOLKIT_ADVISORY_KEY):
|
|
982
|
+
if reserved in payload:
|
|
983
|
+
raise OcrResultMalformed(f"OCR result contains reserved field {reserved!r}")
|
|
940
984
|
warnings = payload.get("warnings", [])
|
|
941
985
|
if not isinstance(warnings, list):
|
|
942
986
|
raise OcrResultMalformed("OCR result warnings must be a list")
|
|
943
|
-
warning_texts = {ocr_warning_text(warning) for warning in warnings}
|
|
944
|
-
appended_warnings: list[str] = []
|
|
945
|
-
for warning in toolkit_warnings:
|
|
946
|
-
if warning in warning_texts:
|
|
947
|
-
continue
|
|
948
|
-
warning_texts.add(warning)
|
|
949
|
-
appended_warnings.append(warning)
|
|
950
|
-
payload = {
|
|
951
|
-
**payload,
|
|
952
|
-
"warnings": [
|
|
953
|
-
*warnings,
|
|
954
|
-
*appended_warnings,
|
|
955
|
-
],
|
|
956
|
-
}
|
|
957
987
|
metadata = _review_receipt(
|
|
958
988
|
payload,
|
|
959
989
|
composition,
|
|
@@ -969,7 +999,10 @@ def _finalize_ocr_result(
|
|
|
969
999
|
mcp = metadata.get("mcp")
|
|
970
1000
|
raw_usage = mcp.get("usage") if isinstance(mcp, dict) else None
|
|
971
1001
|
usage = dict(raw_usage) if isinstance(raw_usage, dict) else {}
|
|
972
|
-
|
|
1002
|
+
finalized = {**projected, TOOLKIT_RESULT_KEY: metadata}
|
|
1003
|
+
if toolkit_advisory is not None:
|
|
1004
|
+
finalized[TOOLKIT_ADVISORY_KEY] = toolkit_advisory_payload(toolkit_advisory)
|
|
1005
|
+
return finalized
|
|
973
1006
|
|
|
974
1007
|
try:
|
|
975
1008
|
transform_ocr_result(result_path, finalize)
|
|
@@ -1158,14 +1191,14 @@ def _parse_background_preview(*, returncode: int, stderr: bytes) -> BackgroundQu
|
|
|
1158
1191
|
or len(max_tools_matches) > 1
|
|
1159
1192
|
):
|
|
1160
1193
|
raise ReviewRunnerError("OCR background preview returned ambiguous diagnostics")
|
|
1161
|
-
|
|
1194
|
+
advisory: OcrToolkitAdvisory | None = None
|
|
1162
1195
|
if soft_matches:
|
|
1163
1196
|
actual, limit = (int(value) for value in soft_matches[0].groups())
|
|
1164
1197
|
if not 0 < limit < actual:
|
|
1165
1198
|
raise ReviewRunnerError("OCR background preview returned invalid thresholds")
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1199
|
+
advisory = background_recommended_advisory(
|
|
1200
|
+
actual=actual,
|
|
1201
|
+
recommended=limit,
|
|
1169
1202
|
)
|
|
1170
1203
|
operator_notices: tuple[str, ...] = ()
|
|
1171
1204
|
if max_tools_matches:
|
|
@@ -1179,7 +1212,7 @@ def _parse_background_preview(*, returncode: int, stderr: bytes) -> BackgroundQu
|
|
|
1179
1212
|
"effective tool-call limit.",
|
|
1180
1213
|
)
|
|
1181
1214
|
return BackgroundQualification(
|
|
1182
|
-
|
|
1215
|
+
advisory=advisory,
|
|
1183
1216
|
operator_notices=operator_notices,
|
|
1184
1217
|
)
|
|
1185
1218
|
if hard_character is not None:
|
|
@@ -1295,9 +1328,11 @@ def _run_background_qualified_review(
|
|
|
1295
1328
|
file=sys.stderr,
|
|
1296
1329
|
)
|
|
1297
1330
|
return 2, BackgroundQualification()
|
|
1298
|
-
if qualification.
|
|
1331
|
+
if qualification.advisory is not None:
|
|
1299
1332
|
print(
|
|
1300
|
-
|
|
1333
|
+
"OCR core advisory: "
|
|
1334
|
+
f"background {qualification.advisory.actual} characters; recommended "
|
|
1335
|
+
f"{qualification.advisory.recommended} characters; accepted by OCR core",
|
|
1301
1336
|
file=sys.stderr,
|
|
1302
1337
|
)
|
|
1303
1338
|
for notice in qualification.operator_notices:
|
|
@@ -1933,11 +1968,7 @@ def run_evidence_review(
|
|
|
1933
1968
|
enrichment,
|
|
1934
1969
|
evidence_action_counts,
|
|
1935
1970
|
forbidden=forbidden,
|
|
1936
|
-
|
|
1937
|
-
(background_qualification.warning,)
|
|
1938
|
-
if background_qualification.warning is not None
|
|
1939
|
-
else ()
|
|
1940
|
-
),
|
|
1971
|
+
toolkit_advisory=background_qualification.advisory,
|
|
1941
1972
|
)
|
|
1942
1973
|
except ReviewRunnerError:
|
|
1943
1974
|
try:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/common/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/common/git.py
RENAMED
|
File without changes
|
{open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/common/language.py
RENAMED
|
File without changes
|
{open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/common/markdown.py
RENAMED
|
File without changes
|
|
File without changes
|
{open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/config_writer.py
RENAMED
|
File without changes
|
{open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/configure.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/context/broker.py
RENAMED
|
File without changes
|
|
File without changes
|
{open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/context/mcp.py
RENAMED
|
File without changes
|
{open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/context/policy.py
RENAMED
|
File without changes
|
|
File without changes
|
{open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/context/store.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/evidence/mcp.py
RENAMED
|
File without changes
|
{open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/evidence/model.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/mcp_config.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/posting/gitlab.py
RENAMED
|
File without changes
|
|
File without changes
|
{open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/posting/markers.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/pre_execution.py
RENAMED
|
File without changes
|
{open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/provider_config.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/result_contract.py
RENAMED
|
File without changes
|
{open_code_review_toolkit-0.8.3 → open_code_review_toolkit-0.8.4}/src/ocr_toolkit/result_usage.py
RENAMED
|
File without changes
|