Skip to content

Commit c4f96aa

Browse files
authored
Merge pull request #115 from JiusiServe/consolidate/reviewbot-sdk-v1
Publish the embedded SDK boundary for ReviewBot
2 parents c04e01d + 229ded5 commit c4f96aa

32 files changed

Lines changed: 4649 additions & 376 deletions

‎.github/workflows/test.yml‎

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,63 @@ jobs:
6666
/tmp/clean/bin/pip install --quiet dist/*.whl
6767
cd /tmp # import the INSTALLED package, never the source checkout
6868
/tmp/clean/bin/python - <<'PY'
69+
import importlib.metadata
70+
import importlib.resources
6971
import sys
72+
import tempfile
73+
from pathlib import Path
74+
from infermatrix_copilot import __version__
75+
from infermatrix_copilot.sdk.v1 import (
76+
ChangedPath, DirectClient, DirectReviewRequest,
77+
KNOWLEDGE_API_VERSION,
78+
KnowledgeCurator, KnowledgeEvidenceBatch,
79+
KnowledgeEvidenceEvent, RepositoryRef, StrictRuntime,
80+
get_capabilities,
81+
)
82+
forbidden = {
83+
"infermatrix_copilot.config",
84+
"infermatrix_copilot.contract",
85+
"infermatrix_copilot.direct_routing",
86+
"infermatrix_copilot.mcp_server",
87+
}
88+
loaded = sorted(forbidden.intersection(sys.modules))
89+
if loaded:
90+
sys.exit(f"public SDK import loaded private implementation: {loaded}")
91+
caps = get_capabilities()
92+
dist_version = importlib.metadata.version("infermatrix-copilot")
93+
if not (__version__ == dist_version == caps.distribution_version == "0.2.0"):
94+
sys.exit(
95+
"version mismatch: "
96+
f"module={__version__}, metadata={dist_version}, "
97+
f"capabilities={caps.distribution_version}"
98+
)
99+
if not caps.resource_revision.startswith("sha256:"):
100+
sys.exit("SDK capabilities have no resource revision")
101+
if not caps.supports_knowledge_curation:
102+
sys.exit("SDK capabilities omit knowledge curation")
103+
if not (
104+
caps.knowledge_api_version == KNOWLEDGE_API_VERSION == "1.0.0"
105+
):
106+
sys.exit("installed SDK knowledge API version mismatch")
107+
if KnowledgeCurator.proposal_schema(max_rules=3)["properties"][
108+
"rules"
109+
]["maxItems"] != 3:
110+
sys.exit("installed SDK has a broken knowledge proposal schema")
111+
evidence = KnowledgeEvidenceBatch(
112+
batch_id="wheel-smoke",
113+
repository=RepositoryRef(alias="vllm-omni"),
114+
events=(KnowledgeEvidenceEvent(
115+
event_id="event-1", source_kind="merged_pr",
116+
source_reference="PR #1", title="wheel smoke",
117+
),),
118+
)
119+
if evidence.to_dict()["events"][0]["event_id"] != "event-1":
120+
sys.exit("installed SDK knowledge models are not serializable")
121+
if not importlib.resources.files("infermatrix_copilot").joinpath(
122+
"py.typed"
123+
).is_file():
124+
sys.exit("wheel does not ship py.typed")
125+
70126
from infermatrix_copilot.config import _resource_dir, _RESOURCE_MARKERS
71127
# resolve through the production probe and require its marker to exist:
72128
# a present-but-empty directory would satisfy a bare is_dir() check
@@ -79,6 +135,33 @@ jobs:
79135
bad.append(f"{name}: {d} has no {marker}")
80136
if bad:
81137
sys.exit("wheel runtime data broken: " + "; ".join(bad))
138+
client = DirectClient()
139+
for repo, changed in (
140+
("vllm-omni", "vllm_omni/core/sched/scheduler.py"),
141+
("afd-plugin", "afd_plugin/compat/vllm.py"),
142+
):
143+
plan = client.plan(DirectReviewRequest(
144+
review_id="wheel-smoke",
145+
repository=RepositoryRef(alias=repo),
146+
pr_number=1,
147+
expected_head_sha="a" * 40,
148+
title="runtime change",
149+
body="exercise the installed SDK",
150+
changed_paths=(ChangedPath(changed),),
151+
))
152+
if not plan.knowledge_routes:
153+
sys.exit(f"installed SDK did not route {repo}")
154+
if any("/" == ref.document.document_id[:1]
155+
for ref in plan.knowledge_routes):
156+
sys.exit("installed SDK leaked an absolute document path")
157+
if Path(plan.navigation_policy["fallback_document_id"]).is_absolute():
158+
sys.exit("installed SDK leaked an absolute fallback path")
159+
with tempfile.TemporaryDirectory() as run_root:
160+
with StrictRuntime(settings_overrides={
161+
"_env_file": None, "run_root": run_root,
162+
}) as strict:
163+
if strict.capabilities().distribution_version != "0.2.0":
164+
sys.exit("Strict facade capabilities mismatch")
82165
print("packaged runtime data resolves via _resource_dir:",
83166
", ".join(_RESOURCE_MARKERS))
84167
PY

‎README.md‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,13 @@ python knowledge/tools/check_knowledge_tree.py
252252
python knowledge/tools/check_wiki_lint.py
253253
```
254254

255+
自动知识回流也遵循同一条边界:本仓库发布的
256+
`infermatrix_copilot.sdk.v1.KnowledgeCurator` 是 catalog、受信 prompt、proposal
257+
shape/ID/page/source 校验、append-only apply 与两道 validator rollback 的唯一实现;
258+
ReviewBot 只把 evidence 和 model JSON 接到该 API,并继续拥有 dedicated clone、
259+
ledger/retry、Git commit、fork push、PR 与 schedule。SDK 不 clone、不调 model、
260+
不发布,也不会写 wheel 内的 knowledge 副本。
261+
255262
## 快速上手 4 · CI 修复(imcifix)
256263

257264
从 GitHub issue 出发:本地复现、最小修复、针对性验证;默认不 commit、

‎doc/README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
| [`features/strict-review-deep-engine.md`](features/strict-review-deep-engine.md) | 已实现(默认开) |
3737
| [`features/review-recall.md`](features/review-recall.md) | 已实现(默认开)——v14/v15 召回攻坚 |
3838
| [`features/auto-run.md`](features/auto-run.md) | **draft,未实现**——GitHub 事件触发 |
39-
| [`RFC-knowledge-intake.md`](RFC-knowledge-intake.md) | 已实现(默认关,shadow 起步)——合并 PR 与 bugfix 学习经 reviewbot 蒸馏,以 fork PR 回流知识库,人审合并即晋升 |
39+
| [`RFC-knowledge-intake.md`](RFC-knowledge-intake.md) | 已实现(默认关,shadow 起步)——Copilot typed SDK 唯一拥有知识规则,ReviewBot 只编排 evidence/model/Git/fork PR,人审合并即晋升 |
4040

4141
## architecture/ —— 维护者
4242

‎doc/RFC-knowledge-intake.md‎

Lines changed: 83 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
# RFC — Knowledge intake: merged-PR and bugfix learnings return as reviewed knowledge PRs
22

3-
- Status: implemented, shadow-first and default-off — copilot side in
4-
PR #110 (`pr.harvest_debug_knowledge`), reviewbot side in
5-
zuiho-kai/omni-reviewbot#21 (intake ledger) and #22 (daily distiller)
6-
- Owner: knowledge plane (`knowledge/`, `AGENTS.md` contract) on this side;
7-
`omni_reviewbot.knowledge_intake` / `knowledge_distiller` on the bot side
3+
- Status: implemented, shadow-first and default-off — producer intake in
4+
PR #110 (`pr.harvest_debug_knowledge`), provider domain contract in public
5+
SDK 0.2.0 (`infermatrix_copilot.sdk.v1.KnowledgeCurator`), and ReviewBot
6+
orchestration in zuiho-kai/omni-reviewbot#21/#22
7+
- Owner: this repo uniquely owns knowledge policy, catalog/prompt/proposal/apply
8+
semantics and validators; ReviewBot owns evidence/model/Git/ledger/publication
9+
orchestration and consumes the versioned public SDK only
810
- Evidence: `test/test_knowledge_harvest.py` (step + executor/resume e2e),
11+
`test/test_sdk_knowledge_v1.py` (provider boundary, rollback and locking),
912
reviewbot `tests/test_knowledge_intake.py`, `tests/test_knowledge_distiller.py`,
1013
`tests/test_e2e_knowledge_flow.py` (cross-repo contract pinned)
1114

@@ -54,23 +57,29 @@ Three stages, deliberately decoupled so each can fail without losing events.
5457

5558
### 2. Distillation (expensive, at most once a day)
5659

57-
- The model **only proposes**: it reads a dedicated work clone of this repo
58-
(never the live checkout the Direct bridge imports) and returns
59-
catalog-constrained structured output — target `rules.md` page from the
60-
scanned catalog, a new auditable rule id, one complete section in the
61-
page's own language and format, cited sources. Source events are fenced
62-
as `<untrusted_data>`; instructions never come from them.
63-
- Code **applies mechanically and append-only**: new sections plus an
64-
`updated:` frontmatter bump. Existing sections are never rewritten —
65-
merging and pruning stay human/curator work. Proposals that miss the
66-
catalog, collide with an existing rule id, or fail shape checks are
67-
dropped and counted.
68-
- The **validators are the gate**: `check_knowledge_tree.py` and
69-
`check_wiki_lint.py` run in the clone; a failure — or a missing
70-
validator — fails the batch closed. Rows stay `pending` and retry on the
71-
next daily batch, bounded by an attempt cap that parks poison rows
72-
visibly as `error`. The vllm-omni release audit runs in CI on the PR, as
73-
it does for any knowledge edit.
60+
- ReviewBot converts pending rows into typed `KnowledgeEvidenceEvent` values
61+
and one bounded `KnowledgeEvidenceBatch`. It creates `KnowledgeCurator`
62+
over a dedicated work clone of this repo — never the immutable knowledge
63+
resources shipped inside the runtime wheel — then asks the SDK to build the catalog-constrained
64+
prompt and strict JSON schema. ReviewBot invokes the configured model; the
65+
model **only proposes**.
66+
- The SDK treats all source events and model output as untrusted. Evidence is
67+
fenced as `<untrusted_data>`; catalog membership, proposal shape, rule ID,
68+
one complete heading, source citations, duplicate IDs and target-page SHA
69+
are checked mechanically. Accepted proposals receive content-addressed IDs;
70+
rejected model indexes and stable reasons are returned to the host.
71+
- `KnowledgeCurator.apply()` **applies mechanically and append-only**: complete
72+
new sections plus the one `updated:` frontmatter bump. Existing sections are
73+
never rewritten. The SDK serializes writers across threads/processes and
74+
rechecks target SHAs after acquiring the lock, so a stale daily worker cannot
75+
overwrite a concurrent edit.
76+
- The **validators are the gate**: the SDK runs exactly
77+
`check_knowledge_tree.py`, then `check_wiki_lint.py`, inside the clone. A
78+
missing validator fails before any write; a failed, timed-out or unlaunchable
79+
validator restores every target page byte-for-byte and raises a typed error.
80+
ReviewBot leaves rows `pending` for retry, bounded by its attempt cap. The
81+
vllm-omni release audit additionally runs in CI on the PR, as for any
82+
knowledge edit.
7483

7584
### 3. Publication (fork-based, double-gated)
7685

@@ -100,9 +109,13 @@ Three stages, deliberately decoupled so each can fail without losing events.
100109
branch recovery was considered and skipped: it trades real complexity
101110
for saving one day of latency on an already-daily loop.
102111

103-
## The cross-repo contract
112+
## The cross-repo contracts
104113

105-
The drop record is the only coupling between the two repos:
114+
There are two narrow, versioned seams; neither repo imports the other's private
115+
modules.
116+
117+
The first is the producer drop record written by a Copilot run and consumed by
118+
ReviewBot:
106119

107120
```json
108121
{"run_id": "...", "repo": "owner/repo", "pr": 123, "kind": "bugfix_run",
@@ -122,6 +135,33 @@ The drop record is the only coupling between the two repos:
122135
side's test but not the other's; the contract therefore changes only in
123136
lockstep, with this section as the reference.
124137

138+
The second is the installed public Python SDK used by ReviewBot's adapter. The
139+
call order is deliberately small:
140+
141+
```python
142+
curator = KnowledgeCurator(dedicated_clone)
143+
batch = KnowledgeEvidenceBatch(...)
144+
prompt = curator.build_prompt(batch)
145+
model_json = reviewbot_model_call(
146+
prompt, output_schema=curator.proposal_schema(max_rules=batch.max_rules)
147+
)
148+
validation = curator.validate_proposals(model_json, batch)
149+
result = curator.apply(validation)
150+
```
151+
152+
All values crossing that seam have lossless `to_dict()` projections and contain
153+
document IDs, never provider absolute paths. ReviewBot uses `accepted_indexes`
154+
and `rejected_indexes` to account for model rows. Malformed requests raise
155+
`InvalidRequestError`; catalog/integrity/stale-writer failures raise
156+
`KnowledgeCurationError`; validator failures raise `KnowledgeValidatorError`
157+
whose `.result` records validator status/output and whether bytes were rolled
158+
back. ReviewBot catches these public errors and owns retry/artifact decisions.
159+
160+
The SDK explicitly has no clone, model, ledger, commit, push, pull-request or
161+
scheduling operation. Conversely, ReviewBot has no local catalog/prompt/parser/
162+
apply/validator implementation: knowledge policy changes are released here and
163+
adopted by pinning a new wheel version.
164+
125165
## Fit with this repo's invariants
126166

127167
- **Repo neutrality**: the harvest step contains no repo literal; the drop
@@ -132,6 +172,9 @@ The drop record is the only coupling between the two repos:
132172
- **Read-wide/write-narrow**: the tree is still written only by humans
133173
merging PRs; the automation's write surface is a drop directory and a
134174
fork.
175+
- **Single policy owner**: knowledge domain rules live behind this repo's
176+
public SDK; the ReviewBot adapter is orchestration-only. Runtime state and
177+
publication credentials never flow into the provider library.
135178

136179
## Alternatives considered
137180

@@ -141,26 +184,33 @@ The drop record is the only coupling between the two repos:
141184
- **Direct writes to the tree (or pushing to this repo)** — rejected
142185
outright: it deletes the human promotion step the knowledge plane is
143186
built on, and would require granting the bot write access here.
144-
- **Copilot-hosted distiller** — this repo has no daemon; the reviewbot
145-
already runs a poll loop with maintenance gating, budgets, and crash
146-
recovery, so the scheduled half lives there and this repo stays a
147-
library plus one playbook step.
187+
- **Copilot-hosted daemon/orchestrator** — rejected: this repo has no daemon;
188+
ReviewBot already runs a poll loop with maintenance gating, budgets and crash
189+
recovery. **Copilot-owned domain library** is the chosen split: policy stays
190+
beside the governed knowledge tree without moving scheduling or publication.
191+
- **Duplicated distillation helpers in ReviewBot** — rejected: catalog, prompt,
192+
validation and append logic would drift independently from `knowledge/`
193+
governance. The versioned SDK makes that dependency explicit and testable.
148194
- **Stopping at skill candidates / debug memory** — that is where learnings
149195
already go today; both are run-scoped proposals with no path into the
150196
human-curated tree. This design is that missing path, not a replacement
151197
for them.
152198

153199
## Rollout
154200

155-
1. Reviewbot: `KNOWLEDGE_INTAKE_ENABLED=true` — merged-PR recording plus
156-
daily shadow batches; inspect artifacts and `knowledge-batch` output.
157-
2. Wire the bugfix channel: point this repo's `KNOWLEDGE_INTAKE_DIR` and
201+
1. Build and pin InferMatrixCopilot SDK `0.2.0` in ReviewBot; its adapter must
202+
import only `infermatrix_copilot.sdk.v1` and exercise the full call order in
203+
a temp-clone integration test.
204+
2. ReviewBot: `KNOWLEDGE_INTAKE_ENABLED=true` — merged-PR recording plus daily
205+
shadow batches; inspect typed curation results, artifacts and
206+
`knowledge-batch` output.
207+
3. Wire the bugfix channel: point this repo's `KNOWLEDGE_INTAKE_DIR` and
158208
the reviewbot's `COPILOT_INTAKE_DIR` at the **same directory** (v1
159209
assumes a shared host). Without this pair, everything else works but
160210
`pr_debug` learnings are silently absent from batches — both values
161211
default to empty/off.
162-
3. Create the bot's fork of this repo; set `KNOWLEDGE_FORK_SLUG`.
163-
4. `KNOWLEDGE_PR_ENABLED=true` under `POST_MODE=review` — PRs start; every
212+
4. Create the bot's fork of this repo; set `KNOWLEDGE_FORK_SLUG`.
213+
5. `KNOWLEDGE_PR_ENABLED=true` under `POST_MODE=review` — PRs start; every
164214
one is reviewed like any other knowledge edit.
165215

166216
Rollback at any stage is turning the flag off; nothing in the tree changes

‎doc/architecture/SPEC/contract.md‎

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,34 @@
11
# contract.py —— 规范
22

3-
<!-- verified-against: 2026-08-28 -->
3+
<!-- verified-against: 2026-08-31 -->
44

5-
`LOC ~181 · 跨仓库公开契约面 · refactor-status: ok`
5+
`LOC ~185 · 旧版跨仓库契约兼容层 · refactor-status: compatibility-shim`
66

77
## 职责
8-
本 copilot 的**对外消费契约**:一个 review bot(或任何宿主)被允许 import 的
9-
一切都住在这里,此外的任何 import 都不受支持。它的存在源于一次真实事故:
8+
本 copilot 的**旧版对外消费契约兼容层**。新宿主只允许 import
9+
`infermatrix_copilot.sdk.v1`;本模块继续服务既有 MCP 与尚未迁移的调用者。
10+
它的存在源于一次真实事故:
1011
下游仓库曾经用 `importlib` 伸进 `thin_mcp_server` 拿四个 `_direct_*` 私有名,
1112
一次重命名就让另一个仓库在运行期断裂、且没有任何构建期信号。
1213

1314
## 公开契约(`__all__`)
14-
`DIRECT_API_VERSION` / `STRICT_API_VERSION`(`"1.0.0"`,形状变化到消费方
15-
必须察觉时递增);`capabilities(max_strict_workers=1,
16-
supports_file_locking=True) -> dict`(版本/能力握手:`supports_expected_head`、
15+
`SDK_API_VERSION` / `DIRECT_API_VERSION` / `STRICT_API_VERSION` /
16+
`KNOWLEDGE_API_VERSION`(当前均为 `"1.0.0"`,且都从 `sdk.v1.models`
17+
取唯一值)。`capabilities(max_strict_workers=1,
18+
supports_file_locking=True) -> dict` 委托 SDK typed handshake 再投影为兼容
19+
dict;它包含 distribution/SDK/Direct/Strict/Knowledge 版本、resource
20+
revision、supported repositories,以及 `supports_expected_head`、
1721
`supports_structured_result`、`supports_post_false`、`supports_file_locking`、
18-
`max_strict_workers`);`build_review_result(run_dir) -> dict`(结构化评审
22+
`supports_idempotent_strict_start`、`supports_knowledge_curation`、
23+
`max_strict_workers`。其余兼容导出包括 `build_review_result(run_dir) -> dict`(结构化评审
1924
结果:`contract_version`、`run_id`、`state`、`reviewed_head_sha`、`verdict`、
2025
`summary_markdown`、`comments`、`stale`/`expected_head_sha`/
2126
`actual_head_sha`、`diagnostics`);`unknown_run_result(run_id)`(显式
2227
`state: unknown`,绝不抛错 —— 丢响应和还在跑必须可区分);
2328
`sanitize_comments` 与 `COMMENT_FIELDS`;以及从 `direct_routing` 再导出的
2429
`direct_knowledge_routes` / `direct_execution_budget` /
25-
`direct_completion_result` / `direct_mandatory_review_guides`。
30+
`direct_completion_result` / `direct_mandatory_review_guides` / 完整的
31+
`direct_review_plan`。
2632

2733
## 不变量
2834
- **依赖方向单向、由测试钉住**:本模块 import 数据层(`run_status`、
@@ -38,6 +44,8 @@ supports_file_locking=True) -> dict`(版本/能力握手:`supports_expected_
3844
(`_verified`、`corroborated_by` 等)绝不泄进消费方输出。
3945
- 本模块自身保持仓库中立;仓库专属的 Direct 路由表住在
4046
`direct_routing.py`(见其页)。
47+
- 能力身份的权威实现在 `sdk.v1.get_capabilities`;这里不维护第二份版本或
48+
resource-revision 算法。
4149

4250
## 边界 —— 不属于这里
4351
不调模型、不执行 run、不做策略强制(`mcp_policy`)、不实现 Direct 路由

0 commit comments

Comments
 (0)