open-code-review-toolkit 0.12.0__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.0 → open_code_review_toolkit-0.13.0}/.gitignore +1 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/PKG-INFO +21 -10
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/README.md +19 -9
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/pyproject.toml +2 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/_version.py +2 -2
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/cli.py +4 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/common/redaction.py +3 -3
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/context/dlp.py +87 -7
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/artifacts.py +91 -4
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/collect.py +131 -3
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/collectors/orchestration.py +1 -1
- open_code_review_toolkit-0.13.0/src/ocr_toolkit/evidence/gitlinks.py +66 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/mcp.py +17 -8
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/model.py +4 -3
- open_code_review_toolkit-0.13.0/src/ocr_toolkit/evidence/opaque_code.py +108 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/project.py +24 -1
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/repository.py +65 -9
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/store/contracts.py +3 -2
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/store/core.py +81 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/store/readback.py +11 -1
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/federation/admission.py +15 -5
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/federation/contracts.py +14 -1
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/federation/gateway.py +19 -6
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/federation/runtime.py +8 -8
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/local_chatgpt.py +16 -4
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/ocr_result.py +5 -2
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/posting/formatting.py +34 -7
- open_code_review_toolkit-0.13.0/src/ocr_toolkit/posting/freshness.py +166 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/posting/gitlab.py +1 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/posting/gitlab_approval.py +69 -2
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/posting/snapshot.py +3 -6
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/posting/workflow.py +716 -236
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/pre_execution.py +159 -15
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/preflight.py +21 -1
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/providers/gitlab.py +182 -29
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/reporting/dlp.py +98 -8
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/reporting/federation.py +67 -4
- open_code_review_toolkit-0.13.0/src/ocr_toolkit/review_identity.py +58 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/review_receipt.py +234 -52
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/review_runner.py +461 -105
- open_code_review_toolkit-0.12.0/src/ocr_toolkit/review_identity.py +0 -26
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/LICENSE +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/__init__.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/common/__init__.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/common/filesystem.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/common/git.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/common/language.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/common/markdown.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/config_writer.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/configure.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/context/__init__.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/context/broker.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/context/ci_outcomes.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/context/contracts.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/context/mcp.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/context/policy.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/context/recognizers.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/context/store.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/__init__.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/__main__.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/actions.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/categorize.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/collectors/__init__.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/collectors/graphs.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/collectors/projections.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/collectors/registry.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/collectors/sources.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/coverage.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/ecosystems/__init__.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/ecosystems/ansible/__init__.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/ecosystems/ansible/requirements.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/ecosystems/ansible/topology.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/ecosystems/contracts.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/ecosystems/go.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/ecosystems/javascript.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/ecosystems/php.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/ecosystems/python.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/frameworks/__init__.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/frameworks/contracts.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/frameworks/detection.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/frameworks/providers/__init__.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/frameworks/providers/frontend.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/frameworks/providers/go.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/frameworks/providers/php.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/frameworks/providers/python.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/frameworks/registry.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/frameworks/schema.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/frameworks/templates.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/infrastructure.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/invocation.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/policy/__init__.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/policy/contracts.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/policy/decisions.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/policy/guidance.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/policy/registry.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/policy/schema.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/policy/scopes.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/review_context.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/store/__init__.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/store/atomic.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/evidence/store/values.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/federation/__init__.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/federation/registry.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/federation/relay.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/federation/transport.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/mcp_config.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/posting/__init__.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/posting/__main__.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/posting/approval.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/posting/comments.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/posting/markers.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/posting/payloads.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/posting/reconciliation.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/posting/result.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/posting/settings.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/posting/suggestions.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/posting/transaction.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/provider_config.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/provider_failure.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/providers/__init__.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/providers/gitlab_ci.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/providers/gitlab_context.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/providers/gitlab_discussions.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/providers/gitlab_identity.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/providers/gitlab_remediation.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/providers/local.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/providers/local_config.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/py.typed +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/reporting/__init__.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/reporting/metadata.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/reporting/model.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/reporting/outcome.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/reporting/result.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/reporting/sections.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/reporting/text.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/reporting/usage.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/result_contract.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/result_usage.py +0 -0
- {open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/review_debug.py +0 -0
- {open_code_review_toolkit-0.12.0 → 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
|
|
|
@@ -242,6 +243,8 @@ Description-Content-Type: text/markdown
|
|
|
242
243
|
|
|
243
244
|
Open Code Review Toolkit is an unofficial review control and integration layer for [Alibaba Open Code Review](https://github.com/alibaba/open-code-review), with GitLab CI and standalone local providers. It provides bounded repository evidence, a compact review bootstrap, a built-in read-only MCP server, environment-driven OCR configuration, preflight validation, local Markdown reports, and safe GitLab merge-request posting. It does **not** bundle or download the `ocr` binary.
|
|
244
245
|
|
|
246
|
+
[Explore the toolkit site](https://open-code-review-toolkit.pages.dev/) for a quick overview, demo video, and latest release notes.
|
|
247
|
+
|
|
245
248
|
> [!NOTE]
|
|
246
249
|
> The project is under active development; the public API, CLI, environment contract, and generated schemas may evolve before 1.0.
|
|
247
250
|
|
|
@@ -295,7 +298,9 @@ commit without forge credentials:
|
|
|
295
298
|
|
|
296
299
|
```console
|
|
297
300
|
ocr-ci preflight --local
|
|
298
|
-
|
|
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
|
|
299
304
|
```
|
|
300
305
|
|
|
301
306
|
Local execution uses the same OCR, mandatory evidence MCP, validation and DLP
|
|
@@ -319,13 +324,17 @@ On a successful rerun, the toolkit replaces untouched OCR-only notes instead of
|
|
|
319
324
|
|
|
320
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.
|
|
321
326
|
|
|
322
|
-
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.
|
|
328
|
+
|
|
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.
|
|
323
332
|
|
|
324
|
-
Accepted tradeoffs can be recorded in `.opencodereview/accepted-decisions.md`; the evidence collector supplies only applicable
|
|
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.
|
|
325
334
|
|
|
326
335
|
## Project architecture
|
|
327
336
|
|
|
328
|
-
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.
|
|
329
338
|
|
|
330
339
|
- [Toolkit strategy](docs/engineering/toolkit_strategy.md) - durable product boundaries, architecture, invariants, and non-goals.
|
|
331
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.
|
|
@@ -336,21 +345,23 @@ The shipped Repository Evidence Engine reads immutable base/head Git objects, st
|
|
|
336
345
|
|
|
337
346
|
1. Configure protected/masked `GITLAB_API_TOKEN` and LLM variables in GitLab.
|
|
338
347
|
2. Pin and checksum the OCR binary.
|
|
339
|
-
3.
|
|
340
|
-
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.
|
|
341
350
|
5. Enable the CI review job in a later merge request and install this package.
|
|
342
351
|
6. Run the four public helper stages around `ocr review`:
|
|
343
352
|
|
|
344
353
|
```console
|
|
345
354
|
ocr-ci preflight
|
|
346
355
|
ocr-ci configure
|
|
347
|
-
|
|
348
|
-
|
|
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"
|
|
349
360
|
```
|
|
350
361
|
|
|
351
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).
|
|
352
363
|
|
|
353
|
-
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.
|
|
354
365
|
|
|
355
366
|
## Configuration and safety
|
|
356
367
|
|
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
|
|
10
10
|
Open Code Review Toolkit is an unofficial review control and integration layer for [Alibaba Open Code Review](https://github.com/alibaba/open-code-review), with GitLab CI and standalone local providers. It provides bounded repository evidence, a compact review bootstrap, a built-in read-only MCP server, environment-driven OCR configuration, preflight validation, local Markdown reports, and safe GitLab merge-request posting. It does **not** bundle or download the `ocr` binary.
|
|
11
11
|
|
|
12
|
+
[Explore the toolkit site](https://open-code-review-toolkit.pages.dev/) for a quick overview, demo video, and latest release notes.
|
|
13
|
+
|
|
12
14
|
> [!NOTE]
|
|
13
15
|
> The project is under active development; the public API, CLI, environment contract, and generated schemas may evolve before 1.0.
|
|
14
16
|
|
|
@@ -62,7 +64,9 @@ commit without forge credentials:
|
|
|
62
64
|
|
|
63
65
|
```console
|
|
64
66
|
ocr-ci preflight --local
|
|
65
|
-
|
|
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
|
|
66
70
|
```
|
|
67
71
|
|
|
68
72
|
Local execution uses the same OCR, mandatory evidence MCP, validation and DLP
|
|
@@ -86,13 +90,17 @@ On a successful rerun, the toolkit replaces untouched OCR-only notes instead of
|
|
|
86
90
|
|
|
87
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.
|
|
88
92
|
|
|
89
|
-
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.
|
|
94
|
+
|
|
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.
|
|
90
98
|
|
|
91
|
-
Accepted tradeoffs can be recorded in `.opencodereview/accepted-decisions.md`; the evidence collector supplies only applicable
|
|
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.
|
|
92
100
|
|
|
93
101
|
## Project architecture
|
|
94
102
|
|
|
95
|
-
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.
|
|
96
104
|
|
|
97
105
|
- [Toolkit strategy](docs/engineering/toolkit_strategy.md) - durable product boundaries, architecture, invariants, and non-goals.
|
|
98
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.
|
|
@@ -103,21 +111,23 @@ The shipped Repository Evidence Engine reads immutable base/head Git objects, st
|
|
|
103
111
|
|
|
104
112
|
1. Configure protected/masked `GITLAB_API_TOKEN` and LLM variables in GitLab.
|
|
105
113
|
2. Pin and checksum the OCR binary.
|
|
106
|
-
3.
|
|
107
|
-
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.
|
|
108
116
|
5. Enable the CI review job in a later merge request and install this package.
|
|
109
117
|
6. Run the four public helper stages around `ocr review`:
|
|
110
118
|
|
|
111
119
|
```console
|
|
112
120
|
ocr-ci preflight
|
|
113
121
|
ocr-ci configure
|
|
114
|
-
|
|
115
|
-
|
|
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"
|
|
116
126
|
```
|
|
117
127
|
|
|
118
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).
|
|
119
129
|
|
|
120
|
-
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.
|
|
121
131
|
|
|
122
132
|
## Configuration and safety
|
|
123
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.0 → 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
|
{open_code_review_toolkit-0.12.0 → open_code_review_toolkit-0.13.0}/src/ocr_toolkit/context/dlp.py
RENAMED
|
@@ -3,14 +3,20 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import html
|
|
6
|
+
import ipaddress
|
|
6
7
|
import re
|
|
7
8
|
import unicodedata
|
|
8
9
|
from collections.abc import Iterator, Mapping
|
|
9
10
|
from contextlib import contextmanager
|
|
10
11
|
from contextvars import ContextVar
|
|
11
12
|
from dataclasses import dataclass
|
|
13
|
+
from datetime import date
|
|
12
14
|
|
|
13
|
-
from ocr_toolkit.common.redaction import
|
|
15
|
+
from ocr_toolkit.common.redaction import (
|
|
16
|
+
SENSITIVE_NAMED_KEY_PATTERN,
|
|
17
|
+
redact_env_secret_values,
|
|
18
|
+
redact_sensitive,
|
|
19
|
+
)
|
|
14
20
|
from ocr_toolkit.context.contracts import TextBudgets
|
|
15
21
|
|
|
16
22
|
EMAIL_RE = re.compile(
|
|
@@ -18,6 +24,20 @@ EMAIL_RE = re.compile(
|
|
|
18
24
|
r"@(?:[a-z0-9-]{1,63}\.)+[a-z]{2,63}(?![a-z0-9_-])"
|
|
19
25
|
)
|
|
20
26
|
PHONE_RE = re.compile(r"(?<!\w)(?:\+?[1-9][\d .()/-]{7,24}\d)(?!\w)")
|
|
27
|
+
PURE_CREDENTIAL_ASSIGNMENT_RE = re.compile(
|
|
28
|
+
rf"(?im)^[ \t]*{SENSITIVE_NAMED_KEY_PATTERN}[ \t]*:[ \t]*"
|
|
29
|
+
r"(?:(?P<quote>['\"])\{\{[ \t]*[A-Za-z_][A-Za-z0-9_]*[ \t]*\}\}(?P=quote)|"
|
|
30
|
+
r"(?P<empty>\"\"|''))[ \t]*$"
|
|
31
|
+
)
|
|
32
|
+
ISO_DATE_RE = re.compile(r"(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})")
|
|
33
|
+
VERSION_CONTEXT_RE = re.compile(r"(?i)\b(?:version|release|build|pin)[ \t]+(?:v)?$")
|
|
34
|
+
DATE_CONTEXT_RE = re.compile(
|
|
35
|
+
r"(?i)\b(?:date|dated|on|since|until|before|after|expires)(?:[ \t]+on)?[ \t]+$"
|
|
36
|
+
)
|
|
37
|
+
IP_CONTEXT_RE = re.compile(
|
|
38
|
+
r"(?i)(?:\b|_)(?:ipv4|ip|cidr|subnet|address|host|loginhost|listen|allow|network)\b"
|
|
39
|
+
)
|
|
40
|
+
PHONE_CONTEXT_RE = re.compile(r"(?i)\b(?:call|phone|tel|contact)[ \t]*$")
|
|
21
41
|
MARKDOWN_DEST_RE = re.compile(
|
|
22
42
|
r"(?:\]\(\s*<?(?:https?://|mailto:)|^\s*\[[^\]\n]+\]:\s*<?(?:https?://|mailto:)|"
|
|
23
43
|
r"<(?:https?://|mailto:))",
|
|
@@ -95,11 +115,59 @@ def _contains_phone(value: str) -> bool:
|
|
|
95
115
|
"""Recognize formatted phone-like values without classifying bare identifiers."""
|
|
96
116
|
|
|
97
117
|
return any(
|
|
98
|
-
|
|
118
|
+
(
|
|
119
|
+
match.group(0).startswith("+")
|
|
120
|
+
or any(separator in match.group(0) for separator in " .()/-")
|
|
121
|
+
)
|
|
122
|
+
and not _technical_number(value, match)
|
|
99
123
|
for match in PHONE_RE.finditer(value)
|
|
100
124
|
)
|
|
101
125
|
|
|
102
126
|
|
|
127
|
+
def _technical_number(value: str, match: re.Match[str]) -> bool:
|
|
128
|
+
"""Exclude only bounded, parsed technical syntax from phone candidates."""
|
|
129
|
+
|
|
130
|
+
candidate = match.group(0)
|
|
131
|
+
if candidate.startswith("+") or candidate != candidate.strip():
|
|
132
|
+
return False
|
|
133
|
+
before, after = value[: match.start()], value[match.end() :]
|
|
134
|
+
if before.endswith((".", "/", "-")) or re.match(r"(?:[./-]\d)", after):
|
|
135
|
+
return False
|
|
136
|
+
if PHONE_CONTEXT_RE.search(before[-24:]):
|
|
137
|
+
return False
|
|
138
|
+
try:
|
|
139
|
+
if "/" in candidate:
|
|
140
|
+
ipaddress.IPv4Network(candidate, strict=False)
|
|
141
|
+
return True
|
|
142
|
+
elif isinstance(ipaddress.ip_address(candidate), ipaddress.IPv4Address):
|
|
143
|
+
if after.startswith(":"):
|
|
144
|
+
port = re.match(r":(\d{1,5})\b", after)
|
|
145
|
+
return bool(port and 1 <= int(port[1]) <= 65535)
|
|
146
|
+
return bool(IP_CONTEXT_RE.search(before[-96:]))
|
|
147
|
+
except ValueError:
|
|
148
|
+
pass
|
|
149
|
+
if ISO_DATE_RE.fullmatch(candidate) and DATE_CONTEXT_RE.search(before[-32:]):
|
|
150
|
+
try:
|
|
151
|
+
date(int(candidate[:4]), int(candidate[5:7]), int(candidate[8:10]))
|
|
152
|
+
except ValueError:
|
|
153
|
+
return False
|
|
154
|
+
return True
|
|
155
|
+
if re.fullmatch(r"\d{4}\.\d{1,2}\.\d{1,3}", candidate):
|
|
156
|
+
return bool(VERSION_CONTEXT_RE.search(before[-32:]))
|
|
157
|
+
if re.fullmatch(r"\d+\.\d+", candidate):
|
|
158
|
+
return bool(
|
|
159
|
+
re.search(r"(?i)\bdecimal\([ \t]*['\"]$", before[-32:])
|
|
160
|
+
and re.match(r"['\"][ \t]*\)", after)
|
|
161
|
+
)
|
|
162
|
+
return False
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def _semantic_secret_text(value: str) -> str:
|
|
166
|
+
"""Remove proven no-value assignments only from generic secret classification."""
|
|
167
|
+
|
|
168
|
+
return PURE_CREDENTIAL_ASSIGNMENT_RE.sub("safe_reference_assignment", value)
|
|
169
|
+
|
|
170
|
+
|
|
103
171
|
def _display_normalize(value: str) -> str:
|
|
104
172
|
"""Approximate closed text rendered by common Markdown/HTML constructs."""
|
|
105
173
|
|
|
@@ -235,6 +303,7 @@ def check_text(
|
|
|
235
303
|
forbidden: tuple[str | ForbiddenValue, ...] = (),
|
|
236
304
|
forbidden_matcher: ForbiddenMatcher | None = None,
|
|
237
305
|
allow_horizontal_tabs: bool = False,
|
|
306
|
+
allow_opaque_code: bool = False,
|
|
238
307
|
enabled: bool | None = None,
|
|
239
308
|
) -> DLPResult:
|
|
240
309
|
"""Apply independent units, redaction, PII, and optional publication checks."""
|
|
@@ -258,15 +327,26 @@ def check_text(
|
|
|
258
327
|
return DLPResult(False, None, "limit", detector)
|
|
259
328
|
if not (_DLP_ENABLED.get() if enabled is None else enabled):
|
|
260
329
|
return DLPResult(True, normalized, "disabled")
|
|
261
|
-
|
|
262
|
-
if
|
|
330
|
+
# Exact environment values retain priority even when they resemble a reference.
|
|
331
|
+
if redact_env_secret_values(normalized) != normalized:
|
|
332
|
+
return DLPResult(False, None, "secret", "normalized")
|
|
333
|
+
semantic = _semantic_secret_text(normalized)
|
|
334
|
+
if redact_sensitive(semantic) != semantic:
|
|
263
335
|
return DLPResult(False, None, "secret", "normalized")
|
|
264
336
|
decoded = _html_decode(normalized) if publication else normalized
|
|
265
337
|
displayed = _display_normalize(normalized) if publication else normalized
|
|
266
338
|
source = _source_normalize(normalized) if publication else normalized
|
|
267
|
-
|
|
339
|
+
semantic_displayed = _display_normalize(semantic) if publication else semantic
|
|
340
|
+
semantic_source = _source_normalize(semantic) if publication else semantic
|
|
341
|
+
if publication and (
|
|
342
|
+
redact_env_secret_values(displayed) != displayed
|
|
343
|
+
or redact_sensitive(semantic_displayed) != semantic_displayed
|
|
344
|
+
):
|
|
268
345
|
return DLPResult(False, None, "secret", "displayed")
|
|
269
|
-
if publication and
|
|
346
|
+
if publication and (
|
|
347
|
+
redact_env_secret_values(source) != source
|
|
348
|
+
or redact_sensitive(semantic_source) != semantic_source
|
|
349
|
+
):
|
|
270
350
|
return DLPResult(False, None, "secret", "source")
|
|
271
351
|
pii_candidates = (("normalized", normalized),)
|
|
272
352
|
if publication:
|
|
@@ -289,7 +369,7 @@ def check_text(
|
|
|
289
369
|
else "html_comment"
|
|
290
370
|
if HTML_COMMENT_RE.search(decoded)
|
|
291
371
|
else "html_tag"
|
|
292
|
-
if HTML_TAG_RE.search(decoded)
|
|
372
|
+
if HTML_TAG_RE.search(decoded) and not (allow_opaque_code and decoded == normalized)
|
|
293
373
|
else "display_control"
|
|
294
374
|
if any(_is_display_control(character) for character in decoded)
|
|
295
375
|
else None
|
|
@@ -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:
|