open-code-review-toolkit 0.7.0__tar.gz → 0.7.1__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.7.0 → open_code_review_toolkit-0.7.1}/PKG-INFO +3 -3
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/README.md +2 -2
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/pyproject.toml +5 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/_version.py +2 -2
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/evidence/__main__.py +3 -1
- open_code_review_toolkit-0.7.1/src/ocr_toolkit/evidence/actions.py +124 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/evidence/artifacts.py +4 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/evidence/mcp.py +20 -2
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/ocr_result.py +2 -2
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/posting/approval.py +65 -20
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/posting/formatting.py +136 -144
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/posting/workflow.py +9 -8
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/preflight.py +1 -1
- open_code_review_toolkit-0.7.1/src/ocr_toolkit/result_usage.py +216 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/review_runner.py +147 -32
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/.gitignore +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/LICENSE +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/__init__.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/cli.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/common/__init__.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/common/filesystem.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/common/git.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/common/language.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/common/markdown.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/common/redaction.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/config_writer.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/configure.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/context/__init__.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/context/adapters.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/context/broker.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/context/contracts.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/context/dlp.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/context/mcp.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/context/policy.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/context/recognizers.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/context/store.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/evidence/__init__.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/evidence/categorize.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/evidence/collect.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/evidence/collectors/__init__.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/evidence/collectors/graphs.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/evidence/collectors/orchestration.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/evidence/collectors/projections.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/evidence/collectors/registry.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/evidence/collectors/sources.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/evidence/coverage.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/evidence/ecosystems/__init__.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/evidence/ecosystems/ansible/__init__.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/evidence/ecosystems/ansible/requirements.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/evidence/ecosystems/ansible/topology.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/evidence/ecosystems/contracts.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/evidence/ecosystems/go.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/evidence/ecosystems/javascript.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/evidence/ecosystems/php.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/evidence/ecosystems/python.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/evidence/frameworks/__init__.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/evidence/frameworks/contracts.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/evidence/frameworks/detection.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/evidence/frameworks/providers/__init__.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/evidence/frameworks/providers/frontend.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/evidence/frameworks/providers/go.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/evidence/frameworks/providers/php.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/evidence/frameworks/providers/python.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/evidence/frameworks/registry.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/evidence/frameworks/schema.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/evidence/frameworks/templates.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/evidence/infrastructure.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/evidence/invocation.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/evidence/model.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/evidence/policy/__init__.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/evidence/policy/contracts.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/evidence/policy/decisions.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/evidence/policy/guidance.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/evidence/policy/registry.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/evidence/policy/schema.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/evidence/policy/scopes.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/evidence/project.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/evidence/repository.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/evidence/review_context.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/evidence/store/__init__.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/evidence/store/atomic.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/evidence/store/contracts.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/evidence/store/core.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/evidence/store/readback.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/evidence/store/values.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/mcp_config.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/posting/__init__.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/posting/__main__.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/posting/comments.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/posting/gitlab.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/posting/gitlab_approval.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/posting/gitlab_identity.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/posting/markers.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/posting/payloads.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/posting/reconciliation.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/posting/result.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/posting/settings.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/posting/snapshot.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/posting/suggestions.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/posting/transaction.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/pre_execution.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/providers/__init__.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/providers/gitlab.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/providers/gitlab_discussions.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/py.typed +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/result_contract.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: open-code-review-toolkit
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.1
|
|
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
|
|
@@ -258,13 +258,13 @@ On a successful rerun, the toolkit replaces untouched OCR-only notes instead of
|
|
|
258
258
|
|
|
259
259
|
Suppression uses both the GitLab diff position and a stable finding fingerprint, so ordinary line shifts do not normally bring the same bug back. A materially changed finding can still receive a new discussion. See [GitLab review operations](docs/operations.md) for the complete lifecycle, posting modes, permissions, failure behavior, and Mermaid state diagram.
|
|
260
260
|
|
|
261
|
-
After every current review note publishes, the GitLab adapter can add a conservative approval bound to receipt
|
|
261
|
+
After every current review note publishes, the GitLab adapter can add a conservative approval bound to receipt v5's exact reviewed source SHA and merge-request author. This write is enabled by default; set `OCR_AUTO_APPROVE=false` when the bot must remain comment-only. Complete optional MR metadata and complete zero-record enrichment remain eligible, while degraded metadata, required context degradation, admitted mutable context, legacy receipts, publication filtering, any direct external MCP, author movement, or bot self-authorship prevents an approval write. GitLab approval rules and protected-branch policy remain authoritative. The toolkit only adds an eligible approval; it never removes an existing approval when a later review is ineligible or disabled.
|
|
262
262
|
|
|
263
263
|
Accepted tradeoffs can be recorded in `.opencodereview/accepted-decisions.md`; the evidence collector supplies only applicable target-ref decisions and never lets a source change self-authorize its review. Root and nested target `AGENTS.md`/`CLAUDE.md` guidance is similarly exposed through the existing evidence MCP with deterministic scope and precedence, while any guidance touched by the merge request is excluded. See [Accepted project decisions](docs/configuration.md#accepted-project-decisions) and [Target project guidance](docs/configuration.md#target-project-guidance) for formats and trust boundaries.
|
|
264
264
|
|
|
265
265
|
## Project architecture
|
|
266
266
|
|
|
267
|
-
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. Version 0.7.0 establishes protected-policy enriched reviews: they can acquire stable GitLab discussions and authorize external issue/document records through a provider-neutral stdio or HTTPS proxy before OCR. The same built-in MCP exposes only opaque committed `context_list`/`context_get` handles; it has no provider network or arbitrary identifier path. Direct external MCP remains a separate privileged, comment-only operator boundary.
|
|
267
|
+
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. Version 0.7.0 establishes protected-policy enriched reviews: they can acquire stable GitLab discussions and authorize external issue/document records through a provider-neutral stdio or HTTPS proxy before OCR. Version 0.7.1 adds closed receipt-v5 publication semantics, reconciled count-only evidence use, privacy-safe normalized token reporting, and deterministic Recommended focus ranking. The same built-in MCP exposes only opaque committed `context_list`/`context_get` handles; it has no provider network or arbitrary identifier path. Direct external MCP remains a separate privileged, comment-only operator boundary.
|
|
268
268
|
|
|
269
269
|
- [Toolkit strategy](docs/engineering/toolkit_strategy.md) - durable product boundaries, architecture, invariants, and non-goals.
|
|
270
270
|
- [Bounded review context](docs/review-context.md) - protected policy, adapter protocol, GitLab discussions, opaque handles, DLP, receipt, and cleanup contracts.
|
|
@@ -31,13 +31,13 @@ On a successful rerun, the toolkit replaces untouched OCR-only notes instead of
|
|
|
31
31
|
|
|
32
32
|
Suppression uses both the GitLab diff position and a stable finding fingerprint, so ordinary line shifts do not normally bring the same bug back. A materially changed finding can still receive a new discussion. See [GitLab review operations](docs/operations.md) for the complete lifecycle, posting modes, permissions, failure behavior, and Mermaid state diagram.
|
|
33
33
|
|
|
34
|
-
After every current review note publishes, the GitLab adapter can add a conservative approval bound to receipt
|
|
34
|
+
After every current review note publishes, the GitLab adapter can add a conservative approval bound to receipt v5's exact reviewed source SHA and merge-request author. This write is enabled by default; set `OCR_AUTO_APPROVE=false` when the bot must remain comment-only. Complete optional MR metadata and complete zero-record enrichment remain eligible, while degraded metadata, required context degradation, admitted mutable context, legacy receipts, publication filtering, any direct external MCP, author movement, or bot self-authorship prevents an approval write. GitLab approval rules and protected-branch policy remain authoritative. The toolkit only adds an eligible approval; it never removes an existing approval when a later review is ineligible or disabled.
|
|
35
35
|
|
|
36
36
|
Accepted tradeoffs can be recorded in `.opencodereview/accepted-decisions.md`; the evidence collector supplies only applicable target-ref decisions and never lets a source change self-authorize its review. Root and nested target `AGENTS.md`/`CLAUDE.md` guidance is similarly exposed through the existing evidence MCP with deterministic scope and precedence, while any guidance touched by the merge request is excluded. See [Accepted project decisions](docs/configuration.md#accepted-project-decisions) and [Target project guidance](docs/configuration.md#target-project-guidance) for formats and trust boundaries.
|
|
37
37
|
|
|
38
38
|
## Project architecture
|
|
39
39
|
|
|
40
|
-
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. Version 0.7.0 establishes protected-policy enriched reviews: they can acquire stable GitLab discussions and authorize external issue/document records through a provider-neutral stdio or HTTPS proxy before OCR. The same built-in MCP exposes only opaque committed `context_list`/`context_get` handles; it has no provider network or arbitrary identifier path. Direct external MCP remains a separate privileged, comment-only operator boundary.
|
|
40
|
+
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. Version 0.7.0 establishes protected-policy enriched reviews: they can acquire stable GitLab discussions and authorize external issue/document records through a provider-neutral stdio or HTTPS proxy before OCR. Version 0.7.1 adds closed receipt-v5 publication semantics, reconciled count-only evidence use, privacy-safe normalized token reporting, and deterministic Recommended focus ranking. The same built-in MCP exposes only opaque committed `context_list`/`context_get` handles; it has no provider network or arbitrary identifier path. Direct external MCP remains a separate privileged, comment-only operator boundary.
|
|
41
41
|
|
|
42
42
|
- [Toolkit strategy](docs/engineering/toolkit_strategy.md) - durable product boundaries, architecture, invariants, and non-goals.
|
|
43
43
|
- [Bounded review context](docs/review-context.md) - protected policy, adapter protocol, GitLab discussions, opaque handles, DLP, receipt, and cleanup contracts.
|
|
@@ -133,6 +133,11 @@ directory = "bugfix"
|
|
|
133
133
|
name = "🐛 Bug Fixes"
|
|
134
134
|
showcontent = true
|
|
135
135
|
|
|
136
|
+
[[tool.towncrier.type]]
|
|
137
|
+
directory = "maintenance"
|
|
138
|
+
name = "🛠 Maintenance"
|
|
139
|
+
showcontent = true
|
|
140
|
+
|
|
136
141
|
[[tool.towncrier.type]]
|
|
137
142
|
directory = "refactor"
|
|
138
143
|
name = "🔧 Refactoring"
|
{open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/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.7.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 7,
|
|
21
|
+
__version__ = version = '0.7.1'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 7, 1)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -15,8 +15,10 @@ def main() -> int:
|
|
|
15
15
|
parser.add_argument("--context-run-id", default="")
|
|
16
16
|
parser.add_argument("--context-policy-digest", default="")
|
|
17
17
|
arguments = parser.parse_args()
|
|
18
|
+
artifacts = repository_artifacts()
|
|
18
19
|
return serve(
|
|
19
|
-
|
|
20
|
+
artifacts.store,
|
|
21
|
+
action_receipt_path=artifacts.action_receipt,
|
|
20
22
|
context_path=arguments.context_store,
|
|
21
23
|
context_run_id=arguments.context_run_id,
|
|
22
24
|
context_policy_digest=arguments.context_policy_digest,
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"""Persist and validate count-only built-in evidence action attribution."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
import stat
|
|
8
|
+
from collections.abc import Iterator
|
|
9
|
+
from contextlib import contextmanager
|
|
10
|
+
from fcntl import LOCK_EX, flock
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
from ocr_toolkit.evidence.store.atomic import atomic_write
|
|
15
|
+
|
|
16
|
+
ACTION_RECEIPT_SCHEMA = "ocr.evidence-action-receipt/v1"
|
|
17
|
+
EVIDENCE_ACTIONS = ("summary", "list", "get")
|
|
18
|
+
MAX_ACTION_CALLS = 1_000_000_000
|
|
19
|
+
MAX_ACTION_RECEIPT_BYTES = 4_096
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _locked_receipt_path(path: Path) -> Path:
|
|
23
|
+
"""Return one sibling lock path that never enters the public receipt."""
|
|
24
|
+
|
|
25
|
+
return path.with_name(f".{path.name}.lock")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@contextmanager
|
|
29
|
+
def _serialized_receipt_update(path: Path) -> Iterator[None]:
|
|
30
|
+
"""Serialize sibling receipt updates through one private regular lock file."""
|
|
31
|
+
|
|
32
|
+
lock_path = _locked_receipt_path(path)
|
|
33
|
+
flags = os.O_CREAT | os.O_RDWR
|
|
34
|
+
if hasattr(os, "O_NOFOLLOW"):
|
|
35
|
+
flags |= os.O_NOFOLLOW
|
|
36
|
+
descriptor = os.open(lock_path, flags, 0o600)
|
|
37
|
+
try:
|
|
38
|
+
metadata = os.fstat(descriptor)
|
|
39
|
+
if not stat.S_ISREG(metadata.st_mode) or metadata.st_nlink != 1:
|
|
40
|
+
raise OSError("evidence action receipt lock is unsafe")
|
|
41
|
+
os.fchmod(descriptor, 0o600)
|
|
42
|
+
flock(descriptor, LOCK_EX)
|
|
43
|
+
yield
|
|
44
|
+
finally:
|
|
45
|
+
os.close(descriptor)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _validated_counts(value: Any) -> dict[str, int] | None:
|
|
49
|
+
if not isinstance(value, dict) or set(value) != {"schema_version", "actions"}:
|
|
50
|
+
return None
|
|
51
|
+
actions = value.get("actions")
|
|
52
|
+
if value.get("schema_version") != ACTION_RECEIPT_SCHEMA or not isinstance(actions, dict):
|
|
53
|
+
return None
|
|
54
|
+
if set(actions) != set(EVIDENCE_ACTIONS):
|
|
55
|
+
return None
|
|
56
|
+
if any(
|
|
57
|
+
not isinstance(count, int) or isinstance(count, bool) or not 0 <= count <= MAX_ACTION_CALLS
|
|
58
|
+
for count in actions.values()
|
|
59
|
+
):
|
|
60
|
+
return None
|
|
61
|
+
return {action: actions[action] for action in EVIDENCE_ACTIONS}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def read_action_receipt(path: Path) -> dict[str, int] | None:
|
|
65
|
+
"""Read one bounded receipt or return unavailable for any unsafe shape."""
|
|
66
|
+
|
|
67
|
+
flags = os.O_RDONLY
|
|
68
|
+
if hasattr(os, "O_NOFOLLOW"):
|
|
69
|
+
flags |= os.O_NOFOLLOW
|
|
70
|
+
if hasattr(os, "O_NONBLOCK"):
|
|
71
|
+
flags |= os.O_NONBLOCK
|
|
72
|
+
descriptor = -1
|
|
73
|
+
try:
|
|
74
|
+
descriptor = os.open(path, flags)
|
|
75
|
+
metadata = os.fstat(descriptor)
|
|
76
|
+
if (
|
|
77
|
+
not stat.S_ISREG(metadata.st_mode)
|
|
78
|
+
or metadata.st_nlink != 1
|
|
79
|
+
or stat.S_IMODE(metadata.st_mode) & 0o077
|
|
80
|
+
):
|
|
81
|
+
return None
|
|
82
|
+
raw = os.read(descriptor, MAX_ACTION_RECEIPT_BYTES + 1)
|
|
83
|
+
except OSError:
|
|
84
|
+
return None
|
|
85
|
+
finally:
|
|
86
|
+
if descriptor >= 0:
|
|
87
|
+
os.close(descriptor)
|
|
88
|
+
if len(raw) > MAX_ACTION_RECEIPT_BYTES:
|
|
89
|
+
return None
|
|
90
|
+
try:
|
|
91
|
+
value = json.loads(raw)
|
|
92
|
+
except (UnicodeDecodeError, json.JSONDecodeError, RecursionError):
|
|
93
|
+
return None
|
|
94
|
+
return _validated_counts(value)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def record_action(path: Path, action: object) -> None:
|
|
98
|
+
"""Atomically increment one completed action without retaining arguments."""
|
|
99
|
+
|
|
100
|
+
if action not in EVIDENCE_ACTIONS:
|
|
101
|
+
raise ValueError("evidence action is outside the closed receipt enum")
|
|
102
|
+
with _serialized_receipt_update(path):
|
|
103
|
+
counts = read_action_receipt(path)
|
|
104
|
+
try:
|
|
105
|
+
path.lstat()
|
|
106
|
+
except FileNotFoundError:
|
|
107
|
+
exists = False
|
|
108
|
+
else:
|
|
109
|
+
exists = True
|
|
110
|
+
if exists and counts is None:
|
|
111
|
+
raise ValueError("evidence action receipt is malformed")
|
|
112
|
+
if counts is None:
|
|
113
|
+
counts = dict.fromkeys(EVIDENCE_ACTIONS, 0)
|
|
114
|
+
if counts[action] >= MAX_ACTION_CALLS:
|
|
115
|
+
raise ValueError("evidence action receipt exceeds the count bound")
|
|
116
|
+
counts[action] += 1
|
|
117
|
+
payload = {
|
|
118
|
+
"schema_version": ACTION_RECEIPT_SCHEMA,
|
|
119
|
+
"actions": {name: counts[name] for name in EVIDENCE_ACTIONS},
|
|
120
|
+
}
|
|
121
|
+
atomic_write(
|
|
122
|
+
path,
|
|
123
|
+
lambda: json.dumps(payload, sort_keys=True, separators=(",", ":")) + "\n",
|
|
124
|
+
)
|
|
@@ -18,6 +18,8 @@ class EvidenceArtifacts:
|
|
|
18
18
|
policy_rules: Path
|
|
19
19
|
context_store: Path
|
|
20
20
|
pre_execution_status: Path
|
|
21
|
+
action_receipt: Path
|
|
22
|
+
action_receipt_lock: Path
|
|
21
23
|
|
|
22
24
|
|
|
23
25
|
def repository_artifacts(root: Path | None = None) -> EvidenceArtifacts:
|
|
@@ -32,6 +34,8 @@ def repository_artifacts(root: Path | None = None) -> EvidenceArtifacts:
|
|
|
32
34
|
policy_rules=directory / "policy-rules.json",
|
|
33
35
|
context_store=directory / "context.json",
|
|
34
36
|
pre_execution_status=directory / "pre-execution-status.json",
|
|
37
|
+
action_receipt=directory / "evidence-actions.json",
|
|
38
|
+
action_receipt_lock=directory / ".evidence-actions.json.lock",
|
|
35
39
|
)
|
|
36
40
|
|
|
37
41
|
|
{open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/evidence/mcp.py
RENAMED
|
@@ -21,6 +21,7 @@ from ocr_toolkit.context.mcp import (
|
|
|
21
21
|
tool_definitions,
|
|
22
22
|
)
|
|
23
23
|
from ocr_toolkit.context.store import ContextStore
|
|
24
|
+
from ocr_toolkit.evidence.actions import record_action
|
|
24
25
|
from ocr_toolkit.evidence.model import CoverageRecord, EvidenceDelta, EvidenceRecord
|
|
25
26
|
from ocr_toolkit.evidence.policy.schema import is_legacy_policy_value
|
|
26
27
|
from ocr_toolkit.evidence.store import EvidenceStore, EvidenceStoreError
|
|
@@ -399,7 +400,11 @@ def _error(request_id: object, code: int, message: str) -> dict[str, object]:
|
|
|
399
400
|
|
|
400
401
|
|
|
401
402
|
def handle_request(
|
|
402
|
-
store: EvidenceStore,
|
|
403
|
+
store: EvidenceStore,
|
|
404
|
+
raw: object,
|
|
405
|
+
context_store: ContextStore | None = None,
|
|
406
|
+
*,
|
|
407
|
+
action_receipt_path: Path | None = None,
|
|
403
408
|
) -> dict[str, object] | None:
|
|
404
409
|
"""Handle one validated JSON-RPC request or notification."""
|
|
405
410
|
|
|
@@ -446,6 +451,13 @@ def handle_request(
|
|
|
446
451
|
request_id,
|
|
447
452
|
{"content": [{"type": "text", "text": str(exc)}], "isError": True},
|
|
448
453
|
)
|
|
454
|
+
if action_receipt_path is not None:
|
|
455
|
+
arguments = params.get("arguments", {})
|
|
456
|
+
action = arguments.get("action") if isinstance(arguments, dict) else None
|
|
457
|
+
try:
|
|
458
|
+
record_action(action_receipt_path, action)
|
|
459
|
+
except (OSError, ValueError):
|
|
460
|
+
pass
|
|
449
461
|
return _success(request_id, result)
|
|
450
462
|
if context_store is not None and isinstance(name, str) and name in {LIST_TOOL, GET_TOOL}:
|
|
451
463
|
try:
|
|
@@ -482,6 +494,7 @@ def serve(
|
|
|
482
494
|
stdin: TextIO = sys.stdin,
|
|
483
495
|
stdout: TextIO = sys.stdout,
|
|
484
496
|
*,
|
|
497
|
+
action_receipt_path: Path | None = None,
|
|
485
498
|
context_path: Path | None = None,
|
|
486
499
|
context_run_id: str = "",
|
|
487
500
|
context_policy_digest: str = "",
|
|
@@ -516,7 +529,12 @@ def serve(
|
|
|
516
529
|
response = _error(None, -32700, "Parse error")
|
|
517
530
|
else:
|
|
518
531
|
has_request_id = isinstance(request, dict) and "id" in request
|
|
519
|
-
response = handle_request(
|
|
532
|
+
response = handle_request(
|
|
533
|
+
store,
|
|
534
|
+
request,
|
|
535
|
+
context_store,
|
|
536
|
+
action_receipt_path=action_receipt_path,
|
|
537
|
+
)
|
|
520
538
|
if response is None or not has_request_id:
|
|
521
539
|
continue
|
|
522
540
|
serialized = json.dumps(response, separators=(",", ":"), ensure_ascii=False)
|
{open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.7.1}/src/ocr_toolkit/ocr_result.py
RENAMED
|
@@ -18,8 +18,8 @@ from ocr_toolkit.common.redaction import sanitize_ocr_value
|
|
|
18
18
|
DEFAULT_MAX_RESULT_BYTES = 2_000_000
|
|
19
19
|
MAX_RESULT_BYTES_HARD_LIMIT = 20_000_000
|
|
20
20
|
TOOLKIT_RESULT_KEY = "_ocr_toolkit"
|
|
21
|
-
TOOLKIT_RESULT_SCHEMA_VERSION =
|
|
22
|
-
SUPPORTED_TOOLKIT_RESULT_SCHEMA_VERSIONS = frozenset({
|
|
21
|
+
TOOLKIT_RESULT_SCHEMA_VERSION = 5
|
|
22
|
+
SUPPORTED_TOOLKIT_RESULT_SCHEMA_VERSIONS = frozenset({TOOLKIT_RESULT_SCHEMA_VERSION})
|
|
23
23
|
# The receipt can name the 16 configured external servers plus the mandatory built-in.
|
|
24
24
|
MAX_TOOLKIT_MCP_USAGE_SERVERS = 17
|
|
25
25
|
MAX_TOOLKIT_MCP_TOOLS_PER_SERVER = 128
|
|
@@ -159,34 +159,42 @@ def _sha256(value: Any) -> bool:
|
|
|
159
159
|
|
|
160
160
|
|
|
161
161
|
def publication_dlp_state(value: Any) -> str | None:
|
|
162
|
-
"""Validate the exact
|
|
162
|
+
"""Validate the exact v5 publication-policy receipt."""
|
|
163
163
|
|
|
164
|
-
if value == {"
|
|
164
|
+
if value == {"state": "passed"}:
|
|
165
165
|
return "passed"
|
|
166
|
+
if not isinstance(value, dict):
|
|
167
|
+
return None
|
|
168
|
+
if value.get("state") == "private-sanitized":
|
|
169
|
+
if set(value) != {"state", "reason_counts", "sanitized_fields"}:
|
|
170
|
+
return None
|
|
171
|
+
reason_counts = value.get("reason_counts")
|
|
172
|
+
sanitized_fields = value.get("sanitized_fields")
|
|
173
|
+
if (
|
|
174
|
+
not _valid_dlp_reason_counts(reason_counts)
|
|
175
|
+
or not any(reason_counts.values())
|
|
176
|
+
or not isinstance(sanitized_fields, int)
|
|
177
|
+
or isinstance(sanitized_fields, bool)
|
|
178
|
+
or not 0 < sanitized_fields <= MAX_TOOLKIT_MCP_USAGE_COUNT
|
|
179
|
+
):
|
|
180
|
+
return None
|
|
181
|
+
return "private-sanitized"
|
|
166
182
|
if not isinstance(value, dict) or set(value) != {
|
|
167
|
-
"
|
|
183
|
+
"state",
|
|
168
184
|
"reason_counts",
|
|
169
185
|
"retained",
|
|
170
186
|
"omitted",
|
|
171
187
|
"original",
|
|
172
188
|
}:
|
|
173
189
|
return None
|
|
174
|
-
if value.get("
|
|
190
|
+
if value.get("state") != "publication-filtered":
|
|
175
191
|
return None
|
|
176
192
|
reason_counts = value.get("reason_counts")
|
|
177
193
|
retained = value.get("retained")
|
|
178
194
|
omitted = value.get("omitted")
|
|
179
195
|
original = value.get("original")
|
|
180
196
|
if (
|
|
181
|
-
not
|
|
182
|
-
or set(reason_counts)
|
|
183
|
-
!= {"forbidden", "invalid_text", "laundering", "limit", "pii", "secret"}
|
|
184
|
-
or any(
|
|
185
|
-
not isinstance(count, int)
|
|
186
|
-
or isinstance(count, bool)
|
|
187
|
-
or not 0 <= count <= MAX_TOOLKIT_MCP_USAGE_COUNT
|
|
188
|
-
for count in reason_counts.values()
|
|
189
|
-
)
|
|
197
|
+
not _valid_dlp_reason_counts(reason_counts)
|
|
190
198
|
or not any(reason_counts.values())
|
|
191
199
|
or not isinstance(retained, dict)
|
|
192
200
|
or set(retained) != {"comments", "warnings"}
|
|
@@ -210,7 +218,20 @@ def publication_dlp_state(value: Any) -> str | None:
|
|
|
210
218
|
)
|
|
211
219
|
):
|
|
212
220
|
return None
|
|
213
|
-
return "filtered"
|
|
221
|
+
return "publication-filtered"
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def _valid_dlp_reason_counts(value: Any) -> bool:
|
|
225
|
+
return bool(
|
|
226
|
+
isinstance(value, dict)
|
|
227
|
+
and set(value) == {"forbidden", "invalid_text", "laundering", "limit", "pii", "secret"}
|
|
228
|
+
and all(
|
|
229
|
+
isinstance(count, int)
|
|
230
|
+
and not isinstance(count, bool)
|
|
231
|
+
and 0 <= count <= MAX_TOOLKIT_MCP_USAGE_COUNT
|
|
232
|
+
for count in value.values()
|
|
233
|
+
)
|
|
234
|
+
)
|
|
214
235
|
|
|
215
236
|
|
|
216
237
|
def automatic_approval_metadata_reason(toolkit_metadata: Any) -> str:
|
|
@@ -219,10 +240,7 @@ def automatic_approval_metadata_reason(toolkit_metadata: Any) -> str:
|
|
|
219
240
|
invalid = "the review-time approval receipt is missing or invalid"
|
|
220
241
|
if not isinstance(toolkit_metadata, dict):
|
|
221
242
|
return invalid
|
|
222
|
-
|
|
223
|
-
if schema_version in {1, 2, 3}:
|
|
224
|
-
return "the review-time approval receipt predates current eligibility controls"
|
|
225
|
-
if schema_version != 4 or set(toolkit_metadata) != {
|
|
243
|
+
if toolkit_metadata.get("schema_version") != 5 or set(toolkit_metadata) != {
|
|
226
244
|
"schema_version",
|
|
227
245
|
"review",
|
|
228
246
|
"context",
|
|
@@ -375,11 +393,17 @@ def automatic_approval_metadata_reason(toolkit_metadata: Any) -> str:
|
|
|
375
393
|
return invalid
|
|
376
394
|
|
|
377
395
|
evidence = toolkit_metadata.get("evidence")
|
|
378
|
-
if not isinstance(evidence, dict) or set(evidence) != {
|
|
396
|
+
if not isinstance(evidence, dict) or set(evidence) != {
|
|
397
|
+
"mandatory",
|
|
398
|
+
"used",
|
|
399
|
+
"calls",
|
|
400
|
+
"actions",
|
|
401
|
+
}:
|
|
379
402
|
return invalid
|
|
380
403
|
mandatory = evidence.get("mandatory")
|
|
381
404
|
used = evidence.get("used")
|
|
382
405
|
evidence_calls = evidence.get("calls")
|
|
406
|
+
evidence_actions = evidence.get("actions")
|
|
383
407
|
evidence_called = isinstance(evidence_calls, int) and evidence_calls > 0
|
|
384
408
|
if (
|
|
385
409
|
not isinstance(mandatory, bool)
|
|
@@ -390,6 +414,7 @@ def automatic_approval_metadata_reason(toolkit_metadata: Any) -> str:
|
|
|
390
414
|
or used is not evidence_called
|
|
391
415
|
or (mandatory and not used)
|
|
392
416
|
or usage.get(builtin_server, 0) != evidence_calls + sum(context_usage.values())
|
|
417
|
+
or not _valid_evidence_actions(evidence_actions, evidence_calls)
|
|
393
418
|
):
|
|
394
419
|
return invalid
|
|
395
420
|
publication = toolkit_metadata.get("publication")
|
|
@@ -397,7 +422,7 @@ def automatic_approval_metadata_reason(toolkit_metadata: Any) -> str:
|
|
|
397
422
|
publication_state = publication_dlp_state(publication)
|
|
398
423
|
if publication_state is None or cleanup != {"result": "passed"}:
|
|
399
424
|
return invalid
|
|
400
|
-
if publication_state == "filtered":
|
|
425
|
+
if publication_state == "publication-filtered":
|
|
401
426
|
return "publication DLP filtered the complete review result"
|
|
402
427
|
if context.get("state") == "degraded" or required_degraded:
|
|
403
428
|
return "the selected review context was degraded"
|
|
@@ -406,3 +431,23 @@ def automatic_approval_metadata_reason(toolkit_metadata: Any) -> str:
|
|
|
406
431
|
if external:
|
|
407
432
|
return "external MCP was configured for a comment-only review"
|
|
408
433
|
return ""
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
def _valid_evidence_actions(value: Any, evidence_calls: Any) -> bool:
|
|
437
|
+
"""Validate verified counts or an explicit unavailable attribution state."""
|
|
438
|
+
|
|
439
|
+
if value == {"state": "unavailable"}:
|
|
440
|
+
return True
|
|
441
|
+
if not isinstance(value, dict) or set(value) != {"state", "summary", "list", "get"}:
|
|
442
|
+
return False
|
|
443
|
+
counts = [value.get(action) for action in ("summary", "list", "get")]
|
|
444
|
+
return bool(
|
|
445
|
+
value.get("state") == "verified"
|
|
446
|
+
and all(
|
|
447
|
+
isinstance(count, int)
|
|
448
|
+
and not isinstance(count, bool)
|
|
449
|
+
and 0 <= count <= MAX_TOOLKIT_MCP_USAGE_COUNT
|
|
450
|
+
for count in counts
|
|
451
|
+
)
|
|
452
|
+
and sum(counts) == evidence_calls
|
|
453
|
+
)
|