open-code-review-toolkit 0.7.0__tar.gz → 0.8.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.7.0 → open_code_review_toolkit-0.8.0}/PKG-INFO +6 -5
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/README.md +5 -4
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/pyproject.toml +6 -1
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/_version.py +2 -2
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/cli.py +11 -1
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/configure.py +7 -13
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/context/adapters.py +7 -3
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/context/broker.py +235 -11
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/context/contracts.py +25 -3
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/context/dlp.py +40 -32
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/context/mcp.py +8 -2
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/context/policy.py +80 -8
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/context/store.py +139 -5
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/evidence/__main__.py +3 -1
- open_code_review_toolkit-0.8.0/src/ocr_toolkit/evidence/actions.py +124 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/evidence/artifacts.py +6 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/evidence/mcp.py +20 -2
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/evidence/project.py +8 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/evidence/review_context.py +21 -2
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/mcp_config.py +1 -1
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/ocr_result.py +2 -2
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/posting/approval.py +66 -21
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/posting/formatting.py +136 -144
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/posting/gitlab.py +21 -16
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/posting/markers.py +18 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/posting/snapshot.py +4 -4
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/posting/transaction.py +1 -1
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/posting/workflow.py +32 -8
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/pre_execution.py +40 -9
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/preflight.py +1 -1
- open_code_review_toolkit-0.8.0/src/ocr_toolkit/providers/gitlab_context.py +83 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/providers/gitlab_discussions.py +192 -101
- open_code_review_toolkit-0.8.0/src/ocr_toolkit/providers/gitlab_identity.py +54 -0
- open_code_review_toolkit-0.8.0/src/ocr_toolkit/providers/gitlab_remediation.py +331 -0
- open_code_review_toolkit-0.8.0/src/ocr_toolkit/result_usage.py +216 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/review_runner.py +740 -84
- open_code_review_toolkit-0.7.0/src/ocr_toolkit/posting/gitlab_identity.py +0 -14
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/.gitignore +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/LICENSE +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/__init__.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/common/__init__.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/common/filesystem.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/common/git.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/common/language.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/common/markdown.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/common/redaction.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/config_writer.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/context/__init__.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/context/recognizers.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/evidence/__init__.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/evidence/categorize.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/evidence/collect.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/evidence/collectors/__init__.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/evidence/collectors/graphs.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/evidence/collectors/orchestration.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/evidence/collectors/projections.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/evidence/collectors/registry.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/evidence/collectors/sources.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/evidence/coverage.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/evidence/ecosystems/__init__.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/evidence/ecosystems/ansible/__init__.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/evidence/ecosystems/ansible/requirements.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/evidence/ecosystems/ansible/topology.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/evidence/ecosystems/contracts.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/evidence/ecosystems/go.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/evidence/ecosystems/javascript.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/evidence/ecosystems/php.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/evidence/ecosystems/python.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/evidence/frameworks/__init__.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/evidence/frameworks/contracts.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/evidence/frameworks/detection.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/evidence/frameworks/providers/__init__.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/evidence/frameworks/providers/frontend.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/evidence/frameworks/providers/go.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/evidence/frameworks/providers/php.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/evidence/frameworks/providers/python.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/evidence/frameworks/registry.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/evidence/frameworks/schema.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/evidence/frameworks/templates.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/evidence/infrastructure.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/evidence/invocation.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/evidence/model.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/evidence/policy/__init__.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/evidence/policy/contracts.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/evidence/policy/decisions.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/evidence/policy/guidance.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/evidence/policy/registry.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/evidence/policy/schema.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/evidence/policy/scopes.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/evidence/repository.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/evidence/store/__init__.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/evidence/store/atomic.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/evidence/store/contracts.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/evidence/store/core.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/evidence/store/readback.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/evidence/store/values.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/posting/__init__.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/posting/__main__.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/posting/comments.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/posting/gitlab_approval.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/posting/payloads.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/posting/reconciliation.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/posting/result.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/posting/settings.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/posting/suggestions.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/providers/__init__.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/providers/gitlab.py +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/py.typed +0 -0
- {open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/result_contract.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: open-code-review-toolkit
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.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
|
|
@@ -254,17 +254,17 @@ Stable distributions are published to [PyPI](https://pypi.org/project/open-code-
|
|
|
254
254
|
|
|
255
255
|
## How reviews evolve
|
|
256
256
|
|
|
257
|
-
On a successful rerun, the toolkit replaces untouched OCR-only notes instead of accumulating stale reviews. A human reply transfers that discussion to the team: the conversation is preserved and a matching finding is suppressed. Reply with `/ocr suppress` to keep a discussion open without future repeats
|
|
257
|
+
On a successful rerun, the toolkit replaces untouched OCR-only notes instead of accumulating stale reviews. A human reply transfers that discussion to the team: the conversation is preserved and a matching finding is suppressed. Reply with `/ocr suppress` or `@<live-bot-username> suppress` to keep a discussion open without future repeats; use the corresponding `resolve` command to resolve it after the next successful posting transaction. For example, a bot named `mr.bot` accepts the exact reply `@mr.bot resolve`.
|
|
258
258
|
|
|
259
259
|
Suppression uses both the GitLab diff position and a stable finding fingerprint, so ordinary line shifts do not normally bring the same bug back. A materially changed finding can still receive a new discussion. See [GitLab review operations](docs/operations.md) for the complete lifecycle, posting modes, permissions, failure behavior, and Mermaid state diagram.
|
|
260
260
|
|
|
261
|
-
After every current review note publishes, the GitLab adapter can add a conservative approval bound to receipt
|
|
261
|
+
After every current review note publishes, the GitLab adapter can add a conservative approval bound to receipt v5's exact reviewed source SHA and merge-request author. This write is enabled by default; set `OCR_AUTO_APPROVE=false` when the bot must remain comment-only. DLP-clean metadata, generic discussions, and adapter records do not independently block approval, while 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. GitLab approval rules and protected-branch policy remain authoritative. The toolkit only adds an eligible approval; it never removes an existing approval when a later review is ineligible or disabled.
|
|
262
262
|
|
|
263
263
|
Accepted tradeoffs can be recorded in `.opencodereview/accepted-decisions.md`; the evidence collector supplies only applicable target-ref decisions and never lets a source change self-authorize its review. Root and nested target `AGENTS.md`/`CLAUDE.md` guidance is similarly exposed through the existing evidence MCP with deterministic scope and precedence, while any guidance touched by the merge request is excluded. See [Accepted project decisions](docs/configuration.md#accepted-project-decisions) and [Target project guidance](docs/configuration.md#target-project-guidance) for formats and trust boundaries.
|
|
264
264
|
|
|
265
265
|
## Project architecture
|
|
266
266
|
|
|
267
|
-
The shipped Repository Evidence Engine reads immutable base/head Git objects, stores bounded typed facts and deltas, creates the compact bootstrap used by OCR, and exposes detailed facts, scoped completeness, and base/head changes through the mandatory built-in read-only MCP server.
|
|
267
|
+
The shipped Repository Evidence Engine reads immutable base/head Git objects, stores bounded typed facts and deltas, creates the compact bootstrap used by OCR, and exposes detailed facts, scoped completeness, and base/head changes through the mandatory built-in read-only MCP server. Protected-policy enriched reviews can acquire stable GitLab discussions, verified remediation history, 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.
|
|
268
268
|
|
|
269
269
|
- [Toolkit strategy](docs/engineering/toolkit_strategy.md) - durable product boundaries, architecture, invariants, and non-goals.
|
|
270
270
|
- [Bounded review context](docs/review-context.md) - protected policy, adapter protocol, GitLab discussions, opaque handles, DLP, receipt, and cleanup contracts.
|
|
@@ -285,7 +285,7 @@ ocr-ci review --result /tmp/ocr-result.json --stderr /tmp/ocr-stderr.log -- ...
|
|
|
285
285
|
ocr-ci post --result /tmp/ocr-result.json --stderr /tmp/ocr-stderr.log
|
|
286
286
|
```
|
|
287
287
|
|
|
288
|
-
See the [
|
|
288
|
+
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).
|
|
289
289
|
|
|
290
290
|
## Configuration and safety
|
|
291
291
|
|
|
@@ -295,6 +295,7 @@ Repository content, OCR output, and provider responses are untrusted inputs. The
|
|
|
295
295
|
|
|
296
296
|
## Development and release
|
|
297
297
|
|
|
298
|
+
- [Documentation index](docs/README.md)
|
|
298
299
|
- [Contributing](CONTRIBUTING.md)
|
|
299
300
|
- [Code of Conduct](CODE_OF_CONDUCT.md)
|
|
300
301
|
- [Development guide](docs/development.md)
|
|
@@ -27,17 +27,17 @@ Stable distributions are published to [PyPI](https://pypi.org/project/open-code-
|
|
|
27
27
|
|
|
28
28
|
## How reviews evolve
|
|
29
29
|
|
|
30
|
-
On a successful rerun, the toolkit replaces untouched OCR-only notes instead of accumulating stale reviews. A human reply transfers that discussion to the team: the conversation is preserved and a matching finding is suppressed. Reply with `/ocr suppress` to keep a discussion open without future repeats
|
|
30
|
+
On a successful rerun, the toolkit replaces untouched OCR-only notes instead of accumulating stale reviews. A human reply transfers that discussion to the team: the conversation is preserved and a matching finding is suppressed. Reply with `/ocr suppress` or `@<live-bot-username> suppress` to keep a discussion open without future repeats; use the corresponding `resolve` command to resolve it after the next successful posting transaction. For example, a bot named `mr.bot` accepts the exact reply `@mr.bot resolve`.
|
|
31
31
|
|
|
32
32
|
Suppression uses both the GitLab diff position and a stable finding fingerprint, so ordinary line shifts do not normally bring the same bug back. A materially changed finding can still receive a new discussion. See [GitLab review operations](docs/operations.md) for the complete lifecycle, posting modes, permissions, failure behavior, and Mermaid state diagram.
|
|
33
33
|
|
|
34
|
-
After every current review note publishes, the GitLab adapter can add a conservative approval bound to receipt
|
|
34
|
+
After every current review note publishes, the GitLab adapter can add a conservative approval bound to receipt v5's exact reviewed source SHA and merge-request author. This write is enabled by default; set `OCR_AUTO_APPROVE=false` when the bot must remain comment-only. DLP-clean metadata, generic discussions, and adapter records do not independently block approval, while 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. GitLab approval rules and protected-branch policy remain authoritative. The toolkit only adds an eligible approval; it never removes an existing approval when a later review is ineligible or disabled.
|
|
35
35
|
|
|
36
36
|
Accepted tradeoffs can be recorded in `.opencodereview/accepted-decisions.md`; the evidence collector supplies only applicable target-ref decisions and never lets a source change self-authorize its review. Root and nested target `AGENTS.md`/`CLAUDE.md` guidance is similarly exposed through the existing evidence MCP with deterministic scope and precedence, while any guidance touched by the merge request is excluded. See [Accepted project decisions](docs/configuration.md#accepted-project-decisions) and [Target project guidance](docs/configuration.md#target-project-guidance) for formats and trust boundaries.
|
|
37
37
|
|
|
38
38
|
## Project architecture
|
|
39
39
|
|
|
40
|
-
The shipped Repository Evidence Engine reads immutable base/head Git objects, stores bounded typed facts and deltas, creates the compact bootstrap used by OCR, and exposes detailed facts, scoped completeness, and base/head changes through the mandatory built-in read-only MCP server.
|
|
40
|
+
The shipped Repository Evidence Engine reads immutable base/head Git objects, stores bounded typed facts and deltas, creates the compact bootstrap used by OCR, and exposes detailed facts, scoped completeness, and base/head changes through the mandatory built-in read-only MCP server. Protected-policy enriched reviews can acquire stable GitLab discussions, verified remediation history, 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.
|
|
41
41
|
|
|
42
42
|
- [Toolkit strategy](docs/engineering/toolkit_strategy.md) - durable product boundaries, architecture, invariants, and non-goals.
|
|
43
43
|
- [Bounded review context](docs/review-context.md) - protected policy, adapter protocol, GitLab discussions, opaque handles, DLP, receipt, and cleanup contracts.
|
|
@@ -58,7 +58,7 @@ ocr-ci review --result /tmp/ocr-result.json --stderr /tmp/ocr-stderr.log -- ...
|
|
|
58
58
|
ocr-ci post --result /tmp/ocr-result.json --stderr /tmp/ocr-stderr.log
|
|
59
59
|
```
|
|
60
60
|
|
|
61
|
-
See the [
|
|
61
|
+
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).
|
|
62
62
|
|
|
63
63
|
## Configuration and safety
|
|
64
64
|
|
|
@@ -68,6 +68,7 @@ Repository content, OCR output, and provider responses are untrusted inputs. The
|
|
|
68
68
|
|
|
69
69
|
## Development and release
|
|
70
70
|
|
|
71
|
+
- [Documentation index](docs/README.md)
|
|
71
72
|
- [Contributing](CONTRIBUTING.md)
|
|
72
73
|
- [Code of Conduct](CODE_OF_CONDUCT.md)
|
|
73
74
|
- [Development guide](docs/development.md)
|
|
@@ -106,7 +106,7 @@ source = ["ocr_toolkit"]
|
|
|
106
106
|
omit = ["src/ocr_toolkit/_version.py"]
|
|
107
107
|
|
|
108
108
|
[tool.coverage.report]
|
|
109
|
-
fail_under =
|
|
109
|
+
fail_under = 85
|
|
110
110
|
show_missing = true
|
|
111
111
|
skip_covered = true
|
|
112
112
|
exclude_also = [
|
|
@@ -133,6 +133,11 @@ directory = "bugfix"
|
|
|
133
133
|
name = "🐛 Bug Fixes"
|
|
134
134
|
showcontent = true
|
|
135
135
|
|
|
136
|
+
[[tool.towncrier.type]]
|
|
137
|
+
directory = "maintenance"
|
|
138
|
+
name = "🛠 Maintenance"
|
|
139
|
+
showcontent = true
|
|
140
|
+
|
|
136
141
|
[[tool.towncrier.type]]
|
|
137
142
|
directory = "refactor"
|
|
138
143
|
name = "🔧 Refactoring"
|
{open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.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.8.0'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 8, 0)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -32,6 +32,11 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
32
32
|
)
|
|
33
33
|
review_parser.add_argument("--result", required=True, help="OCR JSON output path.")
|
|
34
34
|
review_parser.add_argument("--stderr", required=True, help="Full OCR stderr artifact path.")
|
|
35
|
+
review_parser.add_argument(
|
|
36
|
+
"--preserve-private-artifacts",
|
|
37
|
+
action="store_true",
|
|
38
|
+
help="Retain sensitive OCR session artifacts after a local diagnostic review.",
|
|
39
|
+
)
|
|
35
40
|
review_parser.add_argument(
|
|
36
41
|
"ocr_args", nargs=argparse.REMAINDER, help="OCR review arguments after --."
|
|
37
42
|
)
|
|
@@ -65,7 +70,12 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|
|
65
70
|
if args.command == "review":
|
|
66
71
|
ocr_args = args.ocr_args[1:] if args.ocr_args[:1] == ["--"] else args.ocr_args
|
|
67
72
|
try:
|
|
68
|
-
return review_runner.run_evidence_review(
|
|
73
|
+
return review_runner.run_evidence_review(
|
|
74
|
+
Path(args.result),
|
|
75
|
+
Path(args.stderr),
|
|
76
|
+
ocr_args,
|
|
77
|
+
preserve_private_artifacts=args.preserve_private_artifacts,
|
|
78
|
+
)
|
|
69
79
|
except review_runner.ReviewRunnerError as exc:
|
|
70
80
|
print(f"Cannot run Open Code Review: {exc}", file=sys.stderr)
|
|
71
81
|
return 2
|
{open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/configure.py
RENAMED
|
@@ -74,22 +74,16 @@ def _parse_extra_body(value: str) -> Any:
|
|
|
74
74
|
|
|
75
75
|
|
|
76
76
|
def _llm_protocol() -> str:
|
|
77
|
-
"""Resolve and validate the OCR
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
protocol = "anthropic" if legacy_mode == "true" else "openai"
|
|
77
|
+
"""Resolve and validate the explicit OCR LLM wire protocol."""
|
|
78
|
+
|
|
79
|
+
if "OCR_USE_ANTHROPIC" in os.environ:
|
|
80
|
+
raise OCRRuntimeConfigError(
|
|
81
|
+
"OCR_USE_ANTHROPIC was removed; set OCR_LLM_PROTOCOL=anthropic explicitly"
|
|
82
|
+
)
|
|
83
|
+
protocol = _env("OCR_LLM_PROTOCOL", "openai") or "openai"
|
|
85
84
|
if protocol not in LLM_PROTOCOLS:
|
|
86
85
|
allowed = ", ".join(sorted(LLM_PROTOCOLS))
|
|
87
86
|
raise OCRRuntimeConfigError(f"OCR_LLM_PROTOCOL must be one of: {allowed}")
|
|
88
|
-
|
|
89
|
-
if legacy_mode == "true" and protocol != "anthropic":
|
|
90
|
-
raise OCRRuntimeConfigError("OCR_LLM_PROTOCOL conflicts with OCR_USE_ANTHROPIC")
|
|
91
|
-
if legacy_mode == "false" and protocol == "anthropic":
|
|
92
|
-
raise OCRRuntimeConfigError("OCR_LLM_PROTOCOL conflicts with OCR_USE_ANTHROPIC")
|
|
93
87
|
return protocol
|
|
94
88
|
|
|
95
89
|
|
|
@@ -19,7 +19,11 @@ from pathlib import Path
|
|
|
19
19
|
from typing import Any
|
|
20
20
|
|
|
21
21
|
from ocr_toolkit.common.redaction import redact_sensitive
|
|
22
|
-
from ocr_toolkit.context.contracts import
|
|
22
|
+
from ocr_toolkit.context.contracts import (
|
|
23
|
+
REFERENCE_RESOURCE_CLASSES,
|
|
24
|
+
REQUEST_SCHEMA,
|
|
25
|
+
RESPONSE_SCHEMA,
|
|
26
|
+
)
|
|
23
27
|
|
|
24
28
|
MAX_ADAPTER_CONFIG_BYTES = 64 * 1024
|
|
25
29
|
MAX_ADAPTERS = 16
|
|
@@ -197,7 +201,7 @@ def parse_adapter_config(raw: str | None) -> tuple[AdapterConfig, ...]:
|
|
|
197
201
|
resource_classes = _names(
|
|
198
202
|
item.get("resource_classes"),
|
|
199
203
|
label="adapter resource classes",
|
|
200
|
-
allowed=
|
|
204
|
+
allowed=REFERENCE_RESOURCE_CLASSES,
|
|
201
205
|
)
|
|
202
206
|
if kind == "stdio":
|
|
203
207
|
if set(item).difference(common | {"command", "args", "env_from"}):
|
|
@@ -590,7 +594,7 @@ def authorize_and_resolve(
|
|
|
590
594
|
or request.adapter != config.name
|
|
591
595
|
or request.tenant not in config.tenants
|
|
592
596
|
or request.resource_class not in config.resource_classes
|
|
593
|
-
or request.resource_class not in
|
|
597
|
+
or request.resource_class not in REFERENCE_RESOURCE_CLASSES
|
|
594
598
|
or request.requested_fields != tuple(sorted(set(request.requested_fields)))
|
|
595
599
|
or not 1 <= request.max_chars <= 100_000
|
|
596
600
|
or not 1 <= request.max_bytes <= 400_000
|
{open_code_review_toolkit-0.7.0 → open_code_review_toolkit-0.8.0}/src/ocr_toolkit/context/broker.py
RENAMED
|
@@ -21,8 +21,10 @@ from ocr_toolkit.context.adapters import (
|
|
|
21
21
|
from ocr_toolkit.context.contracts import (
|
|
22
22
|
ACCOUNT_CLASSES,
|
|
23
23
|
ContextPolicy,
|
|
24
|
+
ContextProjections,
|
|
24
25
|
DiscussionPolicy,
|
|
25
26
|
ReferencePolicy,
|
|
27
|
+
RemediationThreadPolicy,
|
|
26
28
|
TextBudgets,
|
|
27
29
|
)
|
|
28
30
|
from ocr_toolkit.context.dlp import check_text, normalize_text
|
|
@@ -50,6 +52,15 @@ class BrokerResult:
|
|
|
50
52
|
required_degraded: bool
|
|
51
53
|
|
|
52
54
|
|
|
55
|
+
@dataclass(frozen=True, slots=True)
|
|
56
|
+
class ContextOrigin:
|
|
57
|
+
"""Name one provider composition edge without coupling the broker to its API."""
|
|
58
|
+
|
|
59
|
+
source: str
|
|
60
|
+
adapter: str
|
|
61
|
+
tenant: str
|
|
62
|
+
|
|
63
|
+
|
|
53
64
|
class DiscussionView(Protocol):
|
|
54
65
|
"""Expose only normalized discussion fields needed for store projection."""
|
|
55
66
|
|
|
@@ -67,6 +78,31 @@ class DiscussionView(Protocol):
|
|
|
67
78
|
digest: str
|
|
68
79
|
|
|
69
80
|
|
|
81
|
+
class RemediationReplyView(Protocol):
|
|
82
|
+
"""Expose one provider-normalized reply in a verified remediation thread."""
|
|
83
|
+
|
|
84
|
+
order: int
|
|
85
|
+
author_class: str
|
|
86
|
+
author_pseudonym: str
|
|
87
|
+
body: str
|
|
88
|
+
created_at: int
|
|
89
|
+
updated_at: int
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class RemediationThreadView(Protocol):
|
|
93
|
+
"""Expose only the common verified remediation bundle contract."""
|
|
94
|
+
|
|
95
|
+
root_author_pseudonym: str
|
|
96
|
+
root_body: str
|
|
97
|
+
anchor_state: str
|
|
98
|
+
replies: Sequence[RemediationReplyView]
|
|
99
|
+
completeness: str
|
|
100
|
+
resolved_count: int
|
|
101
|
+
outdated_count: int
|
|
102
|
+
version: str
|
|
103
|
+
digest: str
|
|
104
|
+
|
|
105
|
+
|
|
70
106
|
STATE_RE = re.compile(r"[a-z][a-z0-9_-]{0,63}\Z")
|
|
71
107
|
PSEUDONYM_RE = re.compile(r"actor-[0-9a-f]{16}\Z")
|
|
72
108
|
SHA256_RE = re.compile(r"[0-9a-f]{64}\Z")
|
|
@@ -79,19 +115,44 @@ def _project(
|
|
|
79
115
|
return {field: record[field] for field in fields if field in record}
|
|
80
116
|
|
|
81
117
|
|
|
118
|
+
def _publish_projection(
|
|
119
|
+
record: Mapping[str, object],
|
|
120
|
+
fields: Sequence[str],
|
|
121
|
+
*,
|
|
122
|
+
budgets: TextBudgets,
|
|
123
|
+
forbidden: tuple[str, ...],
|
|
124
|
+
) -> dict[str, object] | None:
|
|
125
|
+
"""Project publishable fields only after publication-specific text DLP."""
|
|
126
|
+
|
|
127
|
+
projected = _project(record, fields)
|
|
128
|
+
text = projected.get("text")
|
|
129
|
+
if isinstance(text, str):
|
|
130
|
+
checked = check_text(
|
|
131
|
+
text,
|
|
132
|
+
budgets=budgets,
|
|
133
|
+
publication=True,
|
|
134
|
+
forbidden=forbidden,
|
|
135
|
+
)
|
|
136
|
+
if not checked.admitted or checked.text != text:
|
|
137
|
+
return None
|
|
138
|
+
return projected
|
|
139
|
+
|
|
140
|
+
|
|
82
141
|
def _normalized_record(
|
|
83
142
|
record: Mapping[str, object],
|
|
84
143
|
*,
|
|
85
|
-
|
|
144
|
+
budgets: TextBudgets,
|
|
145
|
+
projections: ContextProjections,
|
|
146
|
+
resource_class: str,
|
|
86
147
|
forbidden: tuple[str, ...],
|
|
87
148
|
) -> Mapping[str, object] | None:
|
|
88
149
|
result: dict[str, object] = {}
|
|
89
|
-
for field in
|
|
150
|
+
for field in projections.retrieve:
|
|
90
151
|
if field not in record:
|
|
91
152
|
continue
|
|
92
153
|
value = record[field]
|
|
93
154
|
if field == "text":
|
|
94
|
-
checked = check_text(value, budgets=
|
|
155
|
+
checked = check_text(value, budgets=budgets, forbidden=forbidden)
|
|
95
156
|
if not checked.admitted or checked.text is None:
|
|
96
157
|
return None
|
|
97
158
|
result[field] = checked.text
|
|
@@ -106,7 +167,7 @@ def _normalized_record(
|
|
|
106
167
|
)
|
|
107
168
|
if not checked.admitted:
|
|
108
169
|
return None
|
|
109
|
-
if field == "descriptor" and normalized !=
|
|
170
|
+
if field == "descriptor" and normalized != resource_class:
|
|
110
171
|
return None
|
|
111
172
|
if field == "state" and STATE_RE.fullmatch(normalized) is None:
|
|
112
173
|
return None
|
|
@@ -160,7 +221,9 @@ def prepare_discussion_records(
|
|
|
160
221
|
records: Sequence[DiscussionView],
|
|
161
222
|
*,
|
|
162
223
|
policy: DiscussionPolicy,
|
|
224
|
+
origin: ContextOrigin,
|
|
163
225
|
expiry: int,
|
|
226
|
+
forbidden: tuple[str, ...] = (),
|
|
164
227
|
) -> tuple[PendingContextRecord, ...]:
|
|
165
228
|
"""Project provider-normalized discussions into the common private store contract."""
|
|
166
229
|
|
|
@@ -181,18 +244,41 @@ def prepare_discussion_records(
|
|
|
181
244
|
"version": record.version,
|
|
182
245
|
"expiry": expiry,
|
|
183
246
|
}
|
|
184
|
-
retrieved =
|
|
247
|
+
retrieved = _normalized_record(
|
|
248
|
+
value,
|
|
249
|
+
budgets=policy.budgets,
|
|
250
|
+
projections=policy.projections,
|
|
251
|
+
resource_class="discussion",
|
|
252
|
+
forbidden=forbidden,
|
|
253
|
+
)
|
|
254
|
+
if (
|
|
255
|
+
retrieved is None
|
|
256
|
+
or not isinstance(record.digest, str)
|
|
257
|
+
or SHA256_RE.fullmatch(record.digest) is None
|
|
258
|
+
or normalize_text(record.version) != record.version
|
|
259
|
+
or not record.version
|
|
260
|
+
or len(record.version) > 512
|
|
261
|
+
):
|
|
262
|
+
continue
|
|
263
|
+
publish = _publish_projection(
|
|
264
|
+
retrieved,
|
|
265
|
+
policy.projections.publish,
|
|
266
|
+
budgets=policy.budgets,
|
|
267
|
+
forbidden=forbidden,
|
|
268
|
+
)
|
|
269
|
+
if publish is None:
|
|
270
|
+
continue
|
|
185
271
|
pending.append(
|
|
186
272
|
PendingContextRecord(
|
|
187
|
-
source=
|
|
188
|
-
adapter=
|
|
189
|
-
tenant=
|
|
273
|
+
source=origin.source,
|
|
274
|
+
adapter=origin.adapter,
|
|
275
|
+
tenant=origin.tenant,
|
|
190
276
|
canonical_object=hashlib.sha256(f"discussion:{record.digest}".encode()).hexdigest(),
|
|
191
277
|
resource_class="issue",
|
|
192
278
|
descriptor="discussion",
|
|
193
279
|
projections={
|
|
194
280
|
"model": _project(retrieved, policy.projections.model),
|
|
195
|
-
"publish":
|
|
281
|
+
"publish": publish,
|
|
196
282
|
"retain": _project(retrieved, policy.projections.retain),
|
|
197
283
|
},
|
|
198
284
|
version=record.version,
|
|
@@ -204,6 +290,127 @@ def prepare_discussion_records(
|
|
|
204
290
|
return tuple(pending)
|
|
205
291
|
|
|
206
292
|
|
|
293
|
+
def prepare_remediation_records(
|
|
294
|
+
records: Sequence[RemediationThreadView],
|
|
295
|
+
*,
|
|
296
|
+
policy: RemediationThreadPolicy,
|
|
297
|
+
origin: ContextOrigin,
|
|
298
|
+
expiry: int,
|
|
299
|
+
forbidden: tuple[str, ...] = (),
|
|
300
|
+
) -> tuple[PendingContextRecord, ...]:
|
|
301
|
+
"""DLP-check normalized remediation views and build the fixed private projection."""
|
|
302
|
+
|
|
303
|
+
pending: list[PendingContextRecord] = []
|
|
304
|
+
for record in records:
|
|
305
|
+
root = check_text(record.root_body, budgets=policy.budgets, forbidden=forbidden)
|
|
306
|
+
if (
|
|
307
|
+
not root.admitted
|
|
308
|
+
or root.text != record.root_body
|
|
309
|
+
or not isinstance(record.root_author_pseudonym, str)
|
|
310
|
+
or PSEUDONYM_RE.fullmatch(record.root_author_pseudonym) is None
|
|
311
|
+
or record.anchor_state not in {"current", "outdated", "unpositioned"}
|
|
312
|
+
or record.completeness not in {"complete", "partial"}
|
|
313
|
+
or not isinstance(record.digest, str)
|
|
314
|
+
or SHA256_RE.fullmatch(record.digest) is None
|
|
315
|
+
or normalize_text(record.version) != record.version
|
|
316
|
+
or not record.version
|
|
317
|
+
or len(record.version) > 512
|
|
318
|
+
or not isinstance(record.resolved_count, int)
|
|
319
|
+
or isinstance(record.resolved_count, bool)
|
|
320
|
+
or not isinstance(record.outdated_count, int)
|
|
321
|
+
or isinstance(record.outdated_count, bool)
|
|
322
|
+
or record.resolved_count < 0
|
|
323
|
+
or record.outdated_count < 0
|
|
324
|
+
):
|
|
325
|
+
continue
|
|
326
|
+
replies: list[dict[str, object]] = []
|
|
327
|
+
valid = True
|
|
328
|
+
for expected_order, reply in enumerate(record.replies):
|
|
329
|
+
checked = check_text(reply.body, budgets=policy.budgets, forbidden=forbidden)
|
|
330
|
+
if (
|
|
331
|
+
reply.order != expected_order
|
|
332
|
+
or reply.author_class not in policy.account_classes
|
|
333
|
+
or reply.author_class == "toolkit_bot"
|
|
334
|
+
or not isinstance(reply.author_pseudonym, str)
|
|
335
|
+
or PSEUDONYM_RE.fullmatch(reply.author_pseudonym) is None
|
|
336
|
+
or not checked.admitted
|
|
337
|
+
or checked.text != reply.body
|
|
338
|
+
or not isinstance(reply.created_at, int)
|
|
339
|
+
or isinstance(reply.created_at, bool)
|
|
340
|
+
or not isinstance(reply.updated_at, int)
|
|
341
|
+
or isinstance(reply.updated_at, bool)
|
|
342
|
+
or reply.created_at < 0
|
|
343
|
+
or reply.updated_at < reply.created_at
|
|
344
|
+
or reply.updated_at > expiry
|
|
345
|
+
):
|
|
346
|
+
valid = False
|
|
347
|
+
break
|
|
348
|
+
replies.append(
|
|
349
|
+
{
|
|
350
|
+
"order": reply.order,
|
|
351
|
+
"author_class": reply.author_class,
|
|
352
|
+
"author_pseudonym": reply.author_pseudonym,
|
|
353
|
+
"text": checked.text,
|
|
354
|
+
"created_at": reply.created_at,
|
|
355
|
+
"updated_at": reply.updated_at,
|
|
356
|
+
}
|
|
357
|
+
)
|
|
358
|
+
if (
|
|
359
|
+
not valid
|
|
360
|
+
or not replies
|
|
361
|
+
or len(replies) > policy.max_replies_per_thread
|
|
362
|
+
or record.resolved_count > len(replies) + 1
|
|
363
|
+
or record.outdated_count > len(replies) + 1
|
|
364
|
+
or (record.anchor_state == "outdated" and record.outdated_count < 1)
|
|
365
|
+
):
|
|
366
|
+
continue
|
|
367
|
+
remediation = {
|
|
368
|
+
"root": {
|
|
369
|
+
"text": root.text,
|
|
370
|
+
"author_pseudonym": record.root_author_pseudonym,
|
|
371
|
+
},
|
|
372
|
+
"anchor_state": record.anchor_state,
|
|
373
|
+
"replies": replies,
|
|
374
|
+
"completeness": record.completeness,
|
|
375
|
+
"counts": {
|
|
376
|
+
"replies": len(replies),
|
|
377
|
+
"resolved": record.resolved_count,
|
|
378
|
+
"outdated": record.outdated_count,
|
|
379
|
+
},
|
|
380
|
+
}
|
|
381
|
+
pending.append(
|
|
382
|
+
PendingContextRecord(
|
|
383
|
+
source=origin.source,
|
|
384
|
+
adapter=origin.adapter,
|
|
385
|
+
tenant=origin.tenant,
|
|
386
|
+
canonical_object=hashlib.sha256(
|
|
387
|
+
f"remediation:{record.digest}".encode()
|
|
388
|
+
).hexdigest(),
|
|
389
|
+
resource_class="remediation_thread",
|
|
390
|
+
descriptor="remediation_thread",
|
|
391
|
+
projections={
|
|
392
|
+
"model": {
|
|
393
|
+
"descriptor": "remediation_thread",
|
|
394
|
+
"remediation_thread": remediation,
|
|
395
|
+
},
|
|
396
|
+
"publish": {"descriptor": "remediation_thread"},
|
|
397
|
+
"retain": {
|
|
398
|
+
"state": record.completeness,
|
|
399
|
+
"count": len(replies),
|
|
400
|
+
"digest": record.digest,
|
|
401
|
+
"version": record.version,
|
|
402
|
+
"expiry": expiry,
|
|
403
|
+
},
|
|
404
|
+
},
|
|
405
|
+
version=record.version,
|
|
406
|
+
digest=record.digest,
|
|
407
|
+
mutable=True,
|
|
408
|
+
expiry=expiry,
|
|
409
|
+
)
|
|
410
|
+
)
|
|
411
|
+
return tuple(pending)
|
|
412
|
+
|
|
413
|
+
|
|
207
414
|
def acquire_external_records(
|
|
208
415
|
*,
|
|
209
416
|
policy: ContextPolicy,
|
|
@@ -316,7 +523,13 @@ def acquire_external_records(
|
|
|
316
523
|
degradation_counts["invalid"] += 1
|
|
317
524
|
required_degraded = required_degraded or reference.required
|
|
318
525
|
continue
|
|
319
|
-
normalized = _normalized_record(
|
|
526
|
+
normalized = _normalized_record(
|
|
527
|
+
response.record,
|
|
528
|
+
budgets=reference.budgets,
|
|
529
|
+
projections=reference.projections,
|
|
530
|
+
resource_class=reference.resource_class,
|
|
531
|
+
forbidden=forbidden,
|
|
532
|
+
)
|
|
320
533
|
if normalized is None:
|
|
321
534
|
completeness[source] = "unavailable"
|
|
322
535
|
degradation_counts["invalid"] += 1
|
|
@@ -377,6 +590,17 @@ def acquire_external_records(
|
|
|
377
590
|
total_chars += chars
|
|
378
591
|
total_bytes += byte_count
|
|
379
592
|
total_lines += lines
|
|
593
|
+
publish = _publish_projection(
|
|
594
|
+
normalized,
|
|
595
|
+
reference.projections.publish,
|
|
596
|
+
budgets=reference.budgets,
|
|
597
|
+
forbidden=forbidden,
|
|
598
|
+
)
|
|
599
|
+
if publish is None:
|
|
600
|
+
completeness[source] = "unavailable"
|
|
601
|
+
degradation_counts["invalid"] += 1
|
|
602
|
+
required_degraded = required_degraded or reference.required
|
|
603
|
+
continue
|
|
380
604
|
admitted = PendingContextRecord(
|
|
381
605
|
source=source,
|
|
382
606
|
adapter=reference.adapter,
|
|
@@ -386,7 +610,7 @@ def acquire_external_records(
|
|
|
386
610
|
descriptor=str(normalized.get("descriptor", reference.resource_class)),
|
|
387
611
|
projections={
|
|
388
612
|
"model": _project(normalized, reference.projections.model),
|
|
389
|
-
"publish":
|
|
613
|
+
"publish": publish,
|
|
390
614
|
"retain": _project(normalized, reference.projections.retain),
|
|
391
615
|
},
|
|
392
616
|
version=response.version,
|
|
@@ -4,12 +4,16 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
from dataclasses import dataclass
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
POLICY_SCHEMA_V1 = "ocr.review-context-policy/v1"
|
|
8
|
+
POLICY_SCHEMA_V2 = "ocr.review-context-policy/v2"
|
|
9
|
+
POLICY_SCHEMAS = frozenset({POLICY_SCHEMA_V1, POLICY_SCHEMA_V2})
|
|
10
|
+
POLICY_SCHEMA = POLICY_SCHEMA_V2
|
|
11
|
+
STORE_SCHEMA = "ocr.context-store/v2"
|
|
9
12
|
REQUEST_SCHEMA = "ocr.context-adapter-request/v1"
|
|
10
13
|
RESPONSE_SCHEMA = "ocr.context-adapter-response/v1"
|
|
11
14
|
ACCOUNT_CLASSES = frozenset({"user", "automation", "system", "toolkit_bot"})
|
|
12
|
-
|
|
15
|
+
REFERENCE_RESOURCE_CLASSES = frozenset({"issue", "document"})
|
|
16
|
+
STORE_RESOURCE_CLASSES = frozenset({*REFERENCE_RESOURCE_CLASSES, "remediation_thread"})
|
|
13
17
|
PROJECTION_FIELDS = frozenset(
|
|
14
18
|
{
|
|
15
19
|
"descriptor",
|
|
@@ -28,6 +32,8 @@ PROJECTION_FIELDS = frozenset(
|
|
|
28
32
|
"expiry",
|
|
29
33
|
}
|
|
30
34
|
)
|
|
35
|
+
REMEDIATION_MODEL_FIELD = "remediation_thread"
|
|
36
|
+
STORE_PROJECTION_FIELDS = frozenset({*PROJECTION_FIELDS, REMEDIATION_MODEL_FIELD})
|
|
31
37
|
RETENTION_FIELDS = frozenset({"state", "count", "digest", "version", "expiry"})
|
|
32
38
|
|
|
33
39
|
|
|
@@ -78,6 +84,21 @@ class DiscussionPolicy:
|
|
|
78
84
|
projections: ContextProjections
|
|
79
85
|
|
|
80
86
|
|
|
87
|
+
@dataclass(frozen=True, slots=True)
|
|
88
|
+
class RemediationThreadPolicy:
|
|
89
|
+
"""Select verified toolkit-owned GitLab remediation threads."""
|
|
90
|
+
|
|
91
|
+
required: bool
|
|
92
|
+
account_classes: tuple[str, ...]
|
|
93
|
+
include_resolved: bool
|
|
94
|
+
include_outdated: bool
|
|
95
|
+
max_age_seconds: int
|
|
96
|
+
max_threads: int
|
|
97
|
+
max_replies_per_thread: int
|
|
98
|
+
max_items: int
|
|
99
|
+
budgets: TextBudgets
|
|
100
|
+
|
|
101
|
+
|
|
81
102
|
@dataclass(frozen=True, slots=True)
|
|
82
103
|
class RecognizerPolicy:
|
|
83
104
|
"""Hold one fixed toolkit-authored candidate grammar."""
|
|
@@ -110,5 +131,6 @@ class ContextPolicy:
|
|
|
110
131
|
schema_version: str
|
|
111
132
|
budgets: AggregateBudgets
|
|
112
133
|
forge_discussions: DiscussionPolicy | None
|
|
134
|
+
remediation_threads: RemediationThreadPolicy | None
|
|
113
135
|
references: tuple[ReferencePolicy, ...]
|
|
114
136
|
digest: str
|