open-code-review-toolkit 0.8.6__tar.gz → 0.9.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.8.6 → open_code_review_toolkit-0.9.0}/PKG-INFO +18 -11
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/README.md +17 -10
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/_version.py +2 -2
- open_code_review_toolkit-0.9.0/src/ocr_toolkit/context/ci_outcomes.py +141 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/context/contracts.py +28 -4
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/context/policy.py +85 -1
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/context/store.py +92 -1
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/evidence/actions.py +38 -14
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/evidence/collect.py +21 -10
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/evidence/collectors/projections.py +10 -3
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/evidence/mcp.py +367 -9
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/evidence/project.py +40 -7
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/evidence/store/values.py +4 -5
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/mcp_config.py +24 -7
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/ocr_result.py +3 -1
- open_code_review_toolkit-0.9.0/src/ocr_toolkit/posting/approval.py +152 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/posting/formatting.py +34 -17
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/posting/gitlab.py +15 -18
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/posting/workflow.py +41 -37
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/preflight.py +1 -1
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/providers/gitlab.py +27 -4
- open_code_review_toolkit-0.9.0/src/ocr_toolkit/providers/gitlab_ci.py +292 -0
- open_code_review_toolkit-0.9.0/src/ocr_toolkit/review_identity.py +26 -0
- open_code_review_toolkit-0.8.6/src/ocr_toolkit/posting/approval.py → open_code_review_toolkit-0.9.0/src/ocr_toolkit/review_receipt.py +204 -137
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/review_runner.py +424 -41
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/.gitignore +0 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/LICENSE +0 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/pyproject.toml +0 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/__init__.py +0 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/cli.py +0 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/common/__init__.py +0 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/common/filesystem.py +0 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/common/git.py +0 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/common/language.py +0 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/common/markdown.py +0 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/common/redaction.py +0 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/config_writer.py +0 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/configure.py +0 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/context/__init__.py +0 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/context/adapters.py +0 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/context/broker.py +0 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/context/dlp.py +0 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/context/mcp.py +0 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/context/recognizers.py +0 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/evidence/__init__.py +0 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/evidence/__main__.py +0 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/evidence/artifacts.py +0 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/evidence/categorize.py +0 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/evidence/collectors/__init__.py +0 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/evidence/collectors/graphs.py +0 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/evidence/collectors/orchestration.py +0 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/evidence/collectors/registry.py +0 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/evidence/collectors/sources.py +0 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/evidence/coverage.py +0 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/evidence/ecosystems/__init__.py +0 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/evidence/ecosystems/ansible/__init__.py +0 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/evidence/ecosystems/ansible/requirements.py +0 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/evidence/ecosystems/ansible/topology.py +0 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/evidence/ecosystems/contracts.py +0 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/evidence/ecosystems/go.py +0 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/evidence/ecosystems/javascript.py +0 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/evidence/ecosystems/php.py +0 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/evidence/ecosystems/python.py +0 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/evidence/frameworks/__init__.py +0 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/evidence/frameworks/contracts.py +0 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/evidence/frameworks/detection.py +0 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/evidence/frameworks/providers/__init__.py +0 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/evidence/frameworks/providers/frontend.py +0 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/evidence/frameworks/providers/go.py +0 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/evidence/frameworks/providers/php.py +0 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/evidence/frameworks/providers/python.py +0 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/evidence/frameworks/registry.py +0 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/evidence/frameworks/schema.py +0 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/evidence/frameworks/templates.py +0 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/evidence/infrastructure.py +0 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/evidence/invocation.py +0 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/evidence/model.py +0 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/evidence/policy/__init__.py +0 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/evidence/policy/contracts.py +0 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/evidence/policy/decisions.py +0 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/evidence/policy/guidance.py +0 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/evidence/policy/registry.py +0 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/evidence/policy/schema.py +0 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/evidence/policy/scopes.py +0 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/evidence/repository.py +0 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/evidence/review_context.py +0 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/evidence/store/__init__.py +0 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/evidence/store/atomic.py +0 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/evidence/store/contracts.py +0 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/evidence/store/core.py +0 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/evidence/store/readback.py +0 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/posting/__init__.py +0 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/posting/__main__.py +0 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/posting/comments.py +0 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/posting/gitlab_approval.py +0 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/posting/markers.py +0 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/posting/payloads.py +0 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/posting/reconciliation.py +0 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/posting/result.py +0 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/posting/settings.py +0 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/posting/snapshot.py +0 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/posting/suggestions.py +0 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/posting/transaction.py +0 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/pre_execution.py +0 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/provider_config.py +0 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/provider_failure.py +0 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/providers/__init__.py +0 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/providers/gitlab_context.py +0 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/providers/gitlab_discussions.py +0 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/providers/gitlab_identity.py +0 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/providers/gitlab_remediation.py +0 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/py.typed +0 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/result_contract.py +0 -0
- {open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/result_usage.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: open-code-review-toolkit
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.9.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
|
|
@@ -227,6 +227,9 @@ Description-Content-Type: text/markdown
|
|
|
227
227
|
|
|
228
228
|
# Open Code Review Toolkit
|
|
229
229
|
|
|
230
|
+
[](https://pypi.org/project/open-code-review-toolkit/)
|
|
231
|
+
[](https://pypi.org/project/open-code-review-toolkit/)
|
|
232
|
+
[](https://github.com/xeonvs/open-code-review-toolkit/blob/main/LICENSE)
|
|
230
233
|
[](https://www.bestpractices.dev/projects/13906)
|
|
231
234
|
[](https://securityscorecards.dev/viewer/?uri=github.com/xeonvs/open-code-review-toolkit)
|
|
232
235
|
[](https://github.com/xeonvs/open-code-review-toolkit/actions/workflows/codeql.yml)
|
|
@@ -255,11 +258,11 @@ python -m pip install open-code-review-toolkit
|
|
|
255
258
|
```
|
|
256
259
|
|
|
257
260
|
The toolkit does not bundle OCR. Download the platform asset for exact
|
|
258
|
-
[Open Code Review 1.11.
|
|
261
|
+
[Open Code Review 1.11.3](https://github.com/alibaba/open-code-review/releases/tag/v1.11.3),
|
|
259
262
|
verify it before installation against the [compatibility manifest](compatibility/ocr-support.json),
|
|
260
263
|
and place the verified binary on `PATH`. The current Linux amd64 digest is
|
|
261
|
-
`
|
|
262
|
-
`
|
|
264
|
+
`9726204ac81baee153fd65b1ff357c380f73e9d8091c4a73c3c9fb541b5164cb`; Darwin arm64 is
|
|
265
|
+
`515cd92ce300b62dea2bcaf53e910cdf57b681e1d58bbd0a9e8e49617bca52fa`. The manifest owns
|
|
263
266
|
the other platform hashes.
|
|
264
267
|
|
|
265
268
|
Verify the installation without contacting an LLM provider:
|
|
@@ -269,14 +272,14 @@ ocr --version
|
|
|
269
272
|
ocr-ci --help
|
|
270
273
|
```
|
|
271
274
|
|
|
272
|
-
`ocr --version` must report `open-code-review v1.11.
|
|
275
|
+
`ocr --version` must report `open-code-review v1.11.3`; `ocr-ci --help` must exit
|
|
273
276
|
successfully. `ocr-ci preflight` is an operational configuration check, not the installation
|
|
274
277
|
smoke test.
|
|
275
278
|
|
|
276
279
|
The exact recommended OCR release and its verified asset checksums live in the [versioned compatibility manifest](compatibility/ocr-support.json). CI should pin that release and checksum before execution.
|
|
277
280
|
The [versioned compatibility policy](docs/compatibility.md) records tested assets and evidence and describes the conservative Dependabot-like qualification workflow for later upstream releases.
|
|
278
281
|
Review output defaults to English. `OCR_REVIEW_LANGUAGE` accepts another explicit language name when a project needs localized review output; for example, `OCR_REVIEW_LANGUAGE=Russian`.
|
|
279
|
-
The current OCR 1.11.
|
|
282
|
+
The current OCR 1.11.3 integration defaults `OCR_REVIEW_EFFORT` to `medium` for two review rounds. `low` and `high` are explicit one- and three-round alternatives; see the [configuration reference](docs/configuration.md#review-effort) for cost, budget, and precedence boundaries.
|
|
280
283
|
|
|
281
284
|
Stable distributions are published to [PyPI](https://pypi.org/project/open-code-review-toolkit/) and mirrored as checksum-listed, provenance-attested assets in the corresponding [GitHub Release](https://github.com/xeonvs/open-code-review-toolkit/releases). Development snapshots are published only to TestPyPI.
|
|
282
285
|
|
|
@@ -286,16 +289,16 @@ On a successful rerun, the toolkit replaces untouched OCR-only notes instead of
|
|
|
286
289
|
|
|
287
290
|
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.
|
|
288
291
|
|
|
289
|
-
After every current review note publishes, the GitLab adapter can add a conservative approval bound to receipt
|
|
292
|
+
After every current review note publishes, the GitLab adapter can add a conservative approval bound to receipt v8's exact reviewed source SHA, target SHA/protection state, and merge-request author. 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, and adapter records do not independently block approval, while an unprotected target, degraded metadata, DLP rejection, required context degradation, admitted remediation history, legacy receipts, publication filtering, any direct external MCP, 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.
|
|
290
293
|
|
|
291
294
|
Accepted tradeoffs can be recorded in `.opencodereview/accepted-decisions.md`; the evidence collector supplies only applicable target-ref decisions and never lets a source change self-authorize its review. Root and nested target `AGENTS.md`/`CLAUDE.md` guidance is similarly exposed through the existing evidence MCP with deterministic scope and precedence, while any guidance touched by the merge request is excluded. See [Accepted project decisions](docs/configuration.md#accepted-project-decisions) and [Target project guidance](docs/configuration.md#target-project-guidance) for formats and trust boundaries.
|
|
292
295
|
|
|
293
296
|
## Project architecture
|
|
294
297
|
|
|
295
|
-
The shipped Repository Evidence Engine reads immutable base/head Git objects, stores bounded typed facts and deltas, creates the compact bootstrap used by OCR, and exposes detailed facts, scoped
|
|
298
|
+
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 three fixed tools in the mandatory built-in read-only MCP server. Protected-policy enriched reviews can acquire stable GitLab discussions, verified remediation history, protected same-revision CI outcomes, and authorized external issue/document records before OCR. Forge-specific acquisition and posting stay at provider edges; the broker, DLP, store, MCP, receipts, and tests use common contracts so a future GitHub adapter can reuse them without inheriting GitLab API semantics. The same built-in MCP exposes only opaque committed `context_list`/`context_get` handles; it has no provider network or arbitrary identifier path. Direct external MCP remains a separate privileged, comment-only operator boundary.
|
|
296
299
|
|
|
297
300
|
- [Toolkit strategy](docs/engineering/toolkit_strategy.md) - durable product boundaries, architecture, invariants, and non-goals.
|
|
298
|
-
- [Bounded review context](docs/review-context.md) - protected policy,
|
|
301
|
+
- [Bounded review context](docs/review-context.md) - protected policy, GitLab discussions and same-revision CI outcomes, adapter protocol, opaque handles, DLP, receipt, and cleanup contracts.
|
|
299
302
|
- [Roadmap](ROADMAP.md) - milestone status, dependencies, outcomes, and completion signals.
|
|
300
303
|
- [Backlog](docs/codex/TASKS_BACKLOG.md) - inactive implementation-ready work; active execution remains in `PLANS.md`.
|
|
301
304
|
|
|
@@ -303,8 +306,10 @@ The shipped Repository Evidence Engine reads immutable base/head Git objects, st
|
|
|
303
306
|
|
|
304
307
|
1. Configure protected/masked `GITLAB_API_TOKEN` and LLM variables in GitLab.
|
|
305
308
|
2. Pin and checksum the OCR binary.
|
|
306
|
-
3.
|
|
307
|
-
4.
|
|
309
|
+
3. Keep `OCR_GITLAB_TARGET_PROTECTION_MODE=required` unless the project deliberately permits an unprotected target in limited, comment-only mode.
|
|
310
|
+
4. Merge repository review configuration, including the Rules path, into the target branch.
|
|
311
|
+
5. Enable the CI review job in a later merge request and install this package.
|
|
312
|
+
6. Run the four public helper stages around `ocr review`:
|
|
308
313
|
|
|
309
314
|
```console
|
|
310
315
|
ocr-ci preflight
|
|
@@ -315,6 +320,8 @@ ocr-ci post --result /tmp/ocr-result.json --stderr /tmp/ocr-stderr.log
|
|
|
315
320
|
|
|
316
321
|
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).
|
|
317
322
|
|
|
323
|
+
The two-merge-request setup is recommended because repository Rules are loaded only from the captured target commit. On the default protected-target path, a supported one-merge-request alternative may add the Rules and CI job together when that integration merge request does not need review: its first run stops before OCR with a setup-pending note, retrying the same merge request cannot change the captured target Rules, and later merge requests work after it merges. An actually unprotected target with missing target Rules fails closed without promoting the source copy or promising setup-pending status. If the status identity is missing or mismatched, the toolkit likewise uses the generic fail-closed failure note. A green advisory job or pipeline does not prove that OCR or its model executed.
|
|
324
|
+
|
|
318
325
|
## Configuration and safety
|
|
319
326
|
|
|
320
327
|
Configuration is environment-driven. The [configuration reference](docs/configuration.md) documents supported `OCR_*`, `CI_*`, `GITLAB_*`, and MCP inputs. Posting requires `GITLAB_API_TOKEN`; job tokens and legacy aliases are deliberately unsupported.
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# Open Code Review Toolkit
|
|
2
2
|
|
|
3
|
+
[](https://pypi.org/project/open-code-review-toolkit/)
|
|
4
|
+
[](https://pypi.org/project/open-code-review-toolkit/)
|
|
5
|
+
[](https://github.com/xeonvs/open-code-review-toolkit/blob/main/LICENSE)
|
|
3
6
|
[](https://www.bestpractices.dev/projects/13906)
|
|
4
7
|
[](https://securityscorecards.dev/viewer/?uri=github.com/xeonvs/open-code-review-toolkit)
|
|
5
8
|
[](https://github.com/xeonvs/open-code-review-toolkit/actions/workflows/codeql.yml)
|
|
@@ -28,11 +31,11 @@ python -m pip install open-code-review-toolkit
|
|
|
28
31
|
```
|
|
29
32
|
|
|
30
33
|
The toolkit does not bundle OCR. Download the platform asset for exact
|
|
31
|
-
[Open Code Review 1.11.
|
|
34
|
+
[Open Code Review 1.11.3](https://github.com/alibaba/open-code-review/releases/tag/v1.11.3),
|
|
32
35
|
verify it before installation against the [compatibility manifest](compatibility/ocr-support.json),
|
|
33
36
|
and place the verified binary on `PATH`. The current Linux amd64 digest is
|
|
34
|
-
`
|
|
35
|
-
`
|
|
37
|
+
`9726204ac81baee153fd65b1ff357c380f73e9d8091c4a73c3c9fb541b5164cb`; Darwin arm64 is
|
|
38
|
+
`515cd92ce300b62dea2bcaf53e910cdf57b681e1d58bbd0a9e8e49617bca52fa`. The manifest owns
|
|
36
39
|
the other platform hashes.
|
|
37
40
|
|
|
38
41
|
Verify the installation without contacting an LLM provider:
|
|
@@ -42,14 +45,14 @@ ocr --version
|
|
|
42
45
|
ocr-ci --help
|
|
43
46
|
```
|
|
44
47
|
|
|
45
|
-
`ocr --version` must report `open-code-review v1.11.
|
|
48
|
+
`ocr --version` must report `open-code-review v1.11.3`; `ocr-ci --help` must exit
|
|
46
49
|
successfully. `ocr-ci preflight` is an operational configuration check, not the installation
|
|
47
50
|
smoke test.
|
|
48
51
|
|
|
49
52
|
The exact recommended OCR release and its verified asset checksums live in the [versioned compatibility manifest](compatibility/ocr-support.json). CI should pin that release and checksum before execution.
|
|
50
53
|
The [versioned compatibility policy](docs/compatibility.md) records tested assets and evidence and describes the conservative Dependabot-like qualification workflow for later upstream releases.
|
|
51
54
|
Review output defaults to English. `OCR_REVIEW_LANGUAGE` accepts another explicit language name when a project needs localized review output; for example, `OCR_REVIEW_LANGUAGE=Russian`.
|
|
52
|
-
The current OCR 1.11.
|
|
55
|
+
The current OCR 1.11.3 integration defaults `OCR_REVIEW_EFFORT` to `medium` for two review rounds. `low` and `high` are explicit one- and three-round alternatives; see the [configuration reference](docs/configuration.md#review-effort) for cost, budget, and precedence boundaries.
|
|
53
56
|
|
|
54
57
|
Stable distributions are published to [PyPI](https://pypi.org/project/open-code-review-toolkit/) and mirrored as checksum-listed, provenance-attested assets in the corresponding [GitHub Release](https://github.com/xeonvs/open-code-review-toolkit/releases). Development snapshots are published only to TestPyPI.
|
|
55
58
|
|
|
@@ -59,16 +62,16 @@ On a successful rerun, the toolkit replaces untouched OCR-only notes instead of
|
|
|
59
62
|
|
|
60
63
|
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.
|
|
61
64
|
|
|
62
|
-
After every current review note publishes, the GitLab adapter can add a conservative approval bound to receipt
|
|
65
|
+
After every current review note publishes, the GitLab adapter can add a conservative approval bound to receipt v8's exact reviewed source SHA, target SHA/protection state, and merge-request author. 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, and adapter records do not independently block approval, while an unprotected target, degraded metadata, DLP rejection, required context degradation, admitted remediation history, legacy receipts, publication filtering, any direct external MCP, 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.
|
|
63
66
|
|
|
64
67
|
Accepted tradeoffs can be recorded in `.opencodereview/accepted-decisions.md`; the evidence collector supplies only applicable target-ref decisions and never lets a source change self-authorize its review. Root and nested target `AGENTS.md`/`CLAUDE.md` guidance is similarly exposed through the existing evidence MCP with deterministic scope and precedence, while any guidance touched by the merge request is excluded. See [Accepted project decisions](docs/configuration.md#accepted-project-decisions) and [Target project guidance](docs/configuration.md#target-project-guidance) for formats and trust boundaries.
|
|
65
68
|
|
|
66
69
|
## Project architecture
|
|
67
70
|
|
|
68
|
-
The shipped Repository Evidence Engine reads immutable base/head Git objects, stores bounded typed facts and deltas, creates the compact bootstrap used by OCR, and exposes detailed facts, scoped
|
|
71
|
+
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 three fixed tools in the mandatory built-in read-only MCP server. Protected-policy enriched reviews can acquire stable GitLab discussions, verified remediation history, protected same-revision CI outcomes, and authorized external issue/document records before OCR. Forge-specific acquisition and posting stay at provider edges; the broker, DLP, store, MCP, receipts, and tests use common contracts so a future GitHub adapter can reuse them without inheriting GitLab API semantics. The same built-in MCP exposes only opaque committed `context_list`/`context_get` handles; it has no provider network or arbitrary identifier path. Direct external MCP remains a separate privileged, comment-only operator boundary.
|
|
69
72
|
|
|
70
73
|
- [Toolkit strategy](docs/engineering/toolkit_strategy.md) - durable product boundaries, architecture, invariants, and non-goals.
|
|
71
|
-
- [Bounded review context](docs/review-context.md) - protected policy,
|
|
74
|
+
- [Bounded review context](docs/review-context.md) - protected policy, GitLab discussions and same-revision CI outcomes, adapter protocol, opaque handles, DLP, receipt, and cleanup contracts.
|
|
72
75
|
- [Roadmap](ROADMAP.md) - milestone status, dependencies, outcomes, and completion signals.
|
|
73
76
|
- [Backlog](docs/codex/TASKS_BACKLOG.md) - inactive implementation-ready work; active execution remains in `PLANS.md`.
|
|
74
77
|
|
|
@@ -76,8 +79,10 @@ The shipped Repository Evidence Engine reads immutable base/head Git objects, st
|
|
|
76
79
|
|
|
77
80
|
1. Configure protected/masked `GITLAB_API_TOKEN` and LLM variables in GitLab.
|
|
78
81
|
2. Pin and checksum the OCR binary.
|
|
79
|
-
3.
|
|
80
|
-
4.
|
|
82
|
+
3. Keep `OCR_GITLAB_TARGET_PROTECTION_MODE=required` unless the project deliberately permits an unprotected target in limited, comment-only mode.
|
|
83
|
+
4. Merge repository review configuration, including the Rules path, into the target branch.
|
|
84
|
+
5. Enable the CI review job in a later merge request and install this package.
|
|
85
|
+
6. Run the four public helper stages around `ocr review`:
|
|
81
86
|
|
|
82
87
|
```console
|
|
83
88
|
ocr-ci preflight
|
|
@@ -88,6 +93,8 @@ ocr-ci post --result /tmp/ocr-result.json --stderr /tmp/ocr-stderr.log
|
|
|
88
93
|
|
|
89
94
|
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).
|
|
90
95
|
|
|
96
|
+
The two-merge-request setup is recommended because repository Rules are loaded only from the captured target commit. On the default protected-target path, a supported one-merge-request alternative may add the Rules and CI job together when that integration merge request does not need review: its first run stops before OCR with a setup-pending note, retrying the same merge request cannot change the captured target Rules, and later merge requests work after it merges. An actually unprotected target with missing target Rules fails closed without promoting the source copy or promising setup-pending status. If the status identity is missing or mismatched, the toolkit likewise uses the generic fail-closed failure note. A green advisory job or pipeline does not prove that OCR or its model executed.
|
|
97
|
+
|
|
91
98
|
## Configuration and safety
|
|
92
99
|
|
|
93
100
|
Configuration is environment-driven. The [configuration reference](docs/configuration.md) documents supported `OCR_*`, `CI_*`, `GITLAB_*`, and MCP inputs. Posting requires `GITLAB_API_TOKEN`; job tokens and legacy aliases are deliberately unsupported.
|
{open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.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.9.0'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 9, 0)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"""Project provider-neutral same-revision CI outcomes into private context."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import hashlib
|
|
6
|
+
import json
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
|
|
9
|
+
from ocr_toolkit.context.contracts import CIOutcomePolicy, TextBudgets
|
|
10
|
+
from ocr_toolkit.context.dlp import check_text
|
|
11
|
+
from ocr_toolkit.context.store import PendingContextRecord
|
|
12
|
+
|
|
13
|
+
CI_OUTCOME_STATUSES = frozenset({"passed", "failed", "skipped", "canceled", "unknown"})
|
|
14
|
+
CI_OUTCOME_REQUIREMENTS = frozenset({"required", "advisory"})
|
|
15
|
+
CI_OUTCOME_ORIGINS = frozenset({"current_pipeline", "same_revision_pipeline"})
|
|
16
|
+
CI_IDENTITY_BUDGETS = TextBudgets(max_chars=256, max_bytes=1_024, max_lines=1)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass(frozen=True, slots=True)
|
|
20
|
+
class CIOutcome:
|
|
21
|
+
"""Carry one normalized provider outcome without raw forge identities."""
|
|
22
|
+
|
|
23
|
+
check: str
|
|
24
|
+
status: str
|
|
25
|
+
requirement: str
|
|
26
|
+
path_prefixes: tuple[str, ...]
|
|
27
|
+
origin: str
|
|
28
|
+
completed_at: int
|
|
29
|
+
version: str
|
|
30
|
+
digest: str
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass(frozen=True, slots=True)
|
|
34
|
+
class CIOutcomeSnapshot:
|
|
35
|
+
"""Carry a stable provider snapshot and its closed completeness state."""
|
|
36
|
+
|
|
37
|
+
state: str
|
|
38
|
+
records: tuple[CIOutcome, ...]
|
|
39
|
+
omitted: int
|
|
40
|
+
invalid: int
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def prepare_ci_outcome_records(
|
|
44
|
+
snapshot: CIOutcomeSnapshot,
|
|
45
|
+
*,
|
|
46
|
+
policy: CIOutcomePolicy,
|
|
47
|
+
now: int,
|
|
48
|
+
forbidden: tuple[str, ...] = (),
|
|
49
|
+
) -> tuple[PendingContextRecord, ...]:
|
|
50
|
+
"""DLP-check one closed snapshot and build exact immutable store records."""
|
|
51
|
+
|
|
52
|
+
if (
|
|
53
|
+
snapshot.state not in {"complete", "partial", "mutated"}
|
|
54
|
+
or not isinstance(snapshot.omitted, int)
|
|
55
|
+
or isinstance(snapshot.omitted, bool)
|
|
56
|
+
or snapshot.omitted < 0
|
|
57
|
+
or not isinstance(snapshot.invalid, int)
|
|
58
|
+
or isinstance(snapshot.invalid, bool)
|
|
59
|
+
or snapshot.invalid < 0
|
|
60
|
+
or (snapshot.state == "complete" and (snapshot.omitted or snapshot.invalid))
|
|
61
|
+
or (snapshot.state == "mutated" and snapshot.records)
|
|
62
|
+
):
|
|
63
|
+
return ()
|
|
64
|
+
if snapshot.state == "mutated":
|
|
65
|
+
return ()
|
|
66
|
+
authorized = {check.name: check.path_prefixes for check in policy.checks}
|
|
67
|
+
pending: list[PendingContextRecord] = []
|
|
68
|
+
for record in snapshot.records:
|
|
69
|
+
normalized_identity = {
|
|
70
|
+
"check": record.check,
|
|
71
|
+
"status": record.status,
|
|
72
|
+
"requirement": record.requirement,
|
|
73
|
+
"path_prefixes": list(record.path_prefixes),
|
|
74
|
+
"origin": record.origin,
|
|
75
|
+
"completed_at": record.completed_at,
|
|
76
|
+
}
|
|
77
|
+
expected_digest = hashlib.sha256(
|
|
78
|
+
json.dumps(normalized_identity, sort_keys=True, separators=(",", ":")).encode()
|
|
79
|
+
).hexdigest()
|
|
80
|
+
if (
|
|
81
|
+
record.check not in authorized
|
|
82
|
+
or record.path_prefixes != authorized[record.check]
|
|
83
|
+
or record.status not in CI_OUTCOME_STATUSES
|
|
84
|
+
or record.requirement not in CI_OUTCOME_REQUIREMENTS
|
|
85
|
+
or record.origin not in CI_OUTCOME_ORIGINS
|
|
86
|
+
or not isinstance(record.completed_at, int)
|
|
87
|
+
or isinstance(record.completed_at, bool)
|
|
88
|
+
or record.completed_at < 0
|
|
89
|
+
or record.completed_at > now + 300
|
|
90
|
+
or now - record.completed_at > policy.max_age_seconds
|
|
91
|
+
or record.version != str(record.completed_at)
|
|
92
|
+
or len(record.digest) != 64
|
|
93
|
+
or any(character not in "0123456789abcdef" for character in record.digest)
|
|
94
|
+
or record.digest != expected_digest
|
|
95
|
+
):
|
|
96
|
+
continue
|
|
97
|
+
values = (record.check, *record.path_prefixes)
|
|
98
|
+
checked = [
|
|
99
|
+
check_text(value, budgets=CI_IDENTITY_BUDGETS, forbidden=forbidden) for value in values
|
|
100
|
+
]
|
|
101
|
+
if any(
|
|
102
|
+
not value.admitted or value.text != original for value, original in zip(checked, values)
|
|
103
|
+
):
|
|
104
|
+
continue
|
|
105
|
+
model = {
|
|
106
|
+
"check": record.check,
|
|
107
|
+
"revision": "reviewed_head",
|
|
108
|
+
"status": record.status,
|
|
109
|
+
"requirement": record.requirement,
|
|
110
|
+
"scope": {"mode": "declared", "path_prefixes": list(record.path_prefixes)},
|
|
111
|
+
"origin": record.origin,
|
|
112
|
+
"completed_at": record.completed_at,
|
|
113
|
+
}
|
|
114
|
+
canonical = json.dumps(model, sort_keys=True, separators=(",", ":"))
|
|
115
|
+
pending.append(
|
|
116
|
+
PendingContextRecord(
|
|
117
|
+
source="forge:ci_outcomes",
|
|
118
|
+
adapter="gitlab",
|
|
119
|
+
tenant="project",
|
|
120
|
+
canonical_object=hashlib.sha256(
|
|
121
|
+
f"ci-outcome:{record.check}:{record.digest}".encode()
|
|
122
|
+
).hexdigest(),
|
|
123
|
+
resource_class="ci_outcome",
|
|
124
|
+
descriptor="ci_outcome",
|
|
125
|
+
projections={
|
|
126
|
+
"model": {"descriptor": "ci_outcome", "ci_outcome": model},
|
|
127
|
+
"publish": {"descriptor": "ci_outcome"},
|
|
128
|
+
"retain": {
|
|
129
|
+
"state": record.status,
|
|
130
|
+
"digest": hashlib.sha256(canonical.encode()).hexdigest(),
|
|
131
|
+
"version": record.version,
|
|
132
|
+
"expiry": now + 3_600,
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
version=record.version,
|
|
136
|
+
digest=hashlib.sha256(canonical.encode()).hexdigest(),
|
|
137
|
+
mutable=False,
|
|
138
|
+
expiry=now + 3_600,
|
|
139
|
+
)
|
|
140
|
+
)
|
|
141
|
+
return tuple(pending)
|
|
@@ -6,14 +6,17 @@ from dataclasses import dataclass
|
|
|
6
6
|
|
|
7
7
|
POLICY_SCHEMA_V1 = "ocr.review-context-policy/v1"
|
|
8
8
|
POLICY_SCHEMA_V2 = "ocr.review-context-policy/v2"
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
POLICY_SCHEMA_V3 = "ocr.review-context-policy/v3"
|
|
10
|
+
POLICY_SCHEMAS = frozenset({POLICY_SCHEMA_V1, POLICY_SCHEMA_V2, POLICY_SCHEMA_V3})
|
|
11
|
+
POLICY_SCHEMA = POLICY_SCHEMA_V3
|
|
11
12
|
STORE_SCHEMA = "ocr.context-store/v2"
|
|
12
13
|
REQUEST_SCHEMA = "ocr.context-adapter-request/v1"
|
|
13
14
|
RESPONSE_SCHEMA = "ocr.context-adapter-response/v1"
|
|
14
15
|
ACCOUNT_CLASSES = frozenset({"user", "automation", "system", "toolkit_bot"})
|
|
15
16
|
REFERENCE_RESOURCE_CLASSES = frozenset({"issue", "document"})
|
|
16
|
-
STORE_RESOURCE_CLASSES = frozenset(
|
|
17
|
+
STORE_RESOURCE_CLASSES = frozenset(
|
|
18
|
+
{*REFERENCE_RESOURCE_CLASSES, "remediation_thread", "ci_outcome"}
|
|
19
|
+
)
|
|
17
20
|
PROJECTION_FIELDS = frozenset(
|
|
18
21
|
{
|
|
19
22
|
"descriptor",
|
|
@@ -33,7 +36,10 @@ PROJECTION_FIELDS = frozenset(
|
|
|
33
36
|
}
|
|
34
37
|
)
|
|
35
38
|
REMEDIATION_MODEL_FIELD = "remediation_thread"
|
|
36
|
-
|
|
39
|
+
CI_OUTCOME_MODEL_FIELD = "ci_outcome"
|
|
40
|
+
STORE_PROJECTION_FIELDS = frozenset(
|
|
41
|
+
{*PROJECTION_FIELDS, REMEDIATION_MODEL_FIELD, CI_OUTCOME_MODEL_FIELD}
|
|
42
|
+
)
|
|
37
43
|
RETENTION_FIELDS = frozenset({"state", "count", "digest", "version", "expiry"})
|
|
38
44
|
|
|
39
45
|
|
|
@@ -99,6 +105,23 @@ class RemediationThreadPolicy:
|
|
|
99
105
|
budgets: TextBudgets
|
|
100
106
|
|
|
101
107
|
|
|
108
|
+
@dataclass(frozen=True, slots=True)
|
|
109
|
+
class CIOutcomeCheckPolicy:
|
|
110
|
+
"""Authorize one exact forge check and its protected path scope."""
|
|
111
|
+
|
|
112
|
+
name: str
|
|
113
|
+
path_prefixes: tuple[str, ...]
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
@dataclass(frozen=True, slots=True)
|
|
117
|
+
class CIOutcomePolicy:
|
|
118
|
+
"""Select bounded same-revision forge outcomes under protected policy."""
|
|
119
|
+
|
|
120
|
+
required: bool
|
|
121
|
+
max_age_seconds: int
|
|
122
|
+
checks: tuple[CIOutcomeCheckPolicy, ...]
|
|
123
|
+
|
|
124
|
+
|
|
102
125
|
@dataclass(frozen=True, slots=True)
|
|
103
126
|
class RecognizerPolicy:
|
|
104
127
|
"""Hold one fixed toolkit-authored candidate grammar."""
|
|
@@ -132,5 +155,6 @@ class ContextPolicy:
|
|
|
132
155
|
budgets: AggregateBudgets
|
|
133
156
|
forge_discussions: DiscussionPolicy | None
|
|
134
157
|
remediation_threads: RemediationThreadPolicy | None
|
|
158
|
+
ci_outcomes: CIOutcomePolicy | None
|
|
135
159
|
references: tuple[ReferencePolicy, ...]
|
|
136
160
|
digest: str
|
{open_code_review_toolkit-0.8.6 → open_code_review_toolkit-0.9.0}/src/ocr_toolkit/context/policy.py
RENAMED
|
@@ -5,18 +5,23 @@ from __future__ import annotations
|
|
|
5
5
|
import hashlib
|
|
6
6
|
import json
|
|
7
7
|
import re
|
|
8
|
+
import unicodedata
|
|
8
9
|
from collections.abc import Callable, Mapping
|
|
10
|
+
from pathlib import PurePosixPath
|
|
9
11
|
from typing import Any
|
|
10
12
|
from urllib.parse import urlsplit
|
|
11
13
|
|
|
12
14
|
from ocr_toolkit.context.contracts import (
|
|
13
15
|
ACCOUNT_CLASSES,
|
|
14
16
|
POLICY_SCHEMA_V1,
|
|
17
|
+
POLICY_SCHEMA_V2,
|
|
15
18
|
POLICY_SCHEMAS,
|
|
16
19
|
PROJECTION_FIELDS,
|
|
17
20
|
REFERENCE_RESOURCE_CLASSES,
|
|
18
21
|
RETENTION_FIELDS,
|
|
19
22
|
AggregateBudgets,
|
|
23
|
+
CIOutcomeCheckPolicy,
|
|
24
|
+
CIOutcomePolicy,
|
|
20
25
|
ContextContractError,
|
|
21
26
|
ContextPolicy,
|
|
22
27
|
ContextProjections,
|
|
@@ -32,6 +37,7 @@ MAX_POLICY_BYTES = 64 * 1024
|
|
|
32
37
|
MAX_REFERENCES = 16
|
|
33
38
|
NAME_RE = re.compile(r"[a-z][a-z0-9_-]{0,63}\Z")
|
|
34
39
|
ISSUE_PREFIX_RE = re.compile(r"[A-Z][A-Z0-9]{0,15}\Z")
|
|
40
|
+
MAX_CI_CHECKS = 32
|
|
35
41
|
|
|
36
42
|
|
|
37
43
|
def _pairs(pairs: list[tuple[str, Any]]) -> dict[str, Any]:
|
|
@@ -208,6 +214,79 @@ def _remediation_threads(value: object) -> RemediationThreadPolicy:
|
|
|
208
214
|
)
|
|
209
215
|
|
|
210
216
|
|
|
217
|
+
def normalize_ci_path_prefix(value: object) -> str:
|
|
218
|
+
"""Validate one protected repository-relative POSIX path prefix."""
|
|
219
|
+
|
|
220
|
+
if (
|
|
221
|
+
not isinstance(value, str)
|
|
222
|
+
or not 1 <= len(value) <= 256
|
|
223
|
+
or "\\" in value
|
|
224
|
+
or "://" in value
|
|
225
|
+
or any(unicodedata.category(character) in {"Cc", "Cf", "Cs"} for character in value)
|
|
226
|
+
or len(value.encode("utf-8")) > 1_024
|
|
227
|
+
):
|
|
228
|
+
raise ContextContractError("ci_outcomes path prefix is invalid")
|
|
229
|
+
trailing = value.endswith("/")
|
|
230
|
+
path = PurePosixPath(value)
|
|
231
|
+
parts = value.rstrip("/").split("/")
|
|
232
|
+
if path.is_absolute() or any(part in {"", ".", ".."} for part in parts):
|
|
233
|
+
raise ContextContractError("ci_outcomes path prefix is invalid")
|
|
234
|
+
normalized = path.as_posix()
|
|
235
|
+
return f"{normalized}/" if trailing else normalized
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def _ci_outcomes(value: object) -> CIOutcomePolicy:
|
|
239
|
+
"""Parse exact check names and path scopes from protected policy v3."""
|
|
240
|
+
|
|
241
|
+
item = _object(
|
|
242
|
+
value,
|
|
243
|
+
keys=frozenset({"required", "max_age_seconds", "checks"}),
|
|
244
|
+
label="ci_outcomes",
|
|
245
|
+
)
|
|
246
|
+
checks = item.get("checks")
|
|
247
|
+
if not isinstance(checks, list) or not checks or len(checks) > MAX_CI_CHECKS:
|
|
248
|
+
raise ContextContractError("ci_outcomes.checks is invalid")
|
|
249
|
+
parsed: list[CIOutcomeCheckPolicy] = []
|
|
250
|
+
for raw in checks:
|
|
251
|
+
check = _object(
|
|
252
|
+
raw,
|
|
253
|
+
keys=frozenset({"name", "path_prefixes"}),
|
|
254
|
+
label="ci_outcomes check",
|
|
255
|
+
)
|
|
256
|
+
name = check.get("name")
|
|
257
|
+
prefixes = check.get("path_prefixes")
|
|
258
|
+
if (
|
|
259
|
+
not isinstance(name, str)
|
|
260
|
+
or not 1 <= len(name) <= 128
|
|
261
|
+
or "://" in name
|
|
262
|
+
or any(unicodedata.category(character) in {"Cc", "Cf", "Cs"} for character in name)
|
|
263
|
+
or len(name.encode("utf-8")) > 512
|
|
264
|
+
or not isinstance(prefixes, list)
|
|
265
|
+
or not prefixes
|
|
266
|
+
or len(prefixes) > 32
|
|
267
|
+
):
|
|
268
|
+
raise ContextContractError("ci_outcomes check is invalid")
|
|
269
|
+
normalized = tuple(normalize_ci_path_prefix(prefix) for prefix in prefixes)
|
|
270
|
+
if list(normalized) != sorted(set(normalized)):
|
|
271
|
+
raise ContextContractError("ci_outcomes path prefixes must be sorted and unique")
|
|
272
|
+
parsed.append(CIOutcomeCheckPolicy(name=name, path_prefixes=normalized))
|
|
273
|
+
names = [check.name for check in parsed]
|
|
274
|
+
if names != sorted(set(names)):
|
|
275
|
+
raise ContextContractError("ci_outcomes check names must be sorted and unique")
|
|
276
|
+
required = item.get("required", False)
|
|
277
|
+
max_age_seconds = item.get("max_age_seconds", 86_400)
|
|
278
|
+
return CIOutcomePolicy(
|
|
279
|
+
required=_boolean(required, label="ci_outcomes.required"),
|
|
280
|
+
max_age_seconds=_integer(
|
|
281
|
+
max_age_seconds,
|
|
282
|
+
minimum=60,
|
|
283
|
+
maximum=604_800,
|
|
284
|
+
label="ci_outcomes.max_age_seconds",
|
|
285
|
+
),
|
|
286
|
+
checks=tuple(parsed),
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
|
|
211
290
|
def _recognizer(value: object, *, resource_class: str) -> RecognizerPolicy:
|
|
212
291
|
item = _object(
|
|
213
292
|
value,
|
|
@@ -328,6 +407,7 @@ def parse_policy(raw: bytes) -> ContextPolicy:
|
|
|
328
407
|
"budgets",
|
|
329
408
|
"forge_discussions",
|
|
330
409
|
"remediation_threads",
|
|
410
|
+
"ci_outcomes",
|
|
331
411
|
"references",
|
|
332
412
|
}
|
|
333
413
|
),
|
|
@@ -338,6 +418,8 @@ def parse_policy(raw: bytes) -> ContextPolicy:
|
|
|
338
418
|
raise ContextContractError("context policy schema version is unsupported")
|
|
339
419
|
if schema_version == POLICY_SCHEMA_V1 and "remediation_threads" in root:
|
|
340
420
|
raise ContextContractError("context policy v1 cannot select remediation threads")
|
|
421
|
+
if schema_version in {POLICY_SCHEMA_V1, POLICY_SCHEMA_V2} and "ci_outcomes" in root:
|
|
422
|
+
raise ContextContractError("context policy v1/v2 cannot select CI outcomes")
|
|
341
423
|
budgets_value = _object(
|
|
342
424
|
root.get("budgets"),
|
|
343
425
|
keys=frozenset({"max_records", "max_chars", "max_bytes", "max_lines", "timeout_ms"}),
|
|
@@ -364,6 +446,7 @@ def parse_policy(raw: bytes) -> ContextPolicy:
|
|
|
364
446
|
remediation = (
|
|
365
447
|
_remediation_threads(root["remediation_threads"]) if "remediation_threads" in root else None
|
|
366
448
|
)
|
|
449
|
+
ci_outcomes = _ci_outcomes(root["ci_outcomes"]) if "ci_outcomes" in root else None
|
|
367
450
|
references_value = root.get("references", [])
|
|
368
451
|
if (
|
|
369
452
|
not isinstance(references_value, list)
|
|
@@ -375,7 +458,7 @@ def parse_policy(raw: bytes) -> ContextPolicy:
|
|
|
375
458
|
identities = [(item.adapter, item.tenant, item.resource_class) for item in references]
|
|
376
459
|
if len(identities) != len(set(identities)):
|
|
377
460
|
raise ContextContractError("context policy references collide")
|
|
378
|
-
if discussion is None and remediation is None and not references:
|
|
461
|
+
if discussion is None and remediation is None and ci_outcomes is None and not references:
|
|
379
462
|
raise ContextContractError("context policy must select at least one source")
|
|
380
463
|
canonical = json.dumps(
|
|
381
464
|
payload, sort_keys=True, separators=(",", ":"), ensure_ascii=False
|
|
@@ -385,6 +468,7 @@ def parse_policy(raw: bytes) -> ContextPolicy:
|
|
|
385
468
|
budgets=budgets,
|
|
386
469
|
forge_discussions=discussion,
|
|
387
470
|
remediation_threads=remediation,
|
|
471
|
+
ci_outcomes=ci_outcomes,
|
|
388
472
|
references=references,
|
|
389
473
|
digest=hashlib.sha256(canonical).hexdigest(),
|
|
390
474
|
)
|