open-code-review-toolkit 0.12.1__tar.gz → 0.13.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/PKG-INFO +19 -10
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/README.md +17 -9
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/pyproject.toml +2 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/_version.py +2 -2
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/cli.py +4 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/common/redaction.py +3 -3
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/artifacts.py +91 -4
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/collect.py +131 -3
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/collectors/orchestration.py +1 -1
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/mcp.py +17 -8
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/model.py +4 -3
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/project.py +24 -1
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/repository.py +65 -9
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/store/contracts.py +3 -2
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/store/core.py +81 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/store/readback.py +11 -1
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/ocr_result.py +5 -2
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/posting/formatting.py +12 -1
- open_code_review_toolkit-0.13.0/src/ocr_toolkit/posting/freshness.py +166 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/posting/gitlab.py +1 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/posting/gitlab_approval.py +69 -2
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/posting/snapshot.py +3 -6
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/posting/workflow.py +673 -238
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/pre_execution.py +60 -3
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/preflight.py +19 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/providers/gitlab.py +172 -28
- open_code_review_toolkit-0.13.0/src/ocr_toolkit/review_identity.py +58 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/review_receipt.py +215 -57
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/review_runner.py +312 -110
- open_code_review_toolkit-0.12.1/src/ocr_toolkit/review_identity.py +0 -26
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/.gitignore +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/LICENSE +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/__init__.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/common/__init__.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/common/filesystem.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/common/git.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/common/language.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/common/markdown.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/config_writer.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/configure.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/context/__init__.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/context/broker.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/context/ci_outcomes.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/context/contracts.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/context/dlp.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/context/mcp.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/context/policy.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/context/recognizers.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/context/store.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/__init__.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/__main__.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/actions.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/categorize.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/collectors/__init__.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/collectors/graphs.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/collectors/projections.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/collectors/registry.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/collectors/sources.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/coverage.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/ecosystems/__init__.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/ecosystems/ansible/__init__.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/ecosystems/ansible/requirements.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/ecosystems/ansible/topology.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/ecosystems/contracts.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/ecosystems/go.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/ecosystems/javascript.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/ecosystems/php.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/ecosystems/python.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/frameworks/__init__.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/frameworks/contracts.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/frameworks/detection.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/frameworks/providers/__init__.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/frameworks/providers/frontend.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/frameworks/providers/go.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/frameworks/providers/php.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/frameworks/providers/python.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/frameworks/registry.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/frameworks/schema.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/frameworks/templates.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/gitlinks.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/infrastructure.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/invocation.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/opaque_code.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/policy/__init__.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/policy/contracts.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/policy/decisions.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/policy/guidance.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/policy/registry.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/policy/schema.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/policy/scopes.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/review_context.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/store/__init__.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/store/atomic.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/store/values.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/federation/__init__.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/federation/admission.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/federation/contracts.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/federation/gateway.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/federation/registry.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/federation/relay.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/federation/runtime.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/federation/transport.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/local_chatgpt.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/mcp_config.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/posting/__init__.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/posting/__main__.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/posting/approval.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/posting/comments.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/posting/markers.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/posting/payloads.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/posting/reconciliation.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/posting/result.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/posting/settings.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/posting/suggestions.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/posting/transaction.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/provider_config.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/provider_failure.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/providers/__init__.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/providers/gitlab_ci.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/providers/gitlab_context.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/providers/gitlab_discussions.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/providers/gitlab_identity.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/providers/gitlab_remediation.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/providers/local.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/providers/local_config.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/py.typed +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/reporting/__init__.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/reporting/dlp.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/reporting/federation.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/reporting/metadata.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/reporting/model.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/reporting/outcome.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/reporting/result.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/reporting/sections.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/reporting/text.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/reporting/usage.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/result_contract.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/result_usage.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/review_debug.py +0 -0
- {open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/review_progress.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: open-code-review-toolkit
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.13.0
|
|
4
4
|
Summary: Unofficial GitLab CI integration layer for Open Code Review
|
|
5
5
|
Project-URL: Homepage, https://github.com/xeonvs/open-code-review-toolkit
|
|
6
6
|
Project-URL: Repository, https://github.com/xeonvs/open-code-review-toolkit
|
|
@@ -228,6 +228,7 @@ Requires-Dist: certifi<2027,>=2026.10.19
|
|
|
228
228
|
Requires-Dist: httpx2<3,>=2.13.0
|
|
229
229
|
Requires-Dist: jsonschema<5,>=4.26.0
|
|
230
230
|
Requires-Dist: mcp<3,>=2.2.0
|
|
231
|
+
Requires-Dist: opentelemetry-api<2,>=1.45.1
|
|
231
232
|
Requires-Dist: pyjwt<3,>=2.15.0
|
|
232
233
|
Description-Content-Type: text/markdown
|
|
233
234
|
|
|
@@ -297,7 +298,9 @@ commit without forge credentials:
|
|
|
297
298
|
|
|
298
299
|
```console
|
|
299
300
|
ocr-ci preflight --local
|
|
300
|
-
|
|
301
|
+
review_dir=$(mktemp -d)
|
|
302
|
+
review_dir=$(cd "$review_dir" && pwd -P)
|
|
303
|
+
ocr-ci review --local --result "$review_dir/review.json" --stderr "$review_dir/review.stderr" --report "$review_dir/review.md" -- --commit HEAD
|
|
301
304
|
```
|
|
302
305
|
|
|
303
306
|
Local execution uses the same OCR, mandatory evidence MCP, validation and DLP
|
|
@@ -321,13 +324,17 @@ On a successful rerun, the toolkit replaces untouched OCR-only notes instead of
|
|
|
321
324
|
|
|
322
325
|
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.
|
|
323
326
|
|
|
324
|
-
After every current review note publishes, the GitLab adapter can add a conservative approval bound to receipt
|
|
327
|
+
After every current review note publishes, the GitLab adapter can add a conservative approval bound to receipt v11's exact reviewed diff, actual target, policy authority, merge-request author, DLP mode, and reconciled federation use. This write is enabled by default; set `OCR_AUTO_APPROVE=false` when the bot must remain comment-only. DLP-clean metadata, generic discussions, protected same-revision CI outcomes, unused governed services, and successful `review_read` tool use do not independently block approval. An unprotected target, disabled DLP, degraded metadata, a DLP rejection, required context degradation, admitted remediation history, older receipts, publication filtering, used advisory tools, uncertain federation accounting, author movement, or bot self-authorship prevents an approval write. A CI status is review context, never approval authority. GitLab approval rules, Code Owners, and protected-branch policy remain authoritative merge policy. They are not prerequisites for OCR execution. The toolkit only adds an eligible approval; it never removes an existing approval when a later review is ineligible or disabled.
|
|
325
328
|
|
|
326
|
-
|
|
329
|
+
An intentionally absent toolkit receipt retains compatible direct posting outside
|
|
330
|
+
GitLab CI without automatic approval. A present v9/v10 receipt can be read for historical
|
|
331
|
+
diagnostics, but cannot publish a new current MR review.
|
|
332
|
+
|
|
333
|
+
Accepted tradeoffs can be recorded in `.opencodereview/accepted-decisions.md`; the evidence collector supplies only applicable decisions from the selected policy commit and never lets a source change self-authorize its review. Root and nested policy `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.
|
|
327
334
|
|
|
328
335
|
## Project architecture
|
|
329
336
|
|
|
330
|
-
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, literal search, scoped absence checks, and base/head changes through the mandatory built-in read-only MCP server.
|
|
337
|
+
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, literal search, scoped absence checks, and base/head changes through the mandatory built-in read-only MCP server. GitLab reviews can also expose a bounded, separately labelled actual-target snapshot for relevant code context; it does not become policy or another reviewed delta. The internal server is always present and cannot be removed or replaced by operator configuration. Protected-policy enriched reviews can also acquire stable GitLab discussions, verified remediation history, and protected same-revision CI outcomes before OCR. Optional external tools use the governed registry-v2 federation gateway: the toolkit freezes explicit allowlists, validates schemas, origins, DLP and budgets, and reconciles content-free usage receipts while OCR remains the only model loop. Local review permits explicit HTTPS or stdio upstreams; GitLab permits HTTPS only.
|
|
331
338
|
|
|
332
339
|
- [Toolkit strategy](docs/engineering/toolkit_strategy.md) - durable product boundaries, architecture, invariants, and non-goals.
|
|
333
340
|
- [Bounded review context](docs/review-context.md) - protected policy, GitLab discussions and same-revision CI outcomes, legacy reference migration, opaque handles, DLP, receipt, and cleanup contracts.
|
|
@@ -338,21 +345,23 @@ The shipped Repository Evidence Engine reads immutable base/head Git objects, st
|
|
|
338
345
|
|
|
339
346
|
1. Configure protected/masked `GITLAB_API_TOKEN` and LLM variables in GitLab.
|
|
340
347
|
2. Pin and checksum the OCR binary.
|
|
341
|
-
3.
|
|
342
|
-
4. Merge repository review configuration, including the Rules path, into the
|
|
348
|
+
3. With toolkit 0.13.0, leave `OCR_GITLAB_TARGET_PROTECTION_MODE` unset to use its `auto` default for a single MR targeting an unprotected branch under the same project's protected default-branch policy. Select explicit `required` to reject unprotected targets, or `unprotected` for the earlier limited profile.
|
|
349
|
+
4. Merge repository review configuration, including the Rules path, into the protected policy branch before relying on it.
|
|
343
350
|
5. Enable the CI review job in a later merge request and install this package.
|
|
344
351
|
6. Run the four public helper stages around `ocr review`:
|
|
345
352
|
|
|
346
353
|
```console
|
|
347
354
|
ocr-ci preflight
|
|
348
355
|
ocr-ci configure
|
|
349
|
-
|
|
350
|
-
|
|
356
|
+
review_result="/tmp/ocr-result-${CI_JOB_ID}.json"
|
|
357
|
+
review_stderr="/tmp/ocr-stderr-${CI_JOB_ID}.log"
|
|
358
|
+
ocr-ci review --result "$review_result" --stderr "$review_stderr" -- ... --format json || export OCR_EXIT_CODE=$?
|
|
359
|
+
ocr-ci post --result "$review_result" --stderr "$review_stderr"
|
|
351
360
|
```
|
|
352
361
|
|
|
353
362
|
See the [GitLab mode matrix](examples/gitlab/README.md), the complete [`ocr-review.gitlab-ci.yml`](examples/gitlab/ocr-review.gitlab-ci.yml) pipeline, the [GitLab setup guide](docs/gitlab.md), and [GitLab review operations](docs/operations.md).
|
|
354
363
|
|
|
355
|
-
The two-merge-request setup is recommended because repository Rules
|
|
364
|
+
The two-merge-request setup is recommended because repository Rules must already exist on the captured policy commit. For a protected target that is its own authority; for an unprotected target in `auto`, it is the protected default branch. The integration MR can add Rules and CI together when it need not be reviewed: its first run may stop with setup-pending, and later MRs work after the policy change merges. Retrying the same MR cannot promote its source Rules. An unprotected target in explicit `unprotected` mode with missing target Rules fails closed. A green advisory job or pipeline does not prove that OCR or its model executed. See the [GitLab setup guide](docs/gitlab.md#first-integration-sequence) for the exact boundary.
|
|
356
365
|
|
|
357
366
|
## Configuration and safety
|
|
358
367
|
|
|
@@ -64,7 +64,9 @@ commit without forge credentials:
|
|
|
64
64
|
|
|
65
65
|
```console
|
|
66
66
|
ocr-ci preflight --local
|
|
67
|
-
|
|
67
|
+
review_dir=$(mktemp -d)
|
|
68
|
+
review_dir=$(cd "$review_dir" && pwd -P)
|
|
69
|
+
ocr-ci review --local --result "$review_dir/review.json" --stderr "$review_dir/review.stderr" --report "$review_dir/review.md" -- --commit HEAD
|
|
68
70
|
```
|
|
69
71
|
|
|
70
72
|
Local execution uses the same OCR, mandatory evidence MCP, validation and DLP
|
|
@@ -88,13 +90,17 @@ On a successful rerun, the toolkit replaces untouched OCR-only notes instead of
|
|
|
88
90
|
|
|
89
91
|
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.
|
|
90
92
|
|
|
91
|
-
After every current review note publishes, the GitLab adapter can add a conservative approval bound to receipt
|
|
93
|
+
After every current review note publishes, the GitLab adapter can add a conservative approval bound to receipt v11's exact reviewed diff, actual target, policy authority, merge-request author, DLP mode, and reconciled federation use. This write is enabled by default; set `OCR_AUTO_APPROVE=false` when the bot must remain comment-only. DLP-clean metadata, generic discussions, protected same-revision CI outcomes, unused governed services, and successful `review_read` tool use do not independently block approval. An unprotected target, disabled DLP, degraded metadata, a DLP rejection, required context degradation, admitted remediation history, older receipts, publication filtering, used advisory tools, uncertain federation accounting, author movement, or bot self-authorship prevents an approval write. A CI status is review context, never approval authority. GitLab approval rules, Code Owners, and protected-branch policy remain authoritative merge policy. They are not prerequisites for OCR execution. The toolkit only adds an eligible approval; it never removes an existing approval when a later review is ineligible or disabled.
|
|
92
94
|
|
|
93
|
-
|
|
95
|
+
An intentionally absent toolkit receipt retains compatible direct posting outside
|
|
96
|
+
GitLab CI without automatic approval. A present v9/v10 receipt can be read for historical
|
|
97
|
+
diagnostics, but cannot publish a new current MR review.
|
|
98
|
+
|
|
99
|
+
Accepted tradeoffs can be recorded in `.opencodereview/accepted-decisions.md`; the evidence collector supplies only applicable decisions from the selected policy commit and never lets a source change self-authorize its review. Root and nested policy `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.
|
|
94
100
|
|
|
95
101
|
## Project architecture
|
|
96
102
|
|
|
97
|
-
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, literal search, scoped absence checks, and base/head changes through the mandatory built-in read-only MCP server.
|
|
103
|
+
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, literal search, scoped absence checks, and base/head changes through the mandatory built-in read-only MCP server. GitLab reviews can also expose a bounded, separately labelled actual-target snapshot for relevant code context; it does not become policy or another reviewed delta. The internal server is always present and cannot be removed or replaced by operator configuration. Protected-policy enriched reviews can also acquire stable GitLab discussions, verified remediation history, and protected same-revision CI outcomes before OCR. Optional external tools use the governed registry-v2 federation gateway: the toolkit freezes explicit allowlists, validates schemas, origins, DLP and budgets, and reconciles content-free usage receipts while OCR remains the only model loop. Local review permits explicit HTTPS or stdio upstreams; GitLab permits HTTPS only.
|
|
98
104
|
|
|
99
105
|
- [Toolkit strategy](docs/engineering/toolkit_strategy.md) - durable product boundaries, architecture, invariants, and non-goals.
|
|
100
106
|
- [Bounded review context](docs/review-context.md) - protected policy, GitLab discussions and same-revision CI outcomes, legacy reference migration, opaque handles, DLP, receipt, and cleanup contracts.
|
|
@@ -105,21 +111,23 @@ The shipped Repository Evidence Engine reads immutable base/head Git objects, st
|
|
|
105
111
|
|
|
106
112
|
1. Configure protected/masked `GITLAB_API_TOKEN` and LLM variables in GitLab.
|
|
107
113
|
2. Pin and checksum the OCR binary.
|
|
108
|
-
3.
|
|
109
|
-
4. Merge repository review configuration, including the Rules path, into the
|
|
114
|
+
3. With toolkit 0.13.0, leave `OCR_GITLAB_TARGET_PROTECTION_MODE` unset to use its `auto` default for a single MR targeting an unprotected branch under the same project's protected default-branch policy. Select explicit `required` to reject unprotected targets, or `unprotected` for the earlier limited profile.
|
|
115
|
+
4. Merge repository review configuration, including the Rules path, into the protected policy branch before relying on it.
|
|
110
116
|
5. Enable the CI review job in a later merge request and install this package.
|
|
111
117
|
6. Run the four public helper stages around `ocr review`:
|
|
112
118
|
|
|
113
119
|
```console
|
|
114
120
|
ocr-ci preflight
|
|
115
121
|
ocr-ci configure
|
|
116
|
-
|
|
117
|
-
|
|
122
|
+
review_result="/tmp/ocr-result-${CI_JOB_ID}.json"
|
|
123
|
+
review_stderr="/tmp/ocr-stderr-${CI_JOB_ID}.log"
|
|
124
|
+
ocr-ci review --result "$review_result" --stderr "$review_stderr" -- ... --format json || export OCR_EXIT_CODE=$?
|
|
125
|
+
ocr-ci post --result "$review_result" --stderr "$review_stderr"
|
|
118
126
|
```
|
|
119
127
|
|
|
120
128
|
See the [GitLab mode matrix](examples/gitlab/README.md), the complete [`ocr-review.gitlab-ci.yml`](examples/gitlab/ocr-review.gitlab-ci.yml) pipeline, the [GitLab setup guide](docs/gitlab.md), and [GitLab review operations](docs/operations.md).
|
|
121
129
|
|
|
122
|
-
The two-merge-request setup is recommended because repository Rules
|
|
130
|
+
The two-merge-request setup is recommended because repository Rules must already exist on the captured policy commit. For a protected target that is its own authority; for an unprotected target in `auto`, it is the protected default branch. The integration MR can add Rules and CI together when it need not be reviewed: its first run may stop with setup-pending, and later MRs work after the policy change merges. Retrying the same MR cannot promote its source Rules. An unprotected target in explicit `unprotected` mode with missing target Rules fails closed. A green advisory job or pipeline does not prove that OCR or its model executed. See the [GitLab setup guide](docs/gitlab.md#first-integration-sequence) for the exact boundary.
|
|
123
131
|
|
|
124
132
|
## Configuration and safety
|
|
125
133
|
|
|
@@ -27,6 +27,8 @@ dependencies = [
|
|
|
27
27
|
"mcp>=2.2.0,<3",
|
|
28
28
|
# MCP crypto accepts older PyJWT; enforce its security floor for resolver installs.
|
|
29
29
|
"PyJWT>=2.15.0,<3",
|
|
30
|
+
# MCP accepts an OpenTelemetry API version with vulnerable tracestate parsing.
|
|
31
|
+
"opentelemetry-api>=1.45.1,<2",
|
|
30
32
|
"jsonschema>=4.26.0,<5",
|
|
31
33
|
"httpx2>=2.13.0,<3",
|
|
32
34
|
"anyio>=4.10.0,<5",
|
{open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/_version.py
RENAMED
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '0.
|
|
22
|
-
__version_tuple__ = version_tuple = (0,
|
|
21
|
+
__version__ = version = '0.13.0'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 13, 0)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import argparse
|
|
6
|
+
import os
|
|
6
7
|
import sys
|
|
7
8
|
from collections.abc import Sequence
|
|
8
9
|
from pathlib import Path
|
|
@@ -93,6 +94,9 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|
|
93
94
|
**({"report_path": Path(args.report)} if args.report is not None else {}),
|
|
94
95
|
**({"debug_dir": Path(args.debug_dir)} if args.debug_dir is not None else {}),
|
|
95
96
|
)
|
|
97
|
+
except review_runner.ReviewRunnerNoHandoff as exc:
|
|
98
|
+
print(f"Cannot run Open Code Review: {exc}", file=sys.stderr)
|
|
99
|
+
return os.EX_TEMPFAIL
|
|
96
100
|
except review_runner.ReviewRunnerError as exc:
|
|
97
101
|
if not isinstance(exc, review_runner.ReviewRejected):
|
|
98
102
|
print(f"Cannot run Open Code Review: {exc}", file=sys.stderr)
|
|
@@ -147,9 +147,9 @@ def redact_env_secret_values(text: str) -> str:
|
|
|
147
147
|
def redact_sensitive(text: str) -> str:
|
|
148
148
|
"""Replace known secret values from the process environment with `***`.
|
|
149
149
|
|
|
150
|
-
Used before publishing OCR-controlled text to the merge request
|
|
151
|
-
|
|
152
|
-
|
|
150
|
+
Used before publishing OCR-controlled text to the merge request and when
|
|
151
|
+
rendering bounded local diagnostics from structured OCR JSON fields.
|
|
152
|
+
Performs two passes:
|
|
153
153
|
|
|
154
154
|
1. Replace each known env value (length >= 4). This is exact-match
|
|
155
155
|
replacement over a small allowlist of secret-bearing environment
|
|
@@ -4,7 +4,10 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import os
|
|
6
6
|
import stat
|
|
7
|
+
from collections.abc import Iterator
|
|
8
|
+
from contextlib import contextmanager
|
|
7
9
|
from dataclasses import dataclass
|
|
10
|
+
from fcntl import LOCK_EX, LOCK_NB, flock
|
|
8
11
|
from pathlib import Path
|
|
9
12
|
|
|
10
13
|
|
|
@@ -18,6 +21,7 @@ class EvidenceArtifacts:
|
|
|
18
21
|
policy_rules: Path
|
|
19
22
|
context_store: Path
|
|
20
23
|
pre_execution_status: Path
|
|
24
|
+
review_run_lock: Path
|
|
21
25
|
action_receipt: Path
|
|
22
26
|
action_receipt_lock: Path
|
|
23
27
|
dlp_decisions: Path
|
|
@@ -35,6 +39,7 @@ def repository_artifacts(root: Path | None = None) -> EvidenceArtifacts:
|
|
|
35
39
|
policy_rules=directory / "policy-rules.json",
|
|
36
40
|
context_store=directory / "context.json",
|
|
37
41
|
pre_execution_status=directory / "pre-execution-status.json",
|
|
42
|
+
review_run_lock=directory / ".review-run.lock",
|
|
38
43
|
action_receipt=directory / "evidence-actions.json",
|
|
39
44
|
action_receipt_lock=directory / ".evidence-actions.json.lock",
|
|
40
45
|
dlp_decisions=directory / "private-dlp-decisions.json",
|
|
@@ -48,10 +53,92 @@ def prepare_artifact_directory(artifacts: EvidenceArtifacts) -> None:
|
|
|
48
53
|
if directory.is_symlink():
|
|
49
54
|
raise OSError(f"refusing private artifact directory symlink: {directory}")
|
|
50
55
|
directory.mkdir(mode=0o700, parents=False, exist_ok=True)
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
56
|
+
descriptor = os.open(
|
|
57
|
+
directory,
|
|
58
|
+
os.O_RDONLY | getattr(os, "O_DIRECTORY", 0) | getattr(os, "O_NOFOLLOW", 0),
|
|
59
|
+
)
|
|
60
|
+
try:
|
|
61
|
+
metadata = os.fstat(descriptor)
|
|
62
|
+
named = directory.stat(follow_symlinks=False)
|
|
63
|
+
if (
|
|
64
|
+
not stat.S_ISDIR(metadata.st_mode)
|
|
65
|
+
or metadata.st_uid != os.getuid()
|
|
66
|
+
or (metadata.st_dev, metadata.st_ino) != (named.st_dev, named.st_ino)
|
|
67
|
+
):
|
|
68
|
+
raise OSError(f"private artifact path is not a safe directory: {directory}")
|
|
69
|
+
os.fchmod(descriptor, 0o700)
|
|
70
|
+
named = directory.stat(follow_symlinks=False)
|
|
71
|
+
if (metadata.st_dev, metadata.st_ino) != (named.st_dev, named.st_ino):
|
|
72
|
+
raise OSError(f"private artifact directory changed during setup: {directory}")
|
|
73
|
+
finally:
|
|
74
|
+
os.close(descriptor)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class ReviewRunBusy(OSError):
|
|
78
|
+
"""Another review owns the fixed private artifacts for this repository."""
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@contextmanager
|
|
82
|
+
def locked_review_run(artifacts: EvidenceArtifacts) -> Iterator[None]:
|
|
83
|
+
"""Hold one stable private lock across a review's entire artifact lifetime."""
|
|
84
|
+
|
|
85
|
+
prepare_artifact_directory(artifacts)
|
|
86
|
+
directory = -1
|
|
87
|
+
descriptor = -1
|
|
88
|
+
try:
|
|
89
|
+
directory = os.open(
|
|
90
|
+
artifacts.directory,
|
|
91
|
+
os.O_RDONLY | getattr(os, "O_DIRECTORY", 0) | getattr(os, "O_NOFOLLOW", 0),
|
|
92
|
+
)
|
|
93
|
+
directory_stat = os.fstat(directory)
|
|
94
|
+
named_directory = artifacts.directory.stat(follow_symlinks=False)
|
|
95
|
+
if (
|
|
96
|
+
not stat.S_ISDIR(directory_stat.st_mode)
|
|
97
|
+
or directory_stat.st_uid != os.getuid()
|
|
98
|
+
or (directory_stat.st_dev, directory_stat.st_ino)
|
|
99
|
+
!= (named_directory.st_dev, named_directory.st_ino)
|
|
100
|
+
or stat.S_IMODE(directory_stat.st_mode) & 0o077
|
|
101
|
+
):
|
|
102
|
+
raise OSError("private review lock directory is unsafe")
|
|
103
|
+
descriptor = os.open(
|
|
104
|
+
artifacts.review_run_lock.name,
|
|
105
|
+
os.O_CREAT | os.O_RDWR | getattr(os, "O_NOFOLLOW", 0) | getattr(os, "O_NONBLOCK", 0),
|
|
106
|
+
0o600,
|
|
107
|
+
dir_fd=directory,
|
|
108
|
+
)
|
|
109
|
+
opened = os.fstat(descriptor)
|
|
110
|
+
named = os.stat(
|
|
111
|
+
artifacts.review_run_lock.name,
|
|
112
|
+
dir_fd=directory,
|
|
113
|
+
follow_symlinks=False,
|
|
114
|
+
)
|
|
115
|
+
if (
|
|
116
|
+
not stat.S_ISREG(opened.st_mode)
|
|
117
|
+
or opened.st_uid != os.getuid()
|
|
118
|
+
or opened.st_nlink != 1
|
|
119
|
+
or stat.S_IMODE(opened.st_mode) & 0o077
|
|
120
|
+
or (opened.st_dev, opened.st_ino) != (named.st_dev, named.st_ino)
|
|
121
|
+
):
|
|
122
|
+
raise OSError("private review lock file is unsafe")
|
|
123
|
+
try:
|
|
124
|
+
flock(descriptor, LOCK_EX | LOCK_NB)
|
|
125
|
+
except BlockingIOError as exc:
|
|
126
|
+
raise ReviewRunBusy("another review is already running in this repository") from exc
|
|
127
|
+
named = os.stat(
|
|
128
|
+
artifacts.review_run_lock.name,
|
|
129
|
+
dir_fd=directory,
|
|
130
|
+
follow_symlinks=False,
|
|
131
|
+
)
|
|
132
|
+
if (opened.st_dev, opened.st_ino) != (named.st_dev, named.st_ino):
|
|
133
|
+
raise OSError("private review lock file changed during acquisition")
|
|
134
|
+
# Keep the named inode across runs; unlinking it would let a second
|
|
135
|
+
# review lock a replacement while this review still holds the old one.
|
|
136
|
+
yield
|
|
137
|
+
finally:
|
|
138
|
+
if descriptor >= 0:
|
|
139
|
+
os.close(descriptor)
|
|
140
|
+
if directory >= 0:
|
|
141
|
+
os.close(directory)
|
|
55
142
|
|
|
56
143
|
|
|
57
144
|
def write_private_bytes(path: Path, content: bytes) -> None:
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import os
|
|
6
|
-
from pathlib import Path
|
|
6
|
+
from pathlib import Path, PurePosixPath
|
|
7
7
|
|
|
8
8
|
from ocr_toolkit.evidence.categorize import categorize_paths
|
|
9
9
|
from ocr_toolkit.evidence.collectors import collect_ref_facts, fact_deltas
|
|
@@ -16,7 +16,11 @@ from ocr_toolkit.evidence.repository import (
|
|
|
16
16
|
file_deltas,
|
|
17
17
|
)
|
|
18
18
|
from ocr_toolkit.evidence.store import EvidenceStore, EvidenceStoreError
|
|
19
|
-
from ocr_toolkit.evidence.store.contracts import POLICY_KINDS
|
|
19
|
+
from ocr_toolkit.evidence.store.contracts import POLICY_KINDS, TARGET_CONTEXT_KINDS
|
|
20
|
+
|
|
21
|
+
MAX_TARGET_FILE_RECORDS = 64
|
|
22
|
+
MAX_TARGET_DEPENDENCY_RECORDS = 64
|
|
23
|
+
MAX_TARGET_DIAGNOSTICS = 32
|
|
20
24
|
|
|
21
25
|
|
|
22
26
|
def _commit_refs(
|
|
@@ -44,15 +48,62 @@ def _component_for_path(path: str) -> str:
|
|
|
44
48
|
return path.split("/", 1)[0] if "/" in path else "repository"
|
|
45
49
|
|
|
46
50
|
|
|
51
|
+
def _relevant_target_dependency(path: str, changed_paths: tuple[str, ...]) -> bool:
|
|
52
|
+
"""Select manifests in a changed path's own directory or an ancestor."""
|
|
53
|
+
|
|
54
|
+
parent = PurePosixPath(path).parent.as_posix()
|
|
55
|
+
return any(
|
|
56
|
+
changed == path or parent == "." or changed.startswith(parent + "/")
|
|
57
|
+
for changed in changed_paths
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _add_target_diagnostic(store: EvidenceStore, message: str) -> bool:
|
|
62
|
+
"""Keep an optional target notice only when the base evidence still serializes."""
|
|
63
|
+
|
|
64
|
+
prior_diagnostics = list(store.diagnostics)
|
|
65
|
+
try:
|
|
66
|
+
store.add_diagnostic(message)
|
|
67
|
+
store.to_json()
|
|
68
|
+
except EvidenceStoreError as exc:
|
|
69
|
+
store.diagnostics = prior_diagnostics
|
|
70
|
+
if str(exc) not in {
|
|
71
|
+
"serialized evidence store exceeds its byte budget",
|
|
72
|
+
"evidence diagnostic must contain between 1 and 1024 characters",
|
|
73
|
+
}:
|
|
74
|
+
raise
|
|
75
|
+
return False
|
|
76
|
+
return True
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _as_target_record(record: EvidenceRecord, target_sha: str) -> EvidenceRecord:
|
|
80
|
+
"""Reuse a collected code fact without inheriting its old ref or trust label."""
|
|
81
|
+
|
|
82
|
+
return EvidenceRecord(
|
|
83
|
+
kind=record.kind,
|
|
84
|
+
value=record.value,
|
|
85
|
+
source_path=record.source_path,
|
|
86
|
+
ref=RefRole.TARGET,
|
|
87
|
+
commit_sha=target_sha,
|
|
88
|
+
component=record.component,
|
|
89
|
+
provenance=record.provenance,
|
|
90
|
+
confidence=record.confidence,
|
|
91
|
+
trust=TrustClass.TARGET_REPOSITORY,
|
|
92
|
+
sensitivity=record.sensitivity,
|
|
93
|
+
staleness=record.staleness,
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
|
|
47
97
|
def collect_repository_evidence(
|
|
48
98
|
root: Path | None = None,
|
|
49
99
|
*,
|
|
50
100
|
base_ref: str | None = None,
|
|
51
101
|
head_ref: str | None = None,
|
|
52
102
|
policy_ref: str | None = None,
|
|
103
|
+
target_ref: str | None = None,
|
|
53
104
|
include_policy_records: bool = True,
|
|
54
105
|
) -> EvidenceStore:
|
|
55
|
-
"""Build evidence,
|
|
106
|
+
"""Build B/H diff evidence, separate P policy, and optional untrusted T context."""
|
|
56
107
|
|
|
57
108
|
reader = GitRepositoryReader(root or Path.cwd())
|
|
58
109
|
base_sha, head_sha = _commit_refs(reader, base_ref, head_ref)
|
|
@@ -219,4 +270,81 @@ def collect_repository_evidence(
|
|
|
219
270
|
*policy_diagnostics,
|
|
220
271
|
):
|
|
221
272
|
store.add_diagnostic(diagnostic)
|
|
273
|
+
if target_ref is not None:
|
|
274
|
+
# The actual target may differ from both the historical diff base and
|
|
275
|
+
# the protected policy branch. Its facts are optional code context and
|
|
276
|
+
# must never consume the budget reserved for B/H/P or alter their deltas.
|
|
277
|
+
store.to_json() # A pre-existing B/H/P budget failure remains a normal error.
|
|
278
|
+
target_sha = reader.resolve_commit(target_ref)
|
|
279
|
+
store.target = EvidenceSnapshot(RefRole.TARGET, target_sha, ())
|
|
280
|
+
try:
|
|
281
|
+
header_size = len(store.to_json().encode("utf-8"))
|
|
282
|
+
except EvidenceStoreError as exc:
|
|
283
|
+
if str(exc) != "serialized evidence store exceeds its byte budget":
|
|
284
|
+
raise
|
|
285
|
+
header_size = store.limits.max_bytes
|
|
286
|
+
if header_size > store.limits.max_bytes - 256:
|
|
287
|
+
store.target = None
|
|
288
|
+
_add_target_diagnostic(store, "actual target context omitted by evidence byte limit")
|
|
289
|
+
return store
|
|
290
|
+
if target_sha in {base_sha, head_sha}:
|
|
291
|
+
# The common unchanged-target case needs no third repository scan.
|
|
292
|
+
prior = base if target_sha == base_sha else head
|
|
293
|
+
prior_facts = base_facts if target_sha == base_sha else head_facts
|
|
294
|
+
target_files = EvidenceSnapshot(
|
|
295
|
+
RefRole.TARGET,
|
|
296
|
+
target_sha,
|
|
297
|
+
tuple(_as_target_record(record, target_sha) for record in prior.records),
|
|
298
|
+
diagnostics=prior.diagnostics,
|
|
299
|
+
)
|
|
300
|
+
target_facts = [_as_target_record(record, target_sha) for record in prior_facts]
|
|
301
|
+
target_fact_diagnostics: list[str] = []
|
|
302
|
+
else:
|
|
303
|
+
try:
|
|
304
|
+
target_files = build_file_snapshot(
|
|
305
|
+
reader, target_sha, RefRole.TARGET, paths=changed
|
|
306
|
+
)
|
|
307
|
+
target_facts, target_fact_diagnostics = collect_ref_facts(
|
|
308
|
+
reader, target_sha, RefRole.TARGET, changed_paths=changed
|
|
309
|
+
)
|
|
310
|
+
except RepositoryEvidenceError as exc:
|
|
311
|
+
if str(exc) != "repository tree exceeds the bounded entry limit":
|
|
312
|
+
raise
|
|
313
|
+
# T's commit identity was resolved before this optional scan.
|
|
314
|
+
# A target-only tree limit cannot change B/H/P or their deltas.
|
|
315
|
+
_add_target_diagnostic(
|
|
316
|
+
store, "actual target code context omitted by repository tree entry limit"
|
|
317
|
+
)
|
|
318
|
+
return store
|
|
319
|
+
diagnostics = (*target_files.diagnostics, *target_fact_diagnostics)
|
|
320
|
+
target_diagnostics = diagnostics[:MAX_TARGET_DIAGNOSTICS]
|
|
321
|
+
files = target_files.records[:MAX_TARGET_FILE_RECORDS]
|
|
322
|
+
dependencies = tuple(
|
|
323
|
+
record
|
|
324
|
+
for record in target_facts
|
|
325
|
+
if record.kind in TARGET_CONTEXT_KINDS - {"repository.file"}
|
|
326
|
+
and _relevant_target_dependency(record.source_path, changed)
|
|
327
|
+
)
|
|
328
|
+
dependencies = tuple(
|
|
329
|
+
sorted(dependencies, key=lambda item: (item.kind, item.source_path, item.id))
|
|
330
|
+
)
|
|
331
|
+
selected_dependencies = dependencies[:MAX_TARGET_DEPENDENCY_RECORDS]
|
|
332
|
+
omitted = (
|
|
333
|
+
len(target_files.records) > MAX_TARGET_FILE_RECORDS
|
|
334
|
+
or len(dependencies) > MAX_TARGET_DEPENDENCY_RECORDS
|
|
335
|
+
)
|
|
336
|
+
for record in (*files, *selected_dependencies):
|
|
337
|
+
if not store.add_target_context(record):
|
|
338
|
+
omitted = True
|
|
339
|
+
if omitted:
|
|
340
|
+
_add_target_diagnostic(
|
|
341
|
+
store, "target code context is incomplete due to bounded evidence limits"
|
|
342
|
+
)
|
|
343
|
+
if diagnostics:
|
|
344
|
+
_add_target_diagnostic(store, "target code context has unavailable source details")
|
|
345
|
+
for diagnostic in target_diagnostics:
|
|
346
|
+
if not _add_target_diagnostic(store, f"target:{diagnostic}"):
|
|
347
|
+
break
|
|
348
|
+
if len(diagnostics) > MAX_TARGET_DIAGNOSTICS:
|
|
349
|
+
_add_target_diagnostic(store, "target collection diagnostics were truncated")
|
|
222
350
|
return store
|
|
@@ -78,7 +78,7 @@ def collect_ref_facts(
|
|
|
78
78
|
diagnostics = []
|
|
79
79
|
trust = (
|
|
80
80
|
TrustClass.TARGET_REPOSITORY
|
|
81
|
-
if ref in {RefRole.BASE, RefRole.POLICY}
|
|
81
|
+
if ref in {RefRole.BASE, RefRole.POLICY, RefRole.TARGET}
|
|
82
82
|
else TrustClass.SOURCE_REPOSITORY
|
|
83
83
|
)
|
|
84
84
|
changed_exact = tuple(sorted(set(changed_paths)))
|
{open_code_review_toolkit-0.12.1 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/mcp.py
RENAMED
|
@@ -23,7 +23,7 @@ from ocr_toolkit.context.mcp import (
|
|
|
23
23
|
)
|
|
24
24
|
from ocr_toolkit.context.store import ContextStore
|
|
25
25
|
from ocr_toolkit.evidence.actions import UNATTRIBUTED_ACTION, record_action
|
|
26
|
-
from ocr_toolkit.evidence.model import CoverageRecord, EvidenceDelta, EvidenceRecord
|
|
26
|
+
from ocr_toolkit.evidence.model import CoverageRecord, EvidenceDelta, EvidenceRecord, RefRole
|
|
27
27
|
from ocr_toolkit.evidence.policy.schema import is_legacy_policy_value
|
|
28
28
|
from ocr_toolkit.evidence.store import EvidenceStore, EvidenceStoreError
|
|
29
29
|
|
|
@@ -161,6 +161,14 @@ def evidence_summary(store: EvidenceStore) -> dict[str, object]:
|
|
|
161
161
|
"policy": policy,
|
|
162
162
|
"merge_request_context": merge_request_context,
|
|
163
163
|
"policy_ref": store.policy.commit_sha if store.policy else None,
|
|
164
|
+
"target": store.target.commit_sha if store.target else None,
|
|
165
|
+
"target_context": {
|
|
166
|
+
"records": sum(record.ref is RefRole.TARGET for record in store.records),
|
|
167
|
+
"trust": "target_repository" if store.target else None,
|
|
168
|
+
"content_role": "untrusted_code_evidence" if store.target else None,
|
|
169
|
+
"authoritative_for_policy": False,
|
|
170
|
+
"absence_authoritative": False,
|
|
171
|
+
},
|
|
164
172
|
"coverage_contract": ("repository.evidence-coverage/v1" if store.coverage else "absent"),
|
|
165
173
|
"base": store.base.commit_sha if store.base else None,
|
|
166
174
|
"head": store.head.commit_sha if store.head else None,
|
|
@@ -233,8 +241,8 @@ def _list_records(store: EvidenceStore, arguments: dict[str, object]) -> dict[st
|
|
|
233
241
|
raise EvidenceMCPError("delta_kind requires kind=repository.evidence_delta")
|
|
234
242
|
if query.kind == "repository.evidence_delta" and query.ref is not None:
|
|
235
243
|
raise EvidenceMCPError("evidence deltas span base and head and do not accept ref")
|
|
236
|
-
if query.ref not in {None, "base", "head", "policy", "shared"}:
|
|
237
|
-
raise EvidenceMCPError("ref must be base, head, policy, or shared")
|
|
244
|
+
if query.ref not in {None, "base", "head", "policy", "target", "shared"}:
|
|
245
|
+
raise EvidenceMCPError("ref must be base, head, policy, target, or shared")
|
|
238
246
|
page_size = arguments.get("page_size", DEFAULT_PAGE_SIZE)
|
|
239
247
|
if isinstance(page_size, bool) or not isinstance(page_size, int):
|
|
240
248
|
raise EvidenceMCPError("page_size must be an integer")
|
|
@@ -375,8 +383,8 @@ def _search_records(store: EvidenceStore, arguments: dict[str, object]) -> dict[
|
|
|
375
383
|
raise EvidenceMCPError("delta_kind requires kind=repository.evidence_delta")
|
|
376
384
|
if kind == "repository.evidence_delta" and ref is not None:
|
|
377
385
|
raise EvidenceMCPError("evidence deltas span base and head and do not accept ref")
|
|
378
|
-
if ref not in {None, "base", "head", "policy", "shared"}:
|
|
379
|
-
raise EvidenceMCPError("ref must be base, head, policy, or shared")
|
|
386
|
+
if ref not in {None, "base", "head", "policy", "target", "shared"}:
|
|
387
|
+
raise EvidenceMCPError("ref must be base, head, policy, target, or shared")
|
|
380
388
|
max_results = arguments.get("max_results", DEFAULT_SEARCH_RESULTS)
|
|
381
389
|
if isinstance(max_results, bool) or not isinstance(max_results, int):
|
|
382
390
|
raise EvidenceMCPError("max_results must be an integer")
|
|
@@ -560,7 +568,8 @@ def _tool_definition() -> dict[str, object]:
|
|
|
560
568
|
return {
|
|
561
569
|
"name": TOOL_NAME,
|
|
562
570
|
"description": (
|
|
563
|
-
"Read bounded, redacted repository evidence for immutable base/head refs
|
|
571
|
+
"Read bounded, redacted repository evidence for immutable base/head refs and "
|
|
572
|
+
"optional actual-target code context (ref=target). "
|
|
564
573
|
"Use summary first, list to narrow, and get for one stable record. Query "
|
|
565
574
|
"kind=repository.evidence_delta with optional delta_kind for base/head changes. "
|
|
566
575
|
"Current structured decisions and guidance are target-derived non-authoritative "
|
|
@@ -601,7 +610,7 @@ def _tool_definition() -> dict[str, object]:
|
|
|
601
610
|
},
|
|
602
611
|
"ref": {
|
|
603
612
|
"type": "string",
|
|
604
|
-
"enum": ["base", "head", "policy", "shared"],
|
|
613
|
+
"enum": ["base", "head", "policy", "target", "shared"],
|
|
605
614
|
"description": "Optional immutable-ref filter for action=list only.",
|
|
606
615
|
},
|
|
607
616
|
"page_size": {
|
|
@@ -659,7 +668,7 @@ def _search_tool_definition() -> dict[str, object]:
|
|
|
659
668
|
),
|
|
660
669
|
},
|
|
661
670
|
"component": {"type": "string", "maxLength": 256},
|
|
662
|
-
"ref": {"type": "string", "enum": ["base", "head", "policy", "shared"]},
|
|
671
|
+
"ref": {"type": "string", "enum": ["base", "head", "policy", "target", "shared"]},
|
|
663
672
|
"max_results": {
|
|
664
673
|
"type": "integer",
|
|
665
674
|
"minimum": 1,
|
|
@@ -43,6 +43,7 @@ class RefRole(str, Enum):
|
|
|
43
43
|
BASE = "base"
|
|
44
44
|
HEAD = "head"
|
|
45
45
|
POLICY = "policy"
|
|
46
|
+
TARGET = "target"
|
|
46
47
|
SHARED = "shared"
|
|
47
48
|
|
|
48
49
|
|
|
@@ -408,9 +409,9 @@ class EvidenceSnapshot:
|
|
|
408
409
|
"""Ensure snapshot records match the declared ref and are ordered."""
|
|
409
410
|
|
|
410
411
|
if self.ref is RefRole.SHARED:
|
|
411
|
-
raise ValueError("snapshot ref must be base, head, or
|
|
412
|
-
if self.ref
|
|
413
|
-
raise ValueError("policy
|
|
412
|
+
raise ValueError("snapshot ref must be base, head, policy, or target")
|
|
413
|
+
if self.ref in {RefRole.POLICY, RefRole.TARGET} and self.coverage:
|
|
414
|
+
raise ValueError("policy and target snapshots cannot contain coverage")
|
|
414
415
|
if len(self.commit_sha) != 40 or not all(c in "0123456789abcdef" for c in self.commit_sha):
|
|
415
416
|
raise ValueError("snapshot commit_sha must be a lowercase 40-character SHA-1")
|
|
416
417
|
for record in self.records:
|